两段代码改了哪几行?Diff 对比与「忽略空格」的正确用法
上周帮同事 review 一个 PR,他把配置文件从 Windows 邮件给我,我拉下来一比,diff 满屏飘红,几乎每行都是「修改」。我盯着看了两秒,先切到显示不可见字符,果然整份文件每行末尾都多了一个 \r。换成 LF 再比,真正的改动只剩三行。
文本对比报差异报得太狠,十次里有八次不是真改了那么多,而是换行符、缩进、行尾空格在背后捅刀。这篇就把 diff 结果不对的几种典型情况讲清,顺手说清楚「忽略空格」到底在忽略什么,以及它救不了哪些场景。
原理一句话带过
diff 的核心是找两段文本按顺序的最长公共子序列(LCS),公共部分之外的内容就判为新增或删除。工业实现常用 Myers 算法,把这个问题转成图上找最短路径,速度比朴素 LCS 快很多。你不需要懂算法细节,只要记住一点:diff 是按顺序匹配的,它不知道你「把一段话挪到了别处」,只会觉得「这里少了一段,那里多了一段」。这个特点后面会专门讲。
「每行都报差异」几乎都是换行符
这是最高频的坑,没有之一。
Windows 下编辑器默认保存成 CRLF(\r\n),Linux/macOS 默认是 LF(\n)。同样三行字,从 Windows 复制到 Mac 再粘到对比框里,每行末尾都会多出一个 \r。行级 diff 拿整行当比较单元,于是:
旧文本(LF): line1\nline2\nline3
新文本(CRLF): line1\r\nline2\r\nline3\r
每行内容看着一模一样,diff 却说全部改过,因为 line1 和 line1\r 对它来说是两个不同的字符串。
排查办法是打开编辑器的「显示不可见字符」,看一眼是不是一边每行末尾多了个 \r。修起来一行命令:
dos2unix config.txt # CRLF → LF
unix2dos config.txt # LF → CRLF
Git 里配 .gitattributes 写 * text=auto eol=lf,仓库里始终是 LF,签出时按平台转换,团队就不会再因为换行符互相刷屏。
缩进和行尾空格淹了真改动
第二种坑:真改动只有两行,但 diff 报了二十行差异,因为这段代码顺手被格式化过,缩进从 2 空格变成了 4 空格,或者每行末尾被 IDE 自动加了空格。
这种情况下你应该勾上「忽略空格」。这个选项做的事很简单:在比较之前先把空白归一化,连续的空格、Tab、行首行尾空格都不算数,只看「非空白字符的内容」。这样 diff 只会告诉你逻辑改了什么,缩进变化不会刷屏。
类似的还有一个「忽略大小写」。文档里把 UPTools 改成 uptools,默认会被判成修改,但很多场景下这只是大小写风格统一,不算实质性改动,勾上忽略大小写就能跳过。
一个常见疑问:忽略空格会不会把真删掉一个空格这种 bug 藏起来?会。所以忽略空格适合先看逻辑改动,正式 review 时切回默认模式再扫一遍空白变化。两层视图配合着用更靠谱。
逐行 vs 逐字:颗粒度差很多
同样是「忽略空格」也救不了的场景,换种对比模式可能就好了。
- 逐行模式:把每整行当一个比较单元。一行里改了一个字,整行都会被标成「修改」。适合看结构性改动,比如「这一行是新增的」「这一行被删了」。
- 逐字模式:细化到字符级。一行里只有几个字变了,它能精确标出具体是第几个字。适合看一句话里措辞的微调。
举个实在的,旧文本是「订单状态:待付款」,新文本是「订单状态:已付款」。逐行模式会告诉你「这一行改了」,逐字模式会告诉你「待」改成了「已」。排查字段值变化、文档措辞校对,用逐字更省眼力。
逐字的代价是计算量更大,超长文本可能卡一下,正常几 K 行内感觉不出来。
整段移动是 diff 的死穴
最后说一个不是 bug 但经常让人困惑的局限。
你写文档时把第三段挪到了第一段,一字未改。对人来说这是「移动」,对 diff 来说是「第三段那里删了一大段,第一段那里新增了一大段」。行级算法只看顺序匹配,它没有「这段内容在别处出现过」的概念。
很多专业 diff 工具会加「检测移动」选项,原理是事后扫一遍被删的块和被新增的块,看内容是否高度相似,相似就标成「移动」。浏览器里的轻量工具一般不做这个,因为计算开销大、启发式判断也不稳定。
遇到「明明只是挪了位置却报成大段增删」,先别怀疑工具坏了,自己脑子里补一步「这段是不是从别处搬来的」就行。代码 review 时看到一大段「新增」代码,先全局搜一下是不是从别处复制来的,往往能发现重复代码或漏改的引用。
实操
真要排「diff 结果不对」,最快的路是先把两段文本丢进 UPTools Diff 文本对比工具 里。左右两个框分别粘旧文本和新文本,切一下逐行逐字,勾上忽略空格、忽略大小写,几个组合试一遍,基本能把换行符、缩进、大小写这三类干扰全部排除掉,剩下就是真改动。整个对比在浏览器本地跑,文本不会上传。
三句话总结
diff 报「每行都改了」几乎都是 CRLF 和 LF 不一致,先统一换行符再比。缩进和行尾空格的干扰靠「忽略空格」解决,措辞微调用逐字模式看得更细。整段文字挪位置会被报成大段增删,这是行级算法的固有局限,不是工具坏了,得靠人脑补一步。
相关文章