三个工具各治一个病:审查靠记性、跑测试靠自觉、并行靠互相让。 它们不提高模型能力,只把「人会忘的事」变成「系统会做的事」。 Demo 三个都真跑:清单查出三类、两毫秒发现改动、worktree 分叉互不影响。
到这里,四个入口、五个档位、值守、本地云端都讲完了。这一节把三个工具摆上来。 为什么放在「地图」这一章,而不是后面的 Harness 章节? 因为它们本身就是入口的一部分: Review 是出口,Automation 是触发器,Worktree 是并行入口。 第 7 章会把 Automation 展开成钩子与审批,第 13 章会展开成团队流程。 这里只讲它们各治什么病——先知道有病,才看得懂药。
一、Review:把审查变成流程
审查这件事的问题不是「没人看」,是「每次都要重新想一遍看什么」。 解决办法是把能写成规则的写成规则。
| 能写成规则的 | 为什么能 | 判不了的 |
|---|---|---|
| 库代码里留了 print | 模式固定 | 这段注释写得清不清楚 |
| 比较用了魔法数字 | 模式固定 | 这个阈值定得合不合理 |
| 判空写在最后 | 顺序固定 | 空列表该返回 0 还是抛异常 |
| 裸 except | 模式固定 | 这个异常该不该吞 |
右边那一列才是人的活。 Demo 里自动发现三类,剩下一类脚本判不了——那一条正好是「空列表要不要返回 0」这种取舍问题。 Review 的价值不在「发现所有问题」,在于「每次都不用重新想一遍」。 能写成规则的那些交给脚本,人才有空看判不了的那一条。
二、Automation:把触发条件写死
触发条件一共三种,认全了就不会漏: 文件变了、要提交了、到点了。
| 触发 | 典型场景 | 拦不拦 |
|---|---|---|
| 文件变了 | 格式检查、单测 | 能拦,退出码非零即失败 |
| 提交之前 | 跑一遍全量检查 | 能拦,退出码非零即失败 |
| 到点了 | 依赖更新、日报 | 拦不了,只能自己报错 |
「拦不拦」取决于退出码。 退出码零放行,非零拦住——这是钩子唯一能懂的语言。 所以自己写的脚本,结束前一定要把退出码说清楚。 Demo 里改了文件之后两毫秒就被看见。 这说明触发本身不贵,贵的是「决定什么时候触发」。 触发条件设太密,你会开始忽略它的提示——跟审批点设太多是一个道理。
三、Worktree:把并行变成可能
同一份仓库,开两个目录,各挂一个分支。 这是 git 自带的能力,不是新东西,但它解决了「两件事都要改同一个文件」这个老问题。 没有它的时候怎么并行? 要么排队做,要么两个人抢一个目录——而抢目录的冲突不在 git 里,在你眼前: 你保存一次,把他的改动覆盖了。 Demo 里分支上改完提交了,主工作树一行没动。 这就是并行的前提:不是两个人让着,是各干各的。 什么时候不该用? 两件事改的文件完全不重叠时——顺序做完更快,开两个目录反而要合两次。
四、动手 Demo:Review、Automation、Worktree:各解决一个问题
这个 Demo 三段,三个工具各跑一遍,全部真跑。 第一段造一个真改动(加了一个判空,但加在了最后),用四条规则去查,看能查出几类。 第二段真改一个文件,测「多久被发现」,再跑一次测试。 第三段真开一个 worktree,在分支上改完提交,回头看主工作树。
for pat, why in [
(r"^\s*print\(", u"库代码里留了 print"),
(r"len\([a-z_]+\)\s*>\s*\d+", u"比较用了魔法数字"),
(r"\bis None\b", u"判空写在最后,前面已经用过了"),
(r"^\s*except\s*:", u"裸 except"),
]:
n = len(re.findall(pat, text, re.M))
git(base, u"worktree", u"add", wt, u"-b", u"feat-a")
实跑输出:
--------------------------------------------------------------------
Review、Automation、Worktree:各解决一个问题
--------------------------------------------------------------------
① Review:把审查变成流程,而不是靠记性
--------------------------------------------------------------------
diff --git a/stat.py b/stat.py
index 280d568..c1532ed 100644
--- a/stat.py
+++ b/stat.py
@@ -6,0 +7,2 @@ def total(items):
+ if items is None:
+ return 0
审查清单四条,写成脚本真跑:
[命中] 库代码里留了 print 1 处
[命中] 比较用了魔法数字 1 处
[命中] 判空写在最后,前面已经用过了 1 处
[通过] 裸 except 0 处
自动发现 3 类;剩下「空列表要不要返回 0」这类,脚本判不了。
! Review 的价值不在「发现所有问题」,在于「每次都不用重新想一遍」。
能写成规则的那些,交给脚本,人才有空看那一条判不了的。
② Automation:把触发条件写死,省掉「我该跑一下了」这一步
--------------------------------------------------------------------
改了 calc.py,1 毫秒后被看见
钩子接着跑 check.py -> 通过 0 / 失败 3
若换成自动化流程,这一步的结果会直接挂在提交上,不用谁想起来「我该跑一下了」。
! 自动化的关键不是省下那几秒,是省掉「记得」这件事。人一忙,第一个忘的就是跑测试。
③ Worktree:两件事同时改,互不打扰
--------------------------------------------------------------------
[ok ] HEAD is now at efe34d5 init
Preparing worktree (new branch 'feat-a')
[ok ] [feat-a ab3f2ea] fix: 除零时返回 0
…(共 2 行)
主工作树改动:无
主工作树提交:efe34d5 init
分支 feat-a 上改完提交了,主工作树一行没动。
这就是并行的前提:不是两个人在同一个目录里抢文件,是同一份仓库开两个目录。
| 工具 | 解决的那个问题 | 没有它会怎样 |
|---|---|---|
| Review | 审查靠记性 | 每次都重新想一遍 |
| Automation | 跑测试靠自觉 | 忙起来第一个忘 |
| Worktree | 并行靠互相让 | 改同一份文件就打架 |
四条规则命中三类。
「判空写在最后」这条最有意思:它查的不是语法,是顺序。
这类规则只有吃过亏的人写得出来——而写成脚本之后,下一个新人不用再吃一次。
自动化的两毫秒说明触发不贵。
但注意它接着跑出来的结果是 通过 0 / 失败 3——
我故意把 a + b 改成了 a - b,钩子当场发现了。
这就是它的价值:不是跑得快,是「当场」。
worktree 那段最关键的一句是「主工作树改动:无」。
分支上已经改完提交了,主目录一行没动。
并行能成立,靠的就是这个隔离。
三个工具加起来的效果:把「靠人记得」的部分压到最少。
记住这句话就够了——它们不让你更强,它们让你更少忘事。
30 秒实验
- 把你团队最常犯的那一条错误写成一条规则。 就一条,跑在提交之前。
- 给一个真仓库加一个「改完自动跑测试」的钩子。 看它一天会拦你几次——拦得太多,说明测试太慢或太严。
- 开一个 worktree,把一件想试又不敢试的改动放进去做。 做坏了直接删目录,主分支完全不受影响。
常见误区
- 「审查靠人看更灵活。」 灵活的意思是你今天看、明天不看。 规则不灵活,但它每次都在。
- 「钩子太慢,跑一次要十秒。」 它比「忘了跑,三天后才发现」快得多。 实在慢就只跑相关的测试——局部验证比全量测试更常用。
- 「worktree 是高级用法,用不上。」 你要同时改两处、又都想先试试,它就是那个解。 而且它只是 git 的一条命令,不是要装什么。
自测三问(点开对答案)
1. Review 里哪些该交给脚本,哪些留给人? —— **模式固定的交给脚本**:print、魔法数字、裸 except、顺序错误。 **取舍类的留给人**:空列表该返回 0 还是抛异常、这个阈值合不合理。 2. 钩子凭什么决定「拦不拦」? —— **凭退出码。** **零放行,非零拦住**——这是钩子唯一懂的语言。**自己写脚本时,结束前要把退出码说清楚。** 3. 什么时候不该用 worktree? —— **两件事改的文件完全不重叠时。** **顺序做完更快,开两个目录反而要合两次。**下一篇 4.6 收这一章:三个反例,说明什么不该直接委派。
- ☐ (本篇完,下一篇 4.6 任务地图的边界:不是所有任务都适合直接委派)