补全给的是字符,交付给的是变更——差别不在多少,在有没有证据。 一份能交付的改动要有四样东西:改了什么、验过没有、谁改的为什么改、退得回去吗。 四样缺一样,它就只能算「一段能跑的代码」,不能算「一次交付」。
上一篇说三次演进换的是颗粒度。这一篇往下问一层:颗粒度放大了,凭什么说它「交付」了? 「交付」这个词在软件工程里有很具体的含义—— 不是「写完了」,是「可以被别人接过去用、出了问题能查、查出来能退」。 这三条对应到工具上,就是四样证据。 这一篇的 Demo 全部用真 git 跑:造一个真有 bug 的小项目,修掉它, 然后把四样证据一样一样拿出来看。沙箱里有 git 2.55,这一段是真的,不是模拟。
一、补全给的是字符,交付给的是变更
这两件事的差别,用一个例子就说清了。补全给你一段 if not nums: return 0,
这段字符本身没有对错——它放进 avg() 里是对的,放进 sum() 里是错的。
而「变更」是一个有坐标的东西:哪个文件、第几行、原来是什么、现在是什么。
它带着上下文,所以它能被审查、能被合并、能被回滚。
字符不行——字符离开上下文就没有意义。
这个区别落到工具上很具体:
补全的输出是「一段文本」,你要自己决定往哪儿放;
委派的输出是「一个 diff」,位置已经定了,你只需要判断对不对。
前者要人做定位,后者要人做判断。
定位是体力活,判断才是脑力活。
二、一个任务的完整证据链
| 证据 | 回答什么问题 | 没有它会怎样 |
|---|---|---|
| 变更(diff) | 改了哪儿、改了多少 | 不知道该审哪一段 |
| 验证(退出码) | 跑过没有、过没过 | 「我觉得能跑」 |
| 留痕(提交) | 谁改的、为什么改 | 三个月后没人说得清 |
| 回滚(revert) | 能不能原样退回去 | 出事只能手改回去 |
这四样是有顺序的,而且顺序不能乱。 先有变更,才谈得上验证(验的是这次改动);先验证通过,才值得留痕(不然留的是一条坏记录); 留痕之后才谈回滚(回滚是回滚某一次提交)。 四样里最容易被跳过的是第四样。 因为「改对了」的时候,没人想回滚。 但交付的意义恰恰在出错的时候——出事那一刻,你只有一个 revert 的时间窗。 这也是为什么「它说它跑过了」不算证据。 证据得是「一条能被我重跑的命令,加上它的退出码」—— 我能自己验一遍的,才叫证据;只能听你说的,那叫转述。
三、交付工具的三个硬指标
指标一:能不能一次看全。 改一个名字牵扯七个文件,工具必须能一次看到七个文件—— 分七次看,第七次的时候人已经忘了第一次改的是什么。 指标二:能不能自己验。 跑测试、看退出码、读报错,这一整套必须它自己做。 要人代跑,那它就只是个打字机——因为「验」才是这件事的重心。 指标三:能不能留下可复查的记录。 提交信息、diff、测试输出,三样都要能落到仓库里。 落在聊天窗口里不算——窗口会关,仓库不会。 三个指标背后其实是同一句话: 它交出来的东西,要能被另一个人接过去。 接的人不问你、只看仓库,就能判断这次改动该不该合——做到这一步,才叫交付。
四、动手 Demo:一份任务交付物,要能拿出四样证据
这个 Demo 用一个真 bug 走一遍完整流程。项目很小:一个 avg() 函数加三条测试。
bug 是真的——空列表会除零,不是编出来的。
流程走五步:造项目、看变更、看验证、看留痕、看回滚。每一步都真跑 git,输出是真的。
重点看第五步:revert 之后,拿文件的 MD5 跟改动前比。
一致,才说明「退得回去」是真的,不是嘴上说说。
# 1. 造项目(calc.py 有 bug:空表除零)
def avg(nums):
return sum(nums) / len(nums)
# 2. 变更:加两行判断
def avg(nums):
if not nums:
return 0
return sum(nums) / len(nums)
# 3. 验证:跑测试看退出码
python test_calc.py # 退出码 0 才算过
# 4. 留痕:提交信息写两段(改了什么 / 为什么)
git commit -m "fix: avg() 处理空表时除零" \
-m "空表返回 0 而不是抛异常,与前端口径一致"
# 5. 回滚:退回去,比 MD5
git revert --no-edit HEAD
实跑输出:
--------------------------------------------------------------------
一份任务交付物,要能拿出四样证据
--------------------------------------------------------------------
① 造一个有 bug 的小项目
--------------------------------------------------------------------
跑测试:
失败 空表 抛了 ZeroDivisionError
通过 2 / 失败 1
! 空表会除零——这是真 bug,不是造出来的。
② 证据一:变更(改了哪儿、改了多少)
--------------------------------------------------------------------
[ok ] calc.py | 2 ++
1 file changed, 2 insertions(+)
改动:1 个文件、加 2 行、删 0 行。
! 这三个数字是审查的第一道闸——改一个除零判断却动了 300 行,那一定有问题。
③ 证据二:验证(跑过没有、跑出什么)
--------------------------------------------------------------------
跑测试:退出码 0,输出「通过 3 / 失败 0」
退出码比输出重要。0 是通过,非 0 是没过——钩子和 CI 只认这个数。
④ 证据三:留痕(谁改的、为什么改)
--------------------------------------------------------------------
[ok ] [main 1340cd8] fix: avg() 处理空表时除零
1 file changed, 2 insertions(+)
[ok ] 1340cd8 fix: avg() 处理空表时除零
⑤ 证据四:可回滚(能不能原样退回去)
--------------------------------------------------------------------
动手之前的 calc.py 指纹 f6f27c98
revert 之后的指纹 f6f27c98
一致——这一改动能干净地退回去,一个字节都没差。
退回去之后测试:通过 2 / 失败 1
漏洞回来了,说明 revert 真的生效了,不是「看起来退回去了」。
! 四样证据凑齐,才叫「交付」;只有第一样,那叫「改了几行代码」。
第一步的输出值得看一眼:失败 空表 抛了 ZeroDivisionError,通过 2 / 失败 1。
这就是「验证」的原始形态——一条能重跑的命令,加一个能判定的退出码。
第二步的 numstat 给出三个数:1 个文件、加 2 行、删 0 行。
这三个数是审查的第一道闸。
改一个除零判断却动了 300 行,那一定有别的动作混在里面——这一眼就能看出来,不用读代码。
第五步最关键:revert 前后的 MD5 一模一样。
这说明「能退回去」是物理上成立,不是流程上成立。
而且退回去之后测试又失败了——漏洞回来了,才证明 revert 真的生效了,不是「看起来退回去了」。
四样凑齐,这次改动才能交给别人。
只有第一样(改了代码),那叫「我改了几行」——
别人接过去还得自己验、自己猜为什么改、出事自己想办法退。
那不叫交付,那叫甩锅。
30 秒实验
- 翻你最近一次提交,检查四样齐不齐。
大多数人的第一反应是「提交信息写得太随意」——
那一栏恰恰是最便宜的,写两句话就行,但九成人只写
update。 - 找一次「改完之后发现不对」的经历,问自己:当时是怎么退回去的? 如果是手工改回去的,那一次就没有「回滚」这樣证据——下次同样的错,还得再手工一次。
- 给你的项目加一条规矩:提交信息必须能回答「为什么改」。 只写「改了什么」是不够的——diff 已经写了改什么,提交信息要写 diff 里没有的那部分。
常见误区
- 「能跑就算交付了。」 能跑只是第二条证据。 没有变更范围、没有留痕、不能回滚,出了问题就只能靠人回忆。 交付的门槛从来不是「能跑」,是「别人能接」。
- 「提交信息随便写写,反正有 diff。」 diff 能告诉你改了什么,告诉不了你为什么改。 三个月后你自己看 diff,也想不起来当时在解决什么问题。 「为什么」只存在于当下的脑子里,不写下来就没了。
- 「回滚是运维的事,跟我写代码的没关系。」 回滚能力是写代码时决定的—— 一次改动如果横跨三个模块、还改了数据库结构,那它就很难干净地退回去。 能不能回滚,在你按下保存的那一刻就定了。
自测三问(点开对答案)
1. 「交付」的四样证据是哪四样?为什么顺序不能乱? —— **变更、验证、留痕、回滚。** 先有变更才谈验证(验的是这次改动), 验过了才值得留痕(不然留的是坏记录),留痕之后才谈回滚(回滚某一次提交)。 2. 为什么「它说它跑过了」不算证据? —— **因为证据要能被你重跑一遍。** 一条命令加一个退出码,你可以自己验; 一句「跑过了」,你只能选择信或者不信。**能复核的才叫证据。** 3. 「能不能回滚」是什么时候决定的? —— **写代码的时候。** 一次改动横跨几个模块、有没有动数据库结构、 有没有改配置——**这些在提交那一刻就决定了它退不退得干净**,不是出事之后运维能补救的。下一篇 1.3 讲人的这一侧: 工具从「给代码」变成「交付任务」,你的活儿从「写」变成「审」——这个转变具体要改哪几个习惯。
- ☐ (本篇完,下一篇 1.3 从「会写代码」到「能交付任务」:心智模型的转变)