當你把兩個版本之間實際變動的行分離出來時,閱讀困難的文字會變得輕鬆許多,因為那沒有變動的九成內容不再跟你搶注意力。把這些行分離出來最可靠的方法,就是把兩個版本都貼到本機的差異比對工具中,逐行進行比對,並在單一排序檢視中標示出每一列的新增、刪除與未變動。Text Diff Checker正是這樣做的——全程在你的電腦本機上執行,既不需要上傳,也不會呼叫任何第三方。這項技巧無論是在研讀一段被編輯修訂過的段落、比較自己寫的兩份草稿,或是想拿一份遭到大量編輯的法律或技術文件與較舊的公開版本相互對照時,都一樣適用。你不再需要把整份文件重新讀過,只要快速掃過一欄由加號、減號與未變動列組成的內容,把真正的注意力只集中在那些有差異的列上。由於比對是逐行進行的,而且採用嚴格的字串相等比對,因此你也能看見那些細微的機械性改動——一個被替換的詞、一句插入的句子、一個被刪除的子句——這些往往正是你之前漏掉的意義所在。

how to read difficult texts for understanding
在本機比對兩個版本以閱讀困難的文字

為什麼差異比對能幫助你閱讀困難的文字

困難的段落之所以困難,是因為你的工作記憶一次要追蹤的候選項目太多:詞彙、結構、先前脈絡以及陌生的引用,全都同時在爭奪你的注意力。當你盯著整頁看時,文字中沒有變動的部分對理解幾乎毫無貢獻,卻仍然佔據了視覺空間。差異比對檢視正好相反——它把差異推到前景,把其餘部分降級為中性的前置符號。現在,你的眼睛可以跳過每一個沒有變動的列,並停留在有變動的列上。最終結果是,同樣的理解工作,只需要在一個小得多的範圍上完成。

同一個技巧對於遭到大量編輯的文件也很有用。一份修訂過的政策、一條重新起草的合約條款、一段改寫過的學術段落,或一份清理過的日誌,通常都保留了大部分原本的措辭,只更動少數幾行。從頭到尾閱讀整份修訂過的版本,會讓新內容看起來像是混在舊內容裡的雜訊,於是你會錯過真正被改動的地方。只閱讀差異比對中標記為 + 和 - 的列,可以把編輯意圖還原到它應有的比例,而那通常正是你一開始坐下來想理解的事情。

當文字因為其他原因而顯得密集時,這也同樣有幫助——無論是原文語言、排版,還是主題本身很陌生。把一段文字與一個乾淨的參考版本進行標記比對,可以為你提供一個範圍有限的小區域,其中每一列都是作者刻意改動的,因此每一列都是值得重讀、翻譯或註解的候選。這個聚焦過的範圍,遠比整份文件更容易研讀。

在比對之前先準備好兩個版本

決定哪個版本是基準,哪個版本是用來比對的一方。一般的慣例是把較舊的版本貼在左邊,較新的版本貼在右邊,但這個工具對兩側的處理是對稱的——唯一的規則是,以 + 開頭的列表示該行出現在右側但不在左側,以 - 開頭的列表示該行出現在左側但不在右側。

在貼上之前,先把多餘的格式去掉。如果差異比對中混雜了粗體標記、文書處理器產生的智慧引號,以及軟換行,就會產生許多反映格式而非內容的雜訊列。從文件中複製後以純文字貼上,通常可以去除樣式,只保留底層字元。這個工具會將 CRLF、單獨的 CR 以及 LF 都視為換行邊界,且 CRLF 算作一個邊界,因此一個在 Windows 儲存的檔案和一個在 Unix 儲存的檔案,在兩者皆以純文字貼上後,就能夠乾淨地相互比對。

對於敏感資料來說,只在瀏覽器中處理這件事值得認真看待。兩個版本都會停留在目前的分頁中,當你關閉或重新整理頁面時就會一併被丟棄。不會上傳任何內容,不會發出任何程式庫呼叫,也不會保留任何歷史紀錄。這使得它適用於尚未發表的草稿、內部政策、客戶文件,以及任何你不會貼到雲端服務的文字。

在瀏覽器中執行並排差異比對

  1. 把原文貼在左邊,把改過的文字貼在右邊。
  2. 選擇Compare lines
  3. 檢視結果:以加號開頭的列表示新增、以減號開頭的列表示刪除、以中性前置符號開頭的列則代表未變動的行。
  4. 先閱讀頂端的摘要計數,確認變動的規模,再開始閱讀各列內容。

比對步驟是同步執行的,並使用確定性的最長共同子序列演算法。這表示同樣的兩組輸入,一定會以同樣的順序產生同樣的列;當你向同事逐步說明變更內容,並想確保兩人看到的是同一份檢視時,這一點非常重要。

如何閱讀輸出的各列

結果中的每一列都是下列三者之一。沒有前置符號或為空白標記的列,代表該行在兩個版本中以相同的位置出現。以 + 開頭的列,代表該行存在於修改後的版本,但不存在於原始版本。以 - 開頭的列,代表該行原本存在於原始版本,但不存在於修改後的版本。列上方摘要會報告三個計數——新增、刪除與未變動——這些計數是由各列本身推導出來的,因此你所閱讀的內容與摘要所宣稱的內容保證一致。

實際的閱讀順序是先掃過摘要。如果未變動的計數很大,而新增或刪除的計數很小,你面對的是一份輕度編輯過的文字,應該把焦點放在少數有差異的列上。如果新增與刪除的計數都很大,代表文件已被大幅重寫,逐列閱讀是合理的做法。如果未變動的計數接近零,那你基本上是在閱讀兩份碰巧主題相近的不同文件,差異比對並不是合適的工具——這時應該分別閱讀兩側的文件。

當你真的要閱讀各列時,請按照工具由上到下列印的順序來讀。實作會保留原本的行字串及其在每個來源中的順序,因此新增或刪除在文件中的位置會被保留下來。正是這個位置賦予了一列意義:夾在兩個未變動列之間的新增,屬於局部插入;而一整段沒有未變動列夾在其間的新增與刪除,則屬於段落層級的重寫。

差異比對會抓到與不會抓到的事

列前置符號意義該如何處理
空白該行在兩個版本中完全相同地出現。除非需要周圍列的脈絡,否則可跳過。
+該行只出現在右側版本中。仔細閱讀;這是新內容。
-該行只出現在左側版本中。確認它在新版中確實已經消失。

比對的單位是一整行,而且只有在字串完全相等時——包括大小寫、標點、前置空格、尾端空格、Tab 字元以及 Unicode 碼位——行才會被視為相符。因此,一個字元的編輯會以一個被刪除的列加上一個新增的列呈現。這種較粗略的檢視是刻意的:它能讓清單、設定檔、日誌、散文段落與小段程式碼摘錄保持易讀,並避免字元層級高亮所帶來的視覺雜訊。其代價在於,你無法僅從差異比對中看出被修改那一行裡的哪個字元被改動了。

這個工具不是一個補丁產生器。它不會加上行號、不會偵測被搬移的區塊、不會忽略空白、不會摺疊大小寫、不會解析程式語言、不會高亮字元層級的編輯,也不會處理合併衝突。重複的行可能產生多個同樣有效的最佳對齊方式,而實作會以一條確定性的規則來處理平手情況:當剩下的子序列長度相等時,偏好新增的那一邊。輸出結果是一種說明性的檢視,而不是你可以餵給其他程式的 unified-diff 檔案。

差異比對不足以應付的情況

每一側的上限為 500 行以及 200,000 個 UTF-16 碼位,任一側超過此上限就會在建構動態規劃表格之前被拒絕。設定這個上限,是因為比對所需的計算量會隨行數呈二次方成長,而一份未設上限的長文件貼上來,會讓分頁當機。如果你需要比對更大的內容,請先把它切分,或使用能夠處理 MB 級檔案而不必載入瀏覽器的串流式或原生差異比對工具。

對於程式碼儲存庫,請使用 Git 而非這個工具。Git 會跨多個版本追蹤歷史、會處理檔名與重新命名、會產生正規的補丁、會記錄作者資訊,也會解決合併衝突。Text Diff Checker 沒有上述任何功能。它能提供給你的,是一份聚焦於、僅僅兩個有上限文字版本、以行為單位的本機檢視——不需要帳號,也不需要上傳——而這正是它最能真正幫助你閱讀並理解一段困難文字的情境。

還有一件事值得記住的是,這個工具所報告的相等,是依其明文規則下的嚴格字串相等,而不是語意上的等價。不同的 Unicode 正規化形式、視覺上相似的字元、藏在跳脫字串內的換行字元,或是隱形的空白,都可能產生肉眼看起來相同、卻在差異比對中記錄為差異的列。對於敏感的變更,在把差異比對當作紀錄來依賴之前,請在目的端的應用程式中開啟兩個版本,並於該處確認變更內容。

如果你正在權衡各種做法,Text to Slug Bulk: Convert Many Titles Locally對此有詳細說明。