☰
代码考古指南:如何调查一行祖传代码
2026/11/2 8:03:16 网站建设 项目流程

代码考古指南:如何调查一行祖传代码

周一早上,测试同事指着屏幕说:“这个if不能删。”

我问为什么。

他说:“不知道,反正删了会炸。”

于是我们两个对着这行代码看了三分钟。它长这样:

if(user.id===0){returnnull;}

注释写着:// 历史原因,勿动。

“历史原因”四个字,堪称程序员留给后人的甲骨文。你没法反驳,也没法理解,只能选择敬畏。但敬畏不能当饭吃,线上问题还得修。于是,我决定进行一次代码考古。

第一铲:先别删,先 blame

考古的第一原则:不要先动手,先看地层。

gitblame-L118,124-- src/order.ts

git blame会告诉你每一行最后由谁、在哪次提交里改过。注意是“最后修改”,不是“最初作者”。一个人可能只是顺手调了个缩进,就替真正的“肇事者”背了锅。

如果文件改过名,记得加--follow:

gitlog--follow-p-- src/order.ts

这条命令会把重命名之前的历史也翻出来。输出可能很长,可以配合head或分页器:

gitlog--follow-p-- src/order.ts|head-n80

看到可疑提交后,先别急着下结论,把当时的上下文一起看:

gitshow a1b2c3d--statgitshow a1b2c3d -- src/order.ts

--stat看这次提交动了哪些文件,后面的路径限定只看这个文件。很多时候,答案不在代码里,而在提交信息里。比如那句经典的:

fix: 修复用户 ID 为 0 时订单丢失

好,至少知道这行代码不是凭空长出来的。

第二铲:用 pickaxe 找“这句话是什么时候出现的”

git blame只能看到“最后改这一行的人”。如果这行代码被格式化工具整体重排过,blame 会指向一次毫无意义的“style: format code”。

这时候要用git log -S,也就是常说的 pickaxe:

gitlog-S"user.id === 0"--oneline-- src/order.ts

它会找出新增或删除了这个字符串的提交。注意,是字符串出现次数的变化,不是“某行包含它”。所以重命名变量、改缩进都不影响它。

如果你不确定具体写法,可以用正则版本-G:

gitlog-G"user\.id\s*===?\s*0"--oneline-- src/order.ts

-S适合找“这句话什么时候来的”,-G适合找“这类写法什么时候变过”。两者都不是万能的,但比盯着屏幕发呆强。

第三铲:追踪一行的“生平”

有时候你想知道某一行从生到死的完整经历,可以用-L:

gitlog-L120,124:src/order.ts

这会追踪第 120 到 124 行的历史,包括它们被移动、修改、删除的过程。行号会随着历史自动调整,比手动来回切提交舒服得多。

不过要提醒一句:-L在大型仓库里可能比较慢,最好加上路径限定,别让 Git 把整个仓库翻个底朝天。

考古发现图鉴

挖了半小时,我大概总结出几类“遗迹”。它们有共同特征,也有相对固定的处理方式:

遗迹类型典型特征处理建议
TODO 化石// TODO: 临时方案,下周删查 issue;没有 issue 就补一个,别让它继续风化
注释打架注释说“不能为 0”,代码判断=== 0以测试和实际行为为准,先补测试再改
被注释掉的代码一大段/* 旧逻辑 */有 Git 就别留;版本控制比你更会保存历史
魔法数字if (retry > 3)提成常量,并在注释里写清“3 从哪来”
防御性废话if (true)、return;后面还有代码先跑测试,再删;别让“看起来没用”骗了你
幽灵配置代码里写死timeout: 5000,配置文件里也有统一来源,删掉另一份,不然迟早对不上

其中最危险的,不是 TODO,也不是魔法数字,而是注释是错的。

错的注释比没有注释更可怕。没有注释,你至少会去读代码;有了错的注释,你会带着错误的前提读代码,然后得出一个非常自信的错误结论。

最危险的一铲:bisect 找“案发时间”

如果问题不是“这行代码为什么存在”,而是“这个行为从哪个版本开始不对”,那就该请出git bisect了。

先准备一个能自动判断“好/坏”的脚本,比如./check.sh,返回 0 表示正常,非 0 表示有问题。然后:

gitbisect startgitbisect bad HEADgitbisect good v1.2.0gitbisect run ./check.sh

Git 会自动在好版本和坏版本之间二分,跑完告诉你第一个坏提交。最后别忘了:

gitbisect reset

bisect的关键不是命令,而是那个判断脚本。脚本必须稳定、快速、可重复。如果脚本本身时好时坏,你得到的不是案发时间,而是更多悬案。

考古礼仪

挖到提交之后,还有几条不成文的规矩。

第一,不要在群里直接 @ 三年前的人。
尤其是已经离职的同事。你真正想问的往往不是“你当时怎么想的”,而是“现在能不能改”。前者没人记得,后者才需要答案。

第二,删代码前先补测试。
“看起来没用”是一种非常危险的直觉。先写一个测试锁定当前行为,再改代码。测试挂了,说明你挖到了真东西;测试过了,说明你可以放心地送走这段遗迹。

第三,给未来自己留张字条。
注释应该写“为什么”,而不是“是什么”。下面这种注释,等于没写:

// 如果用户 ID 为 0 则返回 nullif(user.id===0){returnnull;}

代码已经说了“是什么”。真正有价值的是:

// 历史订单在迁移时把游客 ID 写成了 0,// 这里返回 null 是为了让上层走“无主订单”分支。// 等数据迁移脚本 v3 全量跑完即可删除。见 ISSUE-1842。if(user.id===0){returnnull;}

前者是复读机,后者是考古现场的路标。

结尾

那天下午,我们终于查清了:三年前的一次数据迁移把游客订单的用户 ID 写成了0,而当时的修复方式是在查询入口直接返回null。迁移早就跑完了,但这行代码一直留着,像一块没人敢动的碑。

我们补了测试,清了历史数据,删掉了那行if。然后我在新代码里郑重地写下:

// 临时方案,等下周接口上线后删除。

写完我笑了一下。

因为我知道,三年后,会有另一个我,拿着git blame来找我。

而那时的我,大概率也已经不记得为什么了。


原文链接:代码考古指南:如何调查一行祖传代码

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询