跳到主要内容

橡皮图章式崩溃:为什么 AI 编写的 PR 正在掏空代码审查

· 阅读需 12 分钟
Tian Pan
Software Engineer

一位资深工程师在四分钟内批准了一个 400 行的 PR。diff 很整洁。命名很合理。测试通过。两周后,值班工程师翻阅一个查询时发现,它返回的行形状是对的,但取错了列 —— 本该用 user.created_at 的地方用了 user.updated_at —— 队列分析仪表板已经对 CFO 撒了九天的谎。审查者很称职。代码结构良好。这个 bug 在 diff 中是不可见的,因为它不是语法异味,而是语义问题。审查者无从着力,因为没有人写下这个变更原本打算做什么。

一旦你代码库中的大部分 diff 都源自模型输出,这种失效模式就会出现。审查者不再问“这正确吗?”,而是开始问“这看起来像代码吗?”。答案几乎总是肯定的。AI 编写的代码在语法上极其流畅,这种流畅性绕过了工程师们花费十年时间在人类编写的烂代码上磨练出来的审查启发式规则。

数据并非微不足道。一项针对企业级仓库中 Pull Request 的大规模研究发现,AI 辅助的 PR 总体上产生的 issue 多出约 1.7 倍,其中严重级别 issue 增加了约 40%,逻辑与正确性发现增加了 75%。在 90 分位处,AI 编写的 PR 每次变更包含 26 个 issue —— 是人类基准的两倍多。另一份覆盖 2.2 万名开发者的 2026 年遥测数据集报告称,每个 PR 的事故数上升了 242.7%,而个人产出指标也在攀升。吞吐量上去了,逃逸缺陷上升得更快。

为什么会出现橡皮图章

“橡皮图章”(随手批准)并不是因为懒惰。它是对信噪比变化的一种理性反应。当大多数 PR 还是人类编写时,审查者的大脑会运行一个后台分类器:草率的命名、不一致的缩进、一个函数做两件事、一个带有否定含义的布尔变量名 —— 这些都与深层问题相关联。语法异味是语义风险的廉价替代指标,而且它很管用,因为在形式上偷工减料的人通常在逻辑上也会偷工减料。

AI 的输出打破了这种关联。模型生成的代码看起来像是出自一位细心的工程师之手:符合项目风格的 docstrings、与项目基调一致的错误信息、从合适的词典中选取的变量名。形式与其产生的推理过程是脱节的。一个习惯于扫描语法异味的审查者,现在是在扫描一个已经不复存在的信号。

更糟糕的是,失效模式往往是人眼难以捕捉的。幻觉出的 API 虽然能编译,但对应的却是你所使用的库版本中并不存在的方法。一个查询关联了正确的表,却投射了错误的列。一个 except 子句吞掉了一个特定的错误,而调用者正依赖该错误进行重试。一个运行 N+1 查询的循环,因为模型默认选择了逐行查找,而不是代码库中已有的批量查找。这些在 diff 层面都不显现为问题。它们表现为普通的、看似合理的代码 —— 这正是 AI 优化的目标产物。

再加上规模的变化。AI 编写的 PR 平均体积更大;一项研究指出,PR 大线的中位数上升了 51.3%,中位审查时间增加了 441%。一旦超过 400 行,审查者就不再是审查,而是在抽样。diff 越大,橡皮图章就盖得越果断,因为在某些时刻,审查者的选择要么是敷衍的批准,要么是诚实地说“我无法在合理时间内审查完这个” —— 第一种选择没有政治成本,而第二种则会被视为阻挠主义。

没人绘制的信号退化曲线

橡皮图章式崩溃在组织上的表现是:在 AI 编写的变更中,有一对指标正朝着相反的方向运动。单个 PR 的审查时间下降(审查者没有深入参与,因为无从下手)。同样是这些 PR,Bug 逃逸率却在上升。这两个变动单独看都不足为奇,但结合在一起就是其特征签名。

大多数团队不会按作者模式切分他们的审查指标,因此这种趋势是不可见的。领导层看到总体吞吐量提高、总体周期时间缩短,便得出 AI 推广卓有成效的结论。事故曲线会在三到六个月后追上来,由于事故具有噪声且由多因造成,因此很容易忽略其与审查流程之间的联系。六个月的时间也足以让那些原本能察觉到偏差的工程师习惯这种新常态。

以下是一些先行指标,表明你的审查质量已经跨过了红线,而还没人公开指出来:

  • 审查者越来越多地批准那些涉及他们从未接触过的代码库部分的 PR,且不提任何问题。
  • 评论偏向于细枝末节(命名、导入顺序、单行重构),而不是“这到底是在做什么?”。
  • 回滚原因从“误解了需求”(审查可发现的类别)转变为“生产数据中的边界情况”(通常并非如此,但现在成了一个方便的托辞)。
  • 在站会中出现“AI 写了大部分,我只是清理了一下”这类话,且从未受到质疑。
  • 事故后审查(Post-incident reviews)不再揭示审查漏洞,因为没人愿意对同事写下“审查者没有仔细阅读”,而怪罪“AI 幻觉了一个方法”则容易得多。

这些迹象单独看都不是问题。但这种模式本身就是问题。

能够经受团队实战考验的对策

直觉通常是强制要求在各方面进行更严格的审查。但这行不通,因为审查者的注意力上限是固定的,而 AI 生成的代码量却不是,结果就是同样的“走过场”被应用到了更多的代码上。真正有效的对策是重导注意力,而不是索取更多注意力。

在 PR 正文中加入一段由人类编写的“意图”说明。 不是描述改动了什么,而是描述改动背后的 意图。审查者应该验证发生了什么?合并后哪些不变量(invariants)必须依然成立?哪些规则以前只存在于审查者的脑海中,而现在因为作者没考虑到而面临风险?这是最有效的杠杆。它迫使作者将模型生成的代码与变动的 目的 分离开来,并给审查者提供一个非 AI 的锚点进行对比。如果作者无法清晰地写出意图说明,说明他们自己也没理解这个 PR。

轮换设置一个“AI 对手”审查员角色。 在每个重要的 PR 中,指定一名审查员专门负责寻找语法审查容易忽略的语义错误。他们对比意图说明阅读差异(diff),寻找人类容易跳过的错误类别:列级别的错误、虚构的 API、看起来合理但错误的库用法、被静默吞掉的异常、在错误作用域内维持的不变量。这个角色必须是明确指定的,否则大家都会以为别人在做这件事,结果谁都没做。

针对人类容易遗漏而非容易发现的失败模式,制定 AI 专用清单。 代码风格、格式和命名已经由 linter 和模型本身处理了。清单应该涵盖:每个外部 API 真的存在吗?每个 schema 引用是否匹配当前版本,而不是三个迁移版本前的?错误处理是具体的还是笼统的 except?新代码是否复用了现有的横切关注点(如认证、重试、日志)助手工具,还是模型在代码中重新造了轮子?是否添加了测试,且如果该改动试图防止的错误真的发生,测试是否会失败?与 linter 功能重叠的清单项不仅无用,甚至有害——它们会诱导审查者扫一眼清单了事。

为 AI 生成的变动设置 PR 大小上限。 如果 PR 过大,就拆分它。这不是风格偏好,而是对“走过场”现象主要由代码量决定这一事实的承认。一个 60 行的 AI 生成 PR 会得到真正的审查;而一个 600 行的 PR 只会得到一个签名。

跟踪“投入-审查比”。 衡量人类审查时长与作者使用 AI 辅助时长的比例。如果某一类 PR 的比例低于阈值,则需介入。这能捕捉到一种情况:一个 30 秒的模型调用生成了 400 行代码,而审查只用了 2 分钟。单从数学逻辑就能判断,这种审查不可能严谨。

这些方法都不需要改变工程师使用 AI 编写 代码的方式。它们改变的是团队如何将输出结果视为一个可审查的产物。

领导层必须明确回答的披露问题

每一个拥有超过几个使用 AI 编写代码的工程师组织,都必须回答一个大多数人想逃避的问题:在 PR 中,“这是 Claude 写的”(或 Copilot,或 Cursor 的后台代理)是必须填写的字段、可选的元数据,还是一种文化禁忌?

没有中立的默认选项。不询问也是一种选择——作者会默默提交 AI 输出而不披露,审查者无法调整注意力,当事故发生时,组织也失去了按编写模式分析审查质量的能力。不完善的询问方式(如可选的复选框)会导致选择性偏差:人们在认为 AI 表现出色时披露,在认为有风险时隐藏,这与披露的初衷背道而驰。

清晰的立场包括:

  • 强制披露,标注作者和来源。 PR 必须指明哪些部分是模型生成的,哪些是人工编辑的,以及使用了哪个提示词或代理。这正是 Linux 内核社区通过 Co-developed-by: 标签趋向的方向,也是 Apache 和 Fedora 项目通过 Generated-by:Assisted-by: 标签所制度化的做法。这虽然增加了摩擦,但它让“按编写模式分析审查质量”的问题有了答案。
  • 通过工作流进行结构化披露。 AI 生成的 PR 进入不同的审查路径——使用不同的模板、强制性的意图说明、专门的审查员角色。作者不需要标记每一行;工作流本身就是披露。这比逐行标注更具扩展性,也更难作弊。
  • 视同作者负责,无需披露。 无论代码是谁打出来的,作者都负全责,并将 每个 PR 的审查标准提高到适用于 AI 输出的水平。这种做法很诚实,但成本高昂,大多数组织口头上说是这种政策,但实际上并不会为此付出代价。

最不诚实的立场——也是许多团队默认陷入的立场——是宣称披露是可选的,看到没人使用可选字段后,得出“AI 辅助编写并不是一个关键因素”的结论。这其实是一个关键因素。信号的缺失反映的是关于衡量方式的选择,而非事实本身。

迎头赶上的曲线

六个月前在全员会议上庆祝 PR 吞吐量增长 10 倍的团队,正是那个季度事故数量悄然翻倍的团队。这两个数字是以不同滞后时间讲述的同一个故事。吞吐量是你当下看到的;而逃逸缺陷(escaped defects)则是代码在生产环境中运行足够久、边缘情况开始出现后你才会看到的。大多数组织不会溯及既往地将前者与后者进行对比绘图,因为等到他们准备这样做时,吞吐量的提升早已被计入下个季度的承诺中,而事故数量则被归因于“我们现在跑得更快了,出现一些回归(regression)是预料之中的”。

事实并非如此。回归是评审流程的失效,而评审流程之所以失效,是因为当代码作者分布发生变化时,它所依赖的信号不再起作用了。修复评审流程——包括意图说明(intent sections)、对抗性评审者(adversary reviewers)、针对语义失效的检查清单、代码大小限制以及诚实的信息披露——这样吞吐量的增长才能持久。如果不这样做,“走过场”式的审批(rubber stamp)就会不断累积,直到下一次痛苦的事故迫使你不得不面对这个问题。这里的选择在于:是你主动安排时间来讨论,还是由事故替你决定讨论的时机。

References:Let's stay in touch and Follow me for more thoughts and updates