信任不在 Code Review 里
发布于 2026 年 6 月 30 日
你变成了那道门
Agent 写代码比任何人读得都快。这不是预测,这已经是基线。一个 Agent,一个下午,写出比一个团队一周仔细阅读还多的代码。Code review 默认的假设是写和读的速度一样。第一次,它们不再一样。
于是你只有两个糟糕的选择。守住防线、逐行阅读,你变成整个团队排队等的瓶颈。快速放行,review 变成一枚印章。大多数团队悄悄选了印章。最糟的不是漏掉的 bug;漏掉的 bug 还能修复。虚假信心不能修复,而它是按计划量产的。印章仍然产出信任感,你靠着那种感觉发布。你不是没能建立信任。你在建一个假的。
但退一步看,因为把关从来不是重点。你需要的不是阅读。你需要的是对项目的控制和对其扎实的信任——那种你清楚自己在发布什么的持续信心。读 diff 从来只是代理,被误当成了事物本身。不能外包的是控制和信任。这些仍然是你的。 阅读可以交给任何最合适的人或物。
你写不了,所以你也读不了
先说为什么你的阅读曾经有价值,那并不是因为你仔细。一次 review 的质量取决于做它的开发者,而让一个开发者好的只有一件事:他的手足够频繁地在代码里,能活生生地持有细节。那种活的理解是 review 的引擎;阅读只是它显现的地方。拿走它,阅读就是空洞的动作,眼睛在行上移动。
Agent 时代把你从那个开发者的位置上拉出来,因为一个原因、两个面向:你和 Agent 为代码付出的代价不同。成本模型不同,一切都由此而来。
在实时中,这个差距表现为速度。Agent 以接近零边际成本迭代;一个在其中手写的人变成工作等待的慢车道。诚实的做法是停下来让 Agent 跑,而你停下的那一刻,你的活理解就开始消退,因为你不再构建它了。
在代码库层面,同样的差距表现为一致性,这一点即使对一个快得能跟上的人类也成立。一个代码库是某一种成本模型的上千次小判断的结晶:一次重复是否值得重构、多少日志值得噪音、一个边界实际落在哪里。Agent 均匀地持守那种严谨,没有让一个疲惫的人砍掉日志或跳过重构的疲劳和厌倦。一个伸手进去的人类不是写更差的代码,只是写为不同成本模型优化的代码,把它丢进一个为另一种模型调校过的系统,会撕裂让这个系统可理解的一致性。高层仍然是你的:系统是做什么的、它能去哪、哪些风险值得预见、哪些取舍值得做。下面的实现颗粒会落到为代码付出代价的任何人或物那里。
说白了,在一个我们正在构建而非已经无处不在的特定阶段里:一旦一个项目已经 agent-native——Agent 在该领域至少和你一样强、代码库已经是它们的颗粒——人类就再也不能真正写代码了,无论从速度还是颗粒来看。而一个不能写代码的人类,也不能活生生地持有代码来 review 它。这不是自然规律,但在那个阶段内结论很坚定:继续 review,你就是把眼睛移过你不再有资格判断的代码行。这也是为什么当 Agent 没有加快你的项目时,瓶颈几乎总是那个还在手写手读的人。不是信任,不是方向感;那些还在。瓶颈是那只还在键盘上的手。
这一切都不是新的;Agent 时代只是让它无法再被忽视。信任从来不在 code review 里,现在不在,以前也不在。掌控大型项目的人不读代码:你认识的最资深的工程师上一次逐行读完他们负责的系统是什么时候?他们依靠对下面的人的信任和对大致架构的把握来掌舵,没人说那是不负责任。逐行阅读是更低的层级负担得起的东西,而负担得起被误当成了信任。Agent 时代只是把每一个层级推到了顶层一直在的那个位置。
那么信任来自哪里?来自一个你与其共事足够久、可以预测的作者,一个在负载下站得住的测试套件,一个看过系统跑了一百次的伙伴,一个干净回来的 beta,三个发布里安静的面板。Code review 从来是那个组合里较弱的渠道之一。它一直在暗示、却从未真正兑现的是对一个问题的回答:系统作为整体还在保持在一起吗?当体量上升时,这个问题不会消失。它变得更尖锐,而它需要的工具从来不是 diff。
更快的代码,更快的腐烂
当一个系统在变更中老化时,能冒充你的程序的数量不断增长。在一个年轻、紧凑的系统里,幸存者几乎是同一个程序。让它不加照料地老化,家族扩散:同样的 spec、同样的绿色检查、一个相互矛盾的实现的巨大空间,全都通过。那种扩散是一种熵,叫作熵正确的词是方向:放任不管,它只增不减。每一个清晰的边界和每一个诚实的测试削减它;每一个捷径和每一个重复的事实来源让它长回来。
这同样不是新的。Lehman 在 1970 年代就写下来了:一个在用的程序,发布又发布,不断增长复杂性,除非有人花真正的工作去抑制它。软件腐烂、代码衰变;这个领域自打交付软件以来就有词来形容它。新的不是现象。是温度。 一个可用的产品可以一个小时内立起来,一天后变得不可维护,两个方向同等速度。Agent 没有发明熵;它们只是调高了温度,移除了过去压住速度的东西:一个人手写和手读代码能有多快。
你感受到上升的熵是一种失去:你无法再预测你自己的系统。你不能说,在你改这里之前,那里会发生什么。对一个系统的信任从来只是这个:预测它的能力。 不是一种感觉,不是一个绿色勾:是赢得的信心——你知道这个东西会怎么行为。一个 diff 不能还你那种信心,因为 diff 展示的是一个变更,而预测是整体的属性。所以问题不是某个 diff 对不对。问题是整体的熵在上升还是在被抑制。那是可读的,而读它完全不需要 diff。
停止读 diff
那你怎样在读熵时不打开每一个 diff?一个 diff 给你看一个变更;它永远不能给你看一个系统。 你停止读 diff,开始读系统。测试套件和面板是信任的来源;下面的信号是你现在读它的方式,活的,在系统运行时读。
Review 读的是一个产物:你面前的那个变更,逐行。但你想知道的不属于任何一个变更。它是整个系统的一个属性,而整个系统已经在告诉你关于它自己,一直在,不需要任何人打开文件。它发射信号;你改为读那些信号。这就是它为什么在 review 不行的地方可以:review 指向产物,而你需要知道的从来不在产物里。
Bug 列表是最直接的,因为它不是预测:它是系统已经出错的记录。Bug 聚集,聚集就是地图。同一个模块一周又一周浮现,同一个合约每个月长出一个新的边界情况,因为那里是系统泄漏的地方。Agent 失败尤其可读,因为一个 Agent 的错误通常回溯到一个不干净的输入:一个模糊的合约、一个留下未说情况的 spec。人类出于习惯补偿、掩盖缝隙;Agent 走进缝隙并标出位置。
其他信号报告它还可能在哪里出问题。Mutation testing 翻转一个条件或删除工作中的代码里的一个分支,问是否有任何测试变红;一个存活的 mutant 是你的套件仍然祝福的错误版程序,是「测试通过」和「行为被锁定」之间的那个精确的差距。静态结构显示断裂开始的地方:一个分支密集的函数比它的 spec 需要更多的状态,一个存在两处的事实邀请有人更新一处而忘记另一处的那一天。而当 Agent 彼此并肩工作时,一个信号是实时的:它们的变更是否碰撞。重要的碰撞不是两个 Agent 触碰相邻的代码——那只是普通的并发;是同一个缝合线一次又一次被争夺,这意味着那里的边界错了。
地图也告诉你该瞄准哪里。Bug 列表密集且分支最深的地方,未知 bug 也最密集,所以那里是你指向测试框架的地方:不是为了填充覆盖率数字,而是构建那些能把藏在纠缠区域里的失效冲洗出来的 case。一个发现的 bug 只在针对它的测试从红变绿后才算数,这样修复被锁住,不能悄悄回来。
但一个绿色的测试不是免费的质量,这是大多数套件对你撒谎的地方。一个测试赢得的绿色只有当它的检查是硬的时候才算数:它必须断言真实行为、针对真实负载,这样真正的回归会让它变红;它必须在行为运行的各路径上完整,因为在一个路径上通过、另一个路径却未执行的检查,是一件穿着通过测试外衣的未测 case。一个检查代码形状而非其效果的测试,是一盏接在空中的绿灯。同样的怀疑落在每一个信号上。一个安静的 bug 列表可能意味着模块健康,也可能意味着没人使用它,或者它的仪表盘暗了。一个缺失的信号只在你能确定它本该触发时才有意义。所以你用你刚才差点对 diff 放下的标准去持守每一个工具:只有当你证明了它会失败之后才信任它。质量存于你的检查有多硬,永远不在任何人读了代码行。
两条坦率在你依赖这些之前。第一,这些读数大多是滞后的:bug 列表是它已经坏过的地方,存活的 mutant 是你已有测试中的缝隙。它们在腐烂落地一拍之后才抓住它,这比从不抓住的印章强,但这不是预防;预防向上游移动,进入输入。而且没有单一读数是全貌,所以你同时读几个,让它们三角定位。第二,不是所有高熵都是衰变。一个插件系统有一百个扩展满足一个接口,按计数是极高的熵,而那正是设计在按意图运转。信号告诉你熵在哪里;它们不告诉你是什么类型。那个判断不会免费来。
Review 是事件;信任是状态
这一切必须离开 diff 的原因比速度更深:review 的形状不对。Review 是一个事件。信任是一个状态。 事件发生一次,留下裁决,消失;没有什么在你要信任的系统里积累。状态才是你真正想要的——那种你能预测自己系统的持续信心——而你不能用不留东西的事件建出一个状态。这就是标题指向的那句话:信任不在 code review 里,因为事件住不下状态。跑一千次事件你仍然没有那个东西。信号是连续的,所以信任从它们积累;印章从不能。
所以 review,作为你的信任工具,离开了。读代码没有消失;它移到了有理解的地方,在 Agent 时代那是写代码的 Agent,不是你。它们是手泡在细节里的那一个,一轮又一轮。离开的是你,作为那道门的你。而人们首先想到的高风险 case——安全边界、不可逆的发布——恰恰是疲惫的人类扫描永远最差的那种。它们现在依靠更强的东西:类型系统使不可能违反的属性,断言够硬的检查,被能抓住阅读会错过的回归的监控注视的分阶段发布。
在这里,读信号不止于观察。每一个信号回溯到一个原因。存活的 mutant 指向没有人锁定的行为。漂移的边界指向不再意味着同一件事的合约。Bug 列表里的聚集指向更深:这段代码是由一个有能力的模型写的,所以当一个模块持续生成 bug,最可能的原因不是模型在那里弱。而是模块的接口误导了任何按它工作的东西。Bug 是系统在告诉你边界错了。所以你按你刚建的测试框架重构它,测试框架保持行为稳定,所以每一个变更要么可证明地保持行为,要么是一个真 bug 被冲洗到明处。
这个动作几乎从不「下一次 review 更用力」。它是把失败当作它真正是的东西:输入给循环的诊断信息。一个失败不是对模型的裁决;它是你留下了不干净输入的地图。 它精确地告诉你,你给它的哪个条件还不干净,一旦你这么看,Agent 的错误就变成了它产出的最有价值的东西:每一个都是一个你本来看不到的缝隙的标记指针。
按角色最容易感受。设计架构的 Agent 失败了,原因很少是它的推理;而是使命——你正在建设的目标——在输入时从未被足够清楚地表达。写实现的 Agent 失败了,原因很少是智能;而是环境——测试面、验证 harness——没建好,或者你画的边界误导了它。按你的意图测试的 Agent 错过了重要的东西,因为意图从未被足够清楚地表达来被测试。失败从不终止在 Agent。它向上走链条,落在输入上:某件你说得含糊的事,一个你没有建的界面,一个你留得软的边界。
这就是循环闭合。你读熵在哪里上升,追溯失败到让它上升的输入,收紧那个输入。有时候这意味着更锐利的 spec;有时候是重构误导模型的合约;最狠的时候是改变设计让失败完全不可能发生:一个使坏状态不可表示的类型,一个由构造强制而非事后检查的不变量。最后那种动作只能触及你能编码为非法状态的失败。外部和时序的那些——一个从不响应的凭证、两个服务之间的竞态——保持不可约,退回到之前的硬检查和分阶段发布。系统变得更可预测,不是因为任何人 review 了更多代码,而是因为每一个失败都被用来让条件更真。
那就是这句话里的实践:停止守门,开始读系统并收紧它的输入,一轮又一轮。一旦 Agent 快到没有人能阅读,这是唯一我们发现能把质量和速度握在同一只手里的东西。
这里只留一个问题,这篇文章一直在走向它的那个问题。如果你不再是那道门,甚至不再是读代码的那个人,如果你的日常是读系统和收紧喂给它的东西——那么,人类的工作,确切地说,现在是什么?那是下一篇文章。



