10. 只看改动也能作出第一轮裁决
上一章能作出裁决,靠的是有人把两份改动的 Diff 摆到了明面上。这一章把这件事自己做起来。
逐行读懂整个程序成本很高,也不总有必要。有一类信息成本低、价值高:这次改动到底动了哪些文件、哪些行。Diff 就是这份信息。它不重放全文,只显示相对上一个版本增删了什么。第一轮裁决,看 Diff 就够。
打开改动,而不是打开整份文件
Section titled “打开改动,而不是打开整份文件”看 Diff 用现成的工具:CodeBuddy 内置的 Git 视图,或者 GitHub Desktop,到官网装好即可。打开后它会把这次改动按文件列出来,每个文件里用加号、减号标出增删的行。
和「打开整份文件从头读」相比,Diff 的好处是把注意力收窄到「变化」上。一份两百行的实现,这次可能只动了三行,Diff 直接把这三行摆出来,其余一百九十七行不用重看。
第一轮只看四类信号
Section titled “第一轮只看四类信号”第一轮不追求读懂每一处逻辑,只扫四类最该先看的信号。这四类的共同点是:它们的影响往往落在测试照不到的地方。
| 信号 | 在看什么 | 为什么排在第一轮 |
|---|---|---|
| 测试文件有没有变 | 实现阶段测试本应只读 | 呼应第 9 章,动了测试就要先问清是修订还是放宽 |
| 依赖有没有增加 | package.json 是否多出没提过的包 | 每个新依赖都是一块新的风险面和信任面 |
| 权限与配置有没有变 | 部署、权限、环境配置文件是否被动 | 这类改动的后果在运行测试之外 |
| 有没有意料之外的新文件 | 是否冒出需求里没提到的文件 | 来路不明的文件要能解释来源 |
四类里任何一类亮了,先停下来问清楚,不因为「测试反正是绿的」就放过。第 9 章那份修改 B,正是「测试文件有没有变」这一类信号该拦下的。
一次真实的例子:自修还是引入库
Section titled “一次真实的例子:自修还是引入库”抽象的四类信号,拿一次真实改动过一遍最清楚。假设已经有一条测试覆盖着这种情况,当前是红的:报价整理器用 line.split(',') 切分字段,遇到这样一行数据就会出错:
"钢管,6分,Φ20mm",个,3.50品名本身带着逗号,为了不被切碎,导出 CSV 时给它加上了一对双引号。split(',') 不认双引号,直接在品名内部的逗号处切开,三段字段全部错位。同一个 bug,交给 AI 修,拿回两份都能让这条测试变绿的改动。
修改 A,在实现里自己写一个认引号的切分函数:
function splitCsvLine(line) { const cells = []; let current = ''; let inQuotes = false; for (const char of line) { if (char === '"') { inQuotes = !inQuotes; continue; } if (char === ',' && !inQuotes) { cells.push(current); current = ''; continue; } current += char; } cells.push(current); return cells;}...const [name, unit, price] = line.split(',').map((cell) => cell.trim());const [name, unit, price] = splitCsvLine(line).map((cell) => cell.trim());修改 B,引入一个现成的 CSV 解析库:
"dependencies": { "csv-parse": "^5.5.0"}import { parse } from 'csv-parse/sync';...const [name, unit, price] = line.split(',').map((cell) => cell.trim());const [name, unit, price] = parse(line)[0].map((cell) => cell.trim());按四类信号过一遍:测试文件都没动;权限配置都没动;意料之外的新文件都没有;唯一亮起来的是「依赖有没有增加」,修改 B 在 package.json 里多了一行,修改 A 没有。
这一类信号亮起来,不直接等于拒绝,落进的是「询问」这一堆:多一个依赖,多一块新的信任面,这个包谁维护、多久没更新一次、体积有多大,都值得在合并之前看一眼;不新增依赖,复杂度留在自己的代码里,往后这段手写的引号处理逻辑由自己维护、自己排错。两条路都能把当前的红灯变绿,第一轮 Diff 筛查给不出「哪条正确」的答案,它只负责把这道选择题从「顺手就合并了」变成「被摆在明面上决定」。报价整理器目前没有其它引入依赖的理由,主线选了修改 A。这是这次的裁决,换个项目可以有别的答案。全书从第 2 章到这里累积的完整实现,收在判定三幕的附录里。
把每处改动分成三堆
Section titled “把每处改动分成三堆”扫完四类信号,给每一处改动贴一个标签,分成三堆:
- 接受:改动落在该改的地方,范围和需求对得上。
- 询问:看不明白,或者动了四类信号里的某一类,需要提出者解释后再定。
- 拒绝:明显超出需求范围,或属于第 9 章那种把尺子改松的改动。
这三堆就是这一章的产物:一张按文件分类的接受、询问、拒绝清单。它不要求当场读懂全部逻辑,只要求把「已经放心的」和「还要追问的」分开。
Diff 筛查不是全部
Section titled “Diff 筛查不是全部”这一轮的定位要说清楚:Diff 审查是第一轮风险筛查,不是完整判断。改动范围看着合理,不等于行为正确,那要靠第 7 章那套判定器实际跑一遍来验证。涉及金额、权限、对外发布这类高风险改动,第一轮筛查之后还需要更细的代码审查。Diff 负责快速拦掉明显有问题的,不负责盖章放行。
改错了怎么退回
Section titled “改错了怎么退回”看 Diff 的过程里难免误操作,得先知道怎么退。三种退回动作后果差别很大,别混用:
- 取消本次提交里对某个文件的选择:只是把它移出这次提交,改动还在文件里。最轻,随时可逆。
- 撤销一个已经提交的提交:生成一次反向改动,把那次提交的效果抵消,历史保留。适合「已经提交了才发现不对」。
- 丢弃未提交的更改:把文件恢复到上一个版本,还没保存进历史的改动会直接消失。
留下什么产物
Section titled “留下什么产物”产物是那张按文件分类的接受、询问、拒绝清单。它记录的不是「这次改动好不好」这一句结论,而是「哪些已经放心、哪些还要追问、哪些直接挡下」。下一章把审查对象从「改动」换成「人」:把工具交给一个真实使用者,看作者一个人自测时照不到的假设会在哪里冒出来。
这一章可以对 AI 说的一句话
Section titled “这一章可以对 AI 说的一句话”这是这次改动的 Diff:______。请先按「有没有动测试文件、有没有新增依赖、有没有改配置、有没有新增文件」四类,逐类告诉我结果。先不要解释业务逻辑,我要先判断改动范围有没有超出需求。