🛠️ 第4章 Codex 的任务地图
📄 一句话概括

三个工具各治一个病:审查靠记性、跑测试靠自觉、并行靠互相让。 它们不提高模型能力,只把「人会忘的事」变成「系统会做的事」。 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 秒实验

  1. 把你团队最常犯的那一条错误写成一条规则。 就一条,跑在提交之前。
  2. 给一个真仓库加一个「改完自动跑测试」的钩子。 看它一天会拦你几次——拦得太多,说明测试太慢或太严。
  3. 开一个 worktree,把一件想试又不敢试的改动放进去做。 做坏了直接删目录,主分支完全不受影响。

常见误区

  1. 「审查靠人看更灵活。」 灵活的意思是你今天看、明天不看。 规则不灵活,但它每次都在。
  2. 「钩子太慢,跑一次要十秒。」 它比「忘了跑,三天后才发现」快得多。 实在慢就只跑相关的测试——局部验证比全量测试更常用。
  3. 「worktree 是高级用法,用不上。」 你要同时改两处、又都想先试试,它就是那个解。 而且它只是 git 的一条命令,不是要装什么。
自测三问(点开对答案) 1. Review 里哪些该交给脚本,哪些留给人? —— **模式固定的交给脚本**:print、魔法数字、裸 except、顺序错误。 **取舍类的留给人**:空列表该返回 0 还是抛异常、这个阈值合不合理。 2. 钩子凭什么决定「拦不拦」? —— **凭退出码。** **零放行,非零拦住**——这是钩子唯一懂的语言。**自己写脚本时,结束前要把退出码说清楚。** 3. 什么时候不该用 worktree? —— **两件事改的文件完全不重叠时。** **顺序做完更快,开两个目录反而要合两次。**

下一篇 4.6 收这一章:三个反例,说明什么不该直接委派。

相关笔记