兩頁文件以不同顏色標示差異,配有連接箭嘴和放大鏡。

Diff 比對工具會直接告訴你兩份文字之間改了什麼——哪些行被新增、哪些被刪除、哪些被改寫——而不是叫你自己用眼睛掃過兩份文件。這篇指南說明怎麼執行比對、怎麼讀結果,以及哪些設定會決定輸出是有效訊號還是一堆噪音。

它適用於原始碼、設定檔、SQL migration、合約與文章,而且比對完全在你的瀏覽器裡完成。要比對兩份 JSON payload 之前,先用 JSON 格式化工具 把兩邊正規化,才不會把縮排也當成變更;如果拿到的是 Base64 編碼後的文字,先用 Base64 編碼 / 解碼 解開。

01Diff 到底在比什麼

Diff 不是逐字元比對相等,而是計出兩邊之間的最短編輯腳本(shortest edit script)——用最少的新增與刪除行數,把一個版本變成另一個——再回報這些新增與刪除:只出現在左邊的行是刪除,只出現在右邊的行是新增,其餘是上下文。Git 預設用的 myers 演算法解的就是同一道問題。

這解釋了為什麼長文件裡的一個小修改只會產生很小的 diff——即使兩份文件差了上百個字元,也只有被改動的行會被標記。實務上有兩個後果值得注意:

  • 被搬移的區塊看起來像刪除加新增,因為算法比的是位置而不是意圖。
  • 排版變更——重新縮排、換行符號改變、行尾空白——都會被當成真實差異,除非你叫工具忽略它們。

02比對兩段文字

  1. 把原始版本貼進 Original — 左邊是基準版本。可以貼上、手打,或用 Upload 上傳檔案。
  2. 把修改版本貼進 Modified — 右邊放新版本。兩邊都能接受大量貼上與檔案上傳。
  3. 按 Compare — 結果區會用紅色標記刪除、綠色標記新增,摘要則回報有多少個區塊變更、新增與刪除了幾行。
  4. 在變更之間跳轉 — 用 Previous 與 Next 逐塊移動,不必自己滾動。計數器(例如「3 changes · 2 added · 1 deleted」)會告訴你實際動了多少。
  5. 選擇並排或統一檢視 — Split 把兩個版本左右對齊;Unified 收成單一 patch 欄位,也就是審閱者熟悉的格式,貼進工單時用的就是它。
  6. 調整粒度與過濾 — 在 Line、Word 與 Char 之間切換,差異只在排版時打開 Ignore whitespace 或 Ignore case。
  7. 交換、複製或匯出 — Swap Sides 反轉比對方向,Copy 取出任一欄,Export .patch 下載標準統一差異檔,也就是 git apply 讀得懂的格式,可直接附在審閱裡。

03行、詞或字元粒度

行粒度是預設值,也是程式碼與設定檔的正確選擇,因為審閱就是以行為單位,git 回報變更的方式也是。

詞粒度會保留被改的那一行裡沒動的部分,所以段落中改一個詞不會看起來像整句重寫。文章、commit message 與翻譯適合用這個。

字元粒度再深一層,用來抓看不見的變更:換了引號、改了分隔符,或是一個長得像普通空白的不斷行空格(non-breaking space)。當你知道確切的樣式後,正則表達式測試工具 可以幫你在整份檔案裡找出並取代所有出現的位置。

04忽略空白與忽略大小寫

兩份看起來一模一樣的檔案,還是可能在你沒打算的地方不同。把 JSON 重新縮排、CRLF 換成 LF,或編輯器補上的行尾空白,都會產生蓋掉真正變更的 diff 噪音。

Ignore whitespace 會把連續空白與 tab 收攏,並在比對行時忽略前後空白,所以純粹重新排版的檔案會回報沒有變更——等同 GNU diffutils 的 diff -w 與 diff -b。Ignore case 會把 ALPHA 與 alpha 視為相同(diff -i),比對設定鍵或 SQL 關鍵字時很方便。

05讀懂統一差異檔

以下是統一差異裡的單一 hunk:刪除行以 - 開頭、新增行以 + 開頭,沒改動的上下文行以空格開頭。在 hunk 標頭裡,@@ -12,7 +12,8 @@ 表示這個 hunk 從第 12 行開始,在原始檔涵蓋 7 行,在修改檔涵蓋 8 行。

--- original
+++ modified
@@ -12,7 +12,8 @@ export function useUser(userId) {
   const [data, setData] = useState(null);
-  const [loading, setLoading] = useState(false);
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState(null);

06變更長什麼樣子

被改掉的設定值

輸入

timeout = 30
retries = 3
log_level = "info"

輸出

  timeout = 30
- retries = 3
+ retries = 5
- log_level = "info"
+ log_level = "debug"

一次重構,並移除空白噪音

輸入

const total = items.reduce((sum, item) => sum + item.price, 0)

輸出

const total = items
  .filter((item) => item.active)
  .reduce((sum, item) => sum + item.price, 0)

打開 Ignore whitespace 之後,只會回報新增的 .filter 那一行;拆成多行的 .reduce 不會被當成重寫。

先檢查空白,再懷疑邏輯

如果每一行都被標成變更,兇手通常是縮排或換行符號,而不是程式邏輯。先打開「忽略空白」再看。

07為什麼 diff 出現你沒改的變更

你看到的原因修好的設定
重新排版後每一行都變了兩個空白 vs 四個空白,或 tab vs 空白Ignore whitespace
沒有可見差異卻每一行都變CRLF vs LF 換行Ignore whitespace,或先正規化換行
一模一樣的程式碼被標成不同編輯器補上的行尾空白Ignore whitespace
某個常數到處都被改名只差大小寫的變更只想看結構變更時用 Ignore case
一個區塊同時被刪除又新增程式碼被搬移了先看統一檢視再判斷是不是重寫

如果兩邊都不在版控裡,瀏覽器比對就是唯一選擇。清理步驟可以看 如何格式化、驗證與壓縮 JSON,它能在你開始比對前先去掉大半噪音。

08資料來源

常見問題

Diff 工具和 git diff 一樣嗎?

比對邏輯相同,輸出也符合 [git 的統一差異格式](https://www.gnu.org/software/diffutils/manual/html_node/Unified-Format.html),也就是 [git diff](https://git-scm.com/docs/git-diff) 印出來的格式。差別在於 git diff 只能處理版本庫裡的檔案,這個工具可以比對任意兩段文字,包含不在版控裡的內容。

可以比對兩個 JSON 檔嗎?

可以,但如果兩者只差在排版,請先格式化兩邊或打開忽略空白,否則每一行重新縮排都會被當成變更。

工具會偵測搬移的行嗎?

不會。搬移的區塊會被記成刪除加新增,因為行比對看的是位置。這和 git 的行為一致,也是多數審閱者預期的結果。

可以比對多大的文字?

伺服器端沒有限制,但比對在瀏覽器裡跑。幾 MB 的檔案都很順;更大的日誌建議先依時間範圍過濾。

比對結果可以還原嗎?

Swap Sides 會反轉比對方向,Clear All 會清空兩邊。比對本身不會被儲存。

我的文字會被上傳嗎?

不會,兩邊的內容與比對結果都留在你的瀏覽器裡。

返回文章列表

相關文章

相關工具