都在用 AI 写代码了,为什么我还坚持 TDD 和 code review
发布于 2026年9月26日
用 AI 写代码越多,我越确信一件事:测试和代码审查比以前更重要了,不是更不重要。
逻辑很简单:AI 同时提高了代码的下限和产量。下限高了,单个函数很少低级错误;产量高了,错误的总数未必下降——而且错误藏得更深:AI 的代码往往"看起来全对"。
不空谈。先讲三个发生在这个个人网站构建过程中的真实案例(全部可以在 git 历史里查证),然后把我装的两套 agent skills——Superpowers 和 Matt Pocock 的 mattpocock-skills——对 TDD 和 code review 的答案放在一起对照。两套体系几乎不重叠,拼起来才是完整的图。
案例一:页面正常,meta 标签错——被"元标签级断言"抓住
给站加 SEO 时,详情页的 og:title 被布局级的 openGraph 配置覆盖成了站名。页面渲染完全正常,肉眼检查永远发现不了——错的在 meta 标签里,只有社交分享卡会露馅。
断言写的是元标签级别的严格匹配:
check(
`${lang} post detail og:title is the post title (meta-tag level)`,
postHtml.includes(`property="og:title" content="${post.title}"`),
);
红灯,修复,绿灯。如果只测"页面包含标题"——标题当然在页面里——这条 bug 就上线了。
案例二:一条断言从未执行——假绿被审查揪出
更阴险的一类:测试看起来在跑,其实从未运行。一条 og:title 抽查断言引用了不存在的变量属性,路径永远读空,断言体从未执行——它永远是绿的。
构建产物断言救不了这种"假绿",是 code review 揪出来的:审查者发现断言依赖的数据结构里根本没有那个字段。教训:测试不仅要过,还要证明自己在跑——断言的输入要从真实数据源推导,并且要看过它在红灯时变红的样子。
案例三:批量替换弄坏类名——构建立即红
我用 sed 批量迁移 Tailwind 类名,正则的词边界判断把已经迁移过的类名又叠了一层。手工检查五六个文件未必全能看出来——构建和全量断言是一秒钟的事,133 项断言(当时)瞬间把坏掉的页面全部指出来。
两套体系,两种答案
我的 agent 里装着两套 skills。面对"TDD 是什么",它们给出的答案几乎不重叠——这才有意思。
Superpowers:纪律学派
Superpowers 的 TDD skill 通篇是铁律和红灯:
- "没有先失败的测试,就没有生产代码。" 先写了代码?删掉。不是"留着当参考",不是"改改接着用"——删掉就是删掉。违反规则的字面,就是违反规则的精神。
- 必须亲眼看着它失败。 没看过测试变红,你就不知道它测的是不是对的东西。测试立即通过,恰恰是最可疑的信号。
- 合理化对照表:把"太简单不用测""回头再补""删掉几小时的代码太浪费"(沉没成本谬误)……全部预先写成红灯信号。人需要这张表,是因为人会累;agent 更需要,因为agent 不会脸红——它跳过测试时没有任何心理不适,你不说,它永远觉得"这次例外"没问题。
- 它还有一条姊妹规则,
verification-before-completion:没有新鲜的验证证据,不得宣称完成。"agent 报告成功"本身就是一条需要独立验证的断言——这条规则的注脚写着,它来自 24 次失败记忆。
Matt:设计学派
mattpocock-skills 的 TDD skill 关心的是另一组问题:
- 好测试通过公共接口验证行为,不碰实现细节。 代码可以整个换掉,测试不该跟着换。测试应该读起来像规格:"用户能用有效购物车结账"。
- 接缝先行约定,越少越好,理想是一。 测试住在接缝上——那个你能观察行为而不必伸进内部去的公共边界。接缝在哪是设计决策,要在写规格时就定下来(本站真的做到了唯一接缝:构建产物)。
- 三大反模式:实现耦合(mock 内部协作者、测私有方法——重构一发生测试就碎);同义反复(预期值用代码自己的方式算出来,构造上永真,永远不可能和代码不一致——预期值必须来自独立事实源);水平切片(先写完所有测试再实现——批量测试验证的是想象中的行为,你测的是东西的形状而不是用户面对的行为)。
- 重构不属于红→绿循环,属于审查。 循环里只做"让这条断言变绿的最小实现"。
对照的实质
一句话:Superpowers 回答"何时、是否"——永远、无例外、要证据;Matt 回答"哪里、什么"——哪条接缝、什么是好测试。
只有纪律没有设计,你会在错误的接缝上堆出一墙脆弱的测试——每条都先红后绿,每条都在逼你重构;只有设计没有纪律,测试会退化成事后补写——立即通过,绿色证明不了任何东西。案例二那条"从未执行的断言",恰好是两种病的交汇:它没写在约定的接缝数据源上(设计缺席),也从未有人看着它红过(纪律缺席)。
Code review 的两种拼图
审查这回事,两套体系贡献的碎片也不同。
Superpowers 管的是审查这场对话的纪律。 requesting-code-review 要求审查者是一个被精确投喂的隔离子代理:只拿描述、需求和 diff 的 SHA 区间,永不继承实现者的会话历史——历史会污染判断。产出按 Critical / Important / Minor 分级,Critical 立即修,Important 修完才能走。更精彩的是 receiving-code-review:收到反馈先验证再实现——查代码库现实、查会不会破坏现有功能、做 YAGNI 检查(grep 实际调用,没人调用的"专业化实现"应该删掉而不是实现);禁止表演式同意,"You're absolutely right!" 被明文列为违规回复;有权技术性推回,推错了就事实陈述地纠正自己,不道歉不辩护。
Matt 管的是审查内容的结构。 code-review 把审查拆成两轴——Standards(是否符合仓库自己的规矩:词汇表、ADR、Fowler 坏味道基线)和 Spec(是否忠实实现了工单)——两个并行子代理各查一轴,永不合并重排。因为一个改动可以通过一轴而挂在另一轴:规范内实现错东西,和忠实实现但破坏约定,是两种不同的病,合并报告会让好轴掩盖坏轴。
拼起来正好:怎么审(隔离、分级、先验证再接受)+ 查什么(双轴、基线、谁覆盖谁)。本站工单 02 的审查就是这套拼图的原型:当场产出四项修复(frontmatter 构建期校验、日期本地化、断言动态推导、MDX 渲染检查),并把一条顺手加的导航链接判为"可辩护的范围蔓延"而保留——门禁之上还有判断力。
用这套词汇重看三个案例
- 案例一 = 接缝选择问题。 og:title 住在 meta 标签里,肉眼和"页面级"断言都够不着;断言必须写在最高接缝(构建产物 HTML)上、做到元标签级严格匹配。Matt 的"最高接缝"原则决定了它住在哪,Superpowers 的"看着它红"证明了它测的是对的东西。
- 案例二 = 假绿,两套体系各有解法。 Superpowers 从流程上杀死它:从未红过的测试不可信;Matt 用反模式命名它:预期值来自代码自己的路径而不是独立事实源。而实际抓到它的是审查——Spec 轴发现断言引用了不存在的字段。测试不仅要过,还要证明自己在跑。
- 案例三 = 回归套件的经济学。 唯一接缝 + 全量断言 = 一秒钟算出批量替换的爆炸半径。这也解释了为什么重构要独立于红→绿循环、且每次重构后必须全量绿:重构改变的是实现,全绿证明行为没动过。
结论
三个案例的共同点:错误都不是"代码跑不起来"级别的,而是看起来能跑级别的。这正是 AI 时代错误的新形态。而两套 skills 体系让我把这个直觉整理成了三句话:
- 测试是对 agent 的可执行规格。 你无法靠自然语言让 agent"理解你的意图",但一条先红后绿的断言是意图的无歧义形式化。TDD 从"质量文化"变成了人和 agent 之间的接口协议。
- code review 是对 agent 的对齐机制。 双轴防止两种失败:规范内实现错东西、忠实实现但破坏约定。agent 的产量越大,对齐检查的单位价值越高。
- 纪律必须外置。 agent 不疲劳、不脸红、不焦虑,人类靠心理不适运转的自制力对它完全无效——所以 Superpowers 把纪律写成机械规则(删掉、看着它红、没有证据不得宣称完成),让每一次违反都显眼到藏不住。
AI 把写代码的成本压到接近零,于是验证成了新的瓶颈。瓶颈在哪,纪律就该在哪:TDD 不再是"质量文化",是产能约束下的经济学——而 code review,是这场经济学里的双轴审计。
所以我的 AI 原生工作流里,每个 diff 必过两轴审查,每张工单必须先红后绿。不是仪式感——是被虫咬出来的。