今年 4 月,有人在 X 上问 Robert C. Martin,也就是写《Clean Code》那位 Uncle Bob,怎么审 agent 生成的代码。他说他不审。

到 7 月他说得更彻底:「我现在的策略是,agent 写的代码我一行都不读。这是我能吃到它们生产力的唯一办法。」那条推被转了两千多次。

Grady Booch 隔天顶了回去:「跟 Bob 不一样,agent 生成的代码我全审。」

同一个问题上还有第三个答案。6 月 arXiv 上有一篇论文主张,人工审查该整体交给 agent 去做。

三个答案摆在一起,分歧其实不在「审不审」,在人眼撤出来之后谁站岗。

AI生成代码的时代,这是个不能回避的问题

写代码的速度涨了。审代码的速度没涨。

一个 agent 跑一晚上产出的 diff,够一个资深工程师看一整天。团队里五个人各自挂着两三个 agent,「所有代码都必须经过人工逐行审查」这条规矩不会被正式废除,它会先变成排队,然后变成盖章,最后变成一句留在纸面上没人执行的话。Uncle Bob 那句「人读代码太慢」讲的就是这个约束。目前看来业界对这个问题有三个不同答案:

Uncle Bob 的答案是指标和测试站岗

他不读代码,但他没说不验证。他量的是覆盖率、依赖结构、圈复杂度、模块大小,配上单元测试、gherkin 测试、QA 流程、变异测试,他管这叫「用极端的约束把 agent 围起来」。

这套围栏里最该讲清楚的是变异测试,因为它是唯一回答「我的测试到底有没有用」的那一项。覆盖率只说这行代码被执行过,不说它写错了会不会有人发现,一个不写断言的测试照样能跑到 100% 覆盖率。变异测试的做法是故意把代码改坏:把 > 改成 >=,把返回值改成 null,然后重跑测试。测试变红,说明这处逻辑真的被守着;测试全绿,说明这行代码只是被跑过,没有被测过。

Booch 的答案是人不能撤

他的反驳只有一句,打在要害上:覆盖率之类的指标能让他相信功能是对的,但完全不能让他相信 agent 没有引入漏洞、没有留下将来看不懂的死代码。

Veracode 今年春天那份报告给这句话补上了数字。80 个编码任务、4 种语言、4 类漏洞,累计测了 150 多个模型。有两个数字应该并排看:语法正确率已经超过 95%,安全通过率是 55%。拆开看更难受,XSS 只有 15% 过关,日志注入 13%,Java 最差,29%。

那 40 个百分点的缝隙有结构性的原因。测试问的是「我要的它做到了吗」,审查问的是「我没要的它做了吗」。测试是白名单,覆盖范围严格等于你列出来的那些条目;审查是黑名单,不带清单去读,找的是不该在那儿的东西。这两件事没法互相替代,因为没有人写得出一个断言叫「这段代码里没有我没想到的东西」。

Monperrus 的答案是让另一个 agent 站岗

6 月 11 日 arXiv 上 Martin Monperrus 那篇《代码审查的终结:编程 agent 取代人工检查》,主张取消「每个改动都要人逐行看」这条默认规矩:日常改动由 agent 签字放行,人工审批只留给高风险改动、新的架构决策,以及必须有具名的人承担法律责任的受监管代码。他要废的是那道默认闸门,不是人的判断。

他给的理由里最硬的一条是:AI 写、人必审这种组合本身是死路。一边是 LLM 能产出几百行看着合理、内部自洽、但藏着微妙语义错误的代码,人在 diff 上看不出来,审查退化成盖章;另一边是 AI 让产出涨了,人的审查容量没涨,瓶颈随生产力增益成比例扩大。他引 Google 的案例研究,大公司开发者本来就有 10% 到 15% 的工时花在审代码上。

这篇文章的性质要说清楚:它是立场论文,不是实验研究,作者综合已有研究做论证,自己没做实验。他引的最有力的数据是 agent 在 SWE-bench(拿真实 GitHub issue 考 AI 能不能修 bug 的基准)上两年内从 2% 左右涨到 70% 以上。

他给的替代方案里有一条要记住:跑多个独立的 agent,基于不同的模型和不同的提示策略,要求达成共识才放行。这不是别人替他补的,是他自己写进论文的处方。

他列的软肋同样有用。最重要的一条:生成和审查用同一个模型时,漏洞会双向隐形,因为同一个模型的盲点在两边是一样的,它写的时候没想到要转义,审的时候同样想不到。另一条是 prompt injection,代码里可以嵌入指令去操纵审查它的 agent,他的原话是这属于「一个活跃的安全研究领域,目前没有完全解决的防御手段」。

我们把第三个答案实践了一段时间

Monperrus 那篇缺的正是实验。我们在 botmux 上做的就是他主张的那套:Claude 写、Codex 审,评审时人已经不看具体代码了。先说结论。一定程度可行。下面两个 PR 既是证据,也是这个「一定程度」的边界所在。

审核出不该存在的配置组合漏洞

有个 PR 要加一个配置项叫 p2pOpen,用途很窄:打开之后任何人都能私聊这个机器人说话,但管理操作(重启、切目录、点敏感按钮)仍然只有名单上的人能做。说话放开,动手不放开。

代码写完了,功能测试也写了。陌生人能私聊,绿。陌生人不能执行管理命令,绿。

Codex 审的时候翻出一件事:判断「这个 bot 到底配没配权限边界」的那个函数,没把 p2pOpen 算进去。于是在只配了 p2pOpen、没配管理员名单的情况下,程序会认为这个 bot 什么边界都没设,进而落进「全开」模式,把群聊和管理权限一起放开。一个用途是「只放开说话」的开关,单独使用时会把管理权限交出去,跟它的语义正好相反。

测试之所以没抓到,是因为功能测试是照着功能写的。写测试的人(或者agent)心里装着「p2pOpen 打开之后应该是什么样」,所以他会把管理员名单一并配好,再去验证陌生人能说话、不能操作。而出问题的是「只配 p2pOpen、不配名单」这一种组合。没有人会去测一个自己认为不该存在的配置。

那条覆盖「只配 p2pOpen 不配名单」的测试,是审查之后才补进 PR 的。

既要断言发生了什么,也要断言什么都没发生

另一个 PR 换了一种失效方式,也很难在测试中发现。

它把定时任务的存储从一个共享文件拆成每个机器人各自一份,拆的时候要做数据迁移:老文件里的任务按归属分发到各自的新文件。其中有一类任务,归属信息指向一个已经不存在的机器人,代码把它们放进主机器人的文件,注释写着「归属不明的交给主机器人兜底执行」。

Codex 复审时把这条路走完了。任务确实进了主机器人的文件,但迁移代码保留了任务原来的归属字段,而调度器判断「这个任务是不是我的」看的是归属字段,不是文件位置。于是主机器人打开自己的文件,看到一个归属别人的任务,跳过。原来那个机器人哪天回来,读自己的文件,是空的。

这个任务从此没有任何人执行。注释里说的兜底,代码一次都没兜住。

它比 p2pOpen 那个更难抓,因为它的症状是什么都不发生。迁移跑完不报错,任务在列表里看得见,一切正常,只是它到点不响。已有的迁移测试断言的是「任务的 key 出现在主机器人的文件里」。这条断言是对的,是绿的。测试验证了数据落到哪,没验证有没有人会去执行它,bug 就住在这两句话中间的缝里。

安静没响的闹钟旁,一个人举着放大镜看一张全部打勾的清单

这类 bug 对管理者的意义要单独说一句:它不产生任何信号。崩溃有堆栈,报错进日志,而「一个定时任务安静地再也不响」什么都不留下。等到有人发现,中间往往已经过去几周,并且发现的方式通常是客户先问。

同一个 PR 的第二个阻塞项形状一样:一段负责兜底清理测试残留任务的代码,因为接口变了,调用时直接抛异常,于是所有兜底清理静默失效,测试造出来的真任务没人清,会持续往真实的群里发消息。安全网悄悄变成了空转。

换模型、换上下文能提高 agent 评审的质量

一轮审查不够,而且不同的轮次抓到的东西不一样。在实践中,提PR之前就需要不同的模型审核一次(Opus/Fable写, GPT评审), PR的评审者又会用几种不同的模型进行多轮(Opus一轮,GPT一轮)代码评审。

前一个 PR,Codex 提了 4 条,其中 1 条高危。之后另一轮独立评审换新的上下文重读,又捞出两条 Codex 没看到的:限流字段在新路径上没挂上,结果「任何人可私聊」这批最需要限额的人反而不受限额;某个命令限制策略管不到私聊陌生人,导致群里被正式授权的用户被拦下的命令,一个素不相识的人在私聊里反而能用,信任顺序整个倒过来。

后一个 PR 走了两轮。第一轮那两个阻塞项修完,第二轮才发现归属字段没做类型校验:传进一个布尔值 true,会被隐式转成字符串,任务落进一个叫 true 的目录;传 false 则被当成「无归属」,落进主机器人那里,用错误的机器人身份执行。

几轮审查的盲点不重合,这件事是观测到的,不是推演出来的。独立性有两个方面:一个是换模型,训练数据和训练目标不同,盲点分布就不同;另一个是换上下文,同一个模型开一个干净的会话重读,不被「我刚才为什么这么写」的记忆绑架。上面几轮,一轮换了模型,一轮换了上下文,各自都有收获。

两个 PR 最后补进去的测试(p2pOpen 那条,加上迁移的 15 例,覆盖 truefalse1230null、空串、对象)一条都不是凭空想出来的。它们是审查报告的产出物。这个顺序不能倒过来:一条测试能守住的漏洞,必须先有人发现这个漏洞存在。

这个发现挑战的正是 Uncle Bob 那套围栏的上限。用测试把 agent 围起来没错,但测试套件本身是审查历史的沉淀,它记录的是过去被发现过的失败模式。保留沉淀、取消产生沉淀的那个过程,只在没有新失效模式出现的前提下成立。围栏挡得住重复犯的错,挡不住第一次犯的错。

至于「一定程度可行」的那个「一定程度」:这套办法在实践中的确抓到了真漏洞,包括高危权限问题和静默失效,包括测试原本覆盖不到的整类输入。但它给不了一个漏检率。我知道它抓住了什么,不知道它漏了什么,漏掉的那部分不会举手。而且这两个 PR 最后都是有人拍板才合的。不读每一行,和不做判断,是两件事。

落到代码审查制度上

人眼该盯什么,是管理者真正要签字的部分。

先从「这段业务逻辑写得对不对」上撤下来,那是测试和互审的活,机器干得比人好,也比人便宜。人眼该盯的是这次改动碰了哪些边界:新增了什么依赖、动没动鉴权路径、有没有新的数据出口、错误处理里加了什么兜底分支、配置和密钥有没有被写进代码。这几处的共同点是,它们最难被测试覆盖(因为得先想到才测得到),又最容易被 agent 顺手加上(因为它在解决眼前那个问题)。

清单上还得加一类,就是上面那个定时任务代表的:本该发生的事情有没有真的发生。定时任务、清理任务、重试、对账、告警本身,这些东西失效的时候不报错,只是安静地不再工作。它们值不值得单列,取决于一件事:如果它停了两周没人知道,代价有多大。

这份清单上的东西不必都由人看。更实际的做法是按风险分配:把发生概率低、出事损失可控的模块划出来,审查整个交给 AI;把高风险模块隔离出来,人脑的注意力全押在那儿。分级这件事 agent 替不了,因为它不知道你们公司哪个模块出事要赔钱。

要说清楚的是,这条分级线不是我们实践里验证出来的。botmux 上并没有引入人工审核这一层,两个 PR 只证明了异构互审能抓到真东西,没告诉我们该在哪儿留人。分级是从这些教训往外推的建议。Monperrus 从成本账那头划的线落在同一个位置——他保留人工审批的地方,也正是高风险改动、新架构决策和受监管的代码路径。两边落点一致可以当个旁证,但两边都还是主张,不是实测。

在决定不看代码之前,还有一个问题要诚实回答:替你看的那台机器是什么。Uncle Bob 的是变异测试加一整套指标,我们的是另一个厂商的模型。不管选哪种,它得真的存在、真的在跑、有人真的在看它的输出。如果覆盖率报告已经三个月没人打开,CI 里也没有第二个模型把关,那么照搬「我不读 agent 的代码」,学到的只是这套方法里免费的那一半。

Veracode 那份报告里还有一个数字容易被略过:GPT-5.1 和 5.2 相比 GPT-4.1,在安全通过率上没有实质提升,Claude 4.5、4.6 相比更早的版本也是平的。语法正确率这一年一直在涨,安全通过率没动。等模型自己变安全,目前看还不现实。


来源

  • Robert C. Martin,2026 年 4 月 14 日:https://x.com/unclebobmartin/status/2044114698451476492
  • Robert C. Martin,2026 年 7 月:https://x.com/unclebobmartin/status/2080257779395154409
  • Uncle Bob 与 Grady Booch 之争的整理:http://mvark.blogspot.com/2026/04/uncle-bob-vs-grady-booch-rethinking.html
  • Veracode,2026 春季 GenAI 代码安全报告:https://www.veracode.com/blog/spring-2026-genai-code-security/
  • Martin Monperrus,《The End of Code Review: Coding Agents Supersede Human Inspection》,arXiv:2606.13175,2026 年 6 月 11 日:https://arxiv.org/abs/2606.13175
  • p2pOpen 的例子出自 botmux PR #448:https://github.com/deepcoldy/botmux/pull/448
  • 定时任务迁移的例子出自 botmux PR #611:https://github.com/deepcoldy/botmux/pull/611