跳到主要内容

Claude Code 团队如何做代码审查(Code Review)

Addy Osmani

原文来源:Claude Code Blog

当智能体以这样的产量生成代码时,你该如何做代码审查?在 Claude Code 团队,当我们做到高覆盖率、深度审查之后,便开始优化目标:找到人工审查时间的下限——少到不能再少,同时不让回滚率或事故率上升。

我们此前说过,Claude Code 自身约 90% 的代码是 Claude 写的。那么,在我们这样的产量下,代码审查是怎么做的?现在还有人在读代码吗?如果出了问题,又是谁发现的?

本文介绍 Claude Code 团队的代码审查流程及其演进过程。从六月到九月,Claude Code 代码库中合并的 PR 数量翻了一倍多,而需要回滚的比例下降了 40% 以上。

我们并没有取消审查。我们只是把大部分“阅读”的工作交给了机器,围绕人的时间与注意力做优化,并在“让错误可以被快速、轻松地撤销”上做了大量投入。

重新思考代码审查的目的

当智能体开始写代码,变更数量会迅速攀升。在 Anthropic 的单体仓库中,每天落地的变更量从今年五月的约 2,500 次增长到九月的 30,000 次以上。

Claude Code 有自己独立的仓库,与单体仓库分开,但它同样承受着巨大压力。当发布速度达到这种程度,手工逐行审查代码即便不是不可能,也极其困难。

代码审查承担两项职责:

  1. 在问题上线前发现并修复它
  2. 在团队内建立共享的上下文与对代码库的共同理解

本文主要关注第一项——发现并修复问题,第二项职责我们会在结尾前再回来讨论。

我们认为,“有没有人读过这段代码?”是一个并不精确的替代指标,它想回答的真问题是:“这个变更可能出什么问题?哪一道检查——自动的还是人工的——才是抓住它的正确方式?”

提出这个问题,也改变了审查发生的时机。过去,审查是漫漫长路尽头的一道关卡:写代码、开 PR、等同事来看。

现在,审查是持续运行的,更像写作时的拼写检查。大多数变更在被要求人工过目之前,已经至少被 Claude 审查并修复过一轮。这样既能拦住大部分例行错误,也把团队的注意力留给更高层次的问题和更细致的审视。

稀缺资源:人的时间与注意力

我们希望把人的时间与注意力留给只有人才能解决的问题,其余的都交给自动化检查。以我们当前的变更速度,人需要做的工作依然多得惊人,团队几乎每天都会遇到新的、前所未见的挑战。

人的时间涵盖了审查成本的两端:人们花在审查上的时间,以及花在修复坏代码所造成的破坏上的时间。如果省下的第一种时间转而出现在第二种上,那省了也等于没省。

所以我们的目标是:Claude Code 各仓库(CLI、桌面端与 Web 应用、移动端应用)中的大多数 PR,在流程中最多只经过一个人就能合并——这个人可以是作者本人——同时回滚率和事故率不上升。为了衡量人工时间,我们跟踪每个 PR 的人工批准次数(中位数与第 95 百分位)、人工评论数量,以及 PR 等待某个人处理的时间。回滚率和事故率是护栏:如果它们上升,就说明我们并没有节省人工时间,只是把它挪到了别处。

一次变更的生命周期

一次典型的变更,从工程师的笔记本电脑(或 Slack 中的一个 Claude 会话)出发,要经过多层代码审查。

一次典型的 Claude Code 变更,从作者的机器到用户

提交 PR 之前 工作已被验证 Claude 运行测试、构建与 linter; 另一个智能体在全新的上下文中 审查 diff 每个 PR 上 必需的 CI Lint、类型检查、各平台测试、构建 与打包。红灯即阻止合并 Claude 代码审查 多个智能体并行找 bug,验证者 对每条发现投票,通过者按 严重程度排序 安全审查 单独一轮。任一审查中未解决的 发现都会阻断合并 基于风险的批准 分类器定级:无需人工、一人 或两人。某些路径始终需人工 合并中 合并队列 在应用了该变更的最新 main 上 重新运行所有检查 合并之后 置于开关之后,先内部使用 Anthropic 员工先用过, 才会推给所有人 渐进式发布 先进入 latest 渠道,stable 渠道随后跟进 监控、向前修复或回滚 作者与 on-call 监控;开关可快速 关闭,坏的构建会被回滚 自动化 由人决定,按风险分级 恢复

图 A 路径上的大部分环节都是自动化的;人的注意力只投在风险所在之处。

逐层拆解

六层检查,其下还有回滚托底

问题进入 验证 必需的 CI AI 审查 安全审查 人工批准 合并队列 + 开关 快速回滚

图 B 每一层都能拦住一些问题,也会漏掉另一些。很少有变更能穿过所有层,剩下的就交给回滚。

1. 在任何人查看之前,工作就已经被验证过

如今大部分审查发生在 PR 存在之前。我们给 Claude 一些它可以实际运行的东西——比如测试、构建,或者一张用来与设计稿比对的截图——并要求它展示输出,而不是只说一句“做完了”。接着,由一个子智能体带着全新的上下文审查这份 diff。这两个习惯都来自我们的最佳实践指南。

在开 PR 之前,我们中很多人还会跑几个内部 skill:一个专门找 bug,一个负责简化 diff,还有一个端到端地验证行为。UI 改动还会对照我们的设计规范进行检查。

Claude Code 的创造者 Boris Cherny 曾说,给 Claude 一种验证自己工作的方式,可能是拿到好结果最重要的一件事。他估计这能让最终结果好上两到三倍,并写道:“验证在每个领域的样子都不一样。它可能简单到只是跑一条 bash 命令、运行一套测试,或者是在浏览器或手机模拟器里试用一下应用。”

当自动审查器反复指出同一个问题时,我们从源头修复它: 修改 skill、增加 lint 规则,或者更新下面这类指导性文件:

  • CLAUDE.md 告诉 Claude 这个项目是什么:技术栈、约定、目标与非目标。每次审查都会加载它,所以在大型代码库中我们会让它保持简短,并从这里指向其他文档。审查器还会标记违反其中约定的代码,以及让其中某一行变得过时的变更。
  • REVIEW.md 由代码所有者(code owner)撰写,告诉审查器在他们负责的那部分代码里什么才重要。我们另外还会单独检查审查器是否真的照做了。写得好的都很短。它们会说明在这个仓库里“失败”是什么样子(对文档来说是“它在说谎”,对配置来说是“它把路由配错了”),限制吹毛求疵的数量,列出应当跳过的路径,并且要求以文件和行号作为证据,而不是凭名字猜。它们还要求审查总结以数量开头,并在没有需要修复的内容时把话说清楚。

2. 每个 PR 都有 AI 审查

每个 PR 在被人看到之前,都会先经过一次 Claude 审查。它会结合整个仓库和 PR 声明的意图来阅读 diff,并追问:这个变更解决了它要解决的问题吗?它契合系统吗?它会不会破坏别的东西?

对于 bug 修复,它会去寻找根本原因。公开版本 Code Review for Claude Code 现已在 Team 和 Enterprise 套餐中正式可用,而 /code-review 可以在本地运行一个更轻量的版本。

我们推出研究预览版时,内部 PR 中获得实质性审查意见的比例从 16% 提升到了 54%。超过 1,000 行的 PR 大多会得到发现项,而大多数小 PR 则干干净净地返回。管理员可以为每个仓库设置 Quick、Standard 或 Deep 模式。在漏掉一个 bug 的代价高于额外审查时间的场景里,Deep 是值得的。

发现项要先由其他智能体验证,才会交给人看。 多个智能体同时寻找 bug。然后由另一组智能体对每条发现投票,只有在足够多的智能体认为它确实成立时,才会被发布出来。只涉及风格、诊断或清理的发现会被丢弃。在保留这些过滤规则之前,我们在自己的 PR 上做过 A/B 测试。剩下的内容以行内评论的形式、按严重程度排序呈现。每条都被标记为 Important、Nit 或 Pre-existing,这样 PR 没有引入的 bug 就不会看起来像是作者的错。

每条发现都必须被回应。 你要么修复它,要么带着理由把该讨论串标记为已解决。通常是智能体在夜里把修复做掉了,于是作者回来时,PR 已经又过了一轮。

3. 可以阻断合并的安全审查

安全也有自己的一套自动审查。只要 bug 审查或安全审查中还有任何未解决的发现项,PR 就不能合并。你要么修复它,要么用一句简短的理由关闭该讨论串——这也是清除误报的方式。

如果后续的提交修复了该问题,这条发现会自动清除。你也可以用开源的安全审查 GitHub Action 或 /security-review 命令搭建同样的机制。具体配置见 Claude Code 中的自动化安全审查。

4. 可以阻断合并的自动化检查

常规的那一套:lint、类型检查、各平台的测试,以及一次构建。(我们在这里写过如何为这样的规模扩展 CI。)一个典型的 CLI PR 在任何人看它之前可能要跑几十项检查。PR 模板会问这个变更是如何测试的、又该如何撤销。Claude 可以把这些填好,但提出该变更的人要为之背书。

5. 按风险分级的人工批准

一个文档修复和一个自动更新器改动带来的风险不同,所以它们得到的审查也不同。

风险 示例 人工批准
低 文档、文案、仅内部可见的功能、位于开关之后的增量改动 自动审查与安全检查全部通过后,无需人工批准
中 常规产品改动,以及对缓存、自动更新器、系统提示词等敏感区域的改动 一人
高 风险最高的代码,外加一小部分始终需要两人的路径 两人

决定等级的是变更本身,而不是作者。 一个分类器读取变更并判定其风险等级。第二个分类器则追问:即使等级判定为不需要他人,这个具体变更是否仍然需要另一个人?有些路径始终需要人来把关:所有者标记为敏感的任何内容,以及任何 CI 工作流文件。

只有受信任的作者才能走更轻的流程。 只有在仓库受信任名单上的作者才能走更轻的流程,这份名单主要是拥有该仓库的团队。其他所有人都需要第二个人批准。智能体的批准也永远不能算作人的批准。自动审查可以让低风险变更通过,但它无法填补一个必须由人来填的位置。

谁发起了这个变更,谁就要对它负责。 在智能体写的 PR 上,指挥智能体的人算作必需的审查者之一,就像代码是他自己敲的一样。

一次批准只覆盖某一个具体的提交。 如果之后有新提交落地,而系统无法为其背书——比如智能体推送的提交,或某人手工解决的合并——那么批准就必须重新给出。

6. 合并队列、功能开关与渐进式发布

合并队列会在应用了该变更的最新 main 上重新运行所有检查,这样两个各自都能通过的 PR 就不会组合出一个坏掉的 main。新行为通常先放在功能开关之后、先向 Anthropic 员工发布,而我们自己的重度使用往往能比用户更早暴露出回归问题。公开发布先进入 latest 渠道,stable 渠道随后跟进。

7. 快速发现,快速回滚

有些错误终究会漏过去,所以最后一层的任务是尽快抓住它们。功能开关无需部署即可关闭。Claude Code 有每周轮换的 on-call 值班,事故复盘会追问:本该是哪一层抓住这个问题?

发现一次糟糕的合并,仍然要依赖作者和 on-call,以及回滚与事故仪表盘。我们目前还没有那种在指标变动时自动收紧审批的熔断机制;在更多代码走向“零人工合并”之前,这里的工程师希望先有这套机制。

一位同事说,放慢速度确实能降低错误率,但主要是因为你在所有事情上都做得更少了。我们更愿意在“发现问题、修复问题”这件事上变得更快。

我们如何让审查者变得更好

我们的代码审查流程在不断循环中持续改进。

任何事故现在都会变成一项评测。 团队收集了过去引发事故的 PR,连同它们的修复跟进(fix-forward)和回滚(revert),并请熟悉那段代码的工程师逐一确认每个 bug 以及它所在的位置。审查者每天都会针对这套集合运行。

由过去的事故和回滚构建而成,每天运行

事故或回滚 一次弄坏了东西的改动 经专家核验的用例 专家确认这个 bug 加入评测集 每次事故增加一个用例 每日评测运行 针对每个用例打分 分数提高了吗? 保留;否则丢弃 修改审查者 它的提示词、工具或模型

图 C 评测集会随着每一次事故而增长,而对审查者的每一处改动都要针对它的全部内容打分。

我们通过观察人们如何处理评论来衡量噪音。 如果你不介意标记大量并非 bug 的问题,那么发现更多 bug 是很容易的,因此我们需要对此加以约束。我们把审查者留下的每条评论按 PR 提交者的反应进行分类:他们修复了它、说它是错的、说该行为是有意为之,或者认为它无关紧要。我们还会让审查者在已知是干净的 PR 上运行,统计它报告出那种会让人做出有害改动的问题的频率。我们的优先级是白纸黑字写下来的:首先抓住真正的 bug,其次才是减少审查轮次。

当作者是 agent 时,噪音的代价更高。一个人可以在几秒钟内把一个吹毛求疵的意见挥手带过,而 agent 会去修它,这意味着又一次提交和又一轮审查。

审查必须收敛。 agent 作者和 agent 审查者可以让彼此无休止地忙碌下去。有一个 CLI 的 PR 在一夜之间经历了五十多轮审查与修复,直到它的作者把它叫停。

当我们对审查者反复标记的代码运行 git blame 时,发现其中约 59% 是审查者第一轮审查时就已存在的代码。审查者很少是在发现新 bug,它只是在每一轮里多找到几个旧 bug。

我们自己也让情况变得更糟。由于每一轮都能抓到新东西,我们已经习惯了默认跑好几轮。对于有风险的改动,这可能值得,但不该对每个 PR 都这样。团队现在把“通过审查所需轮数”和召回率一起跟踪,并改掉了复审方式:

  • 设置轮数上限。 三轮之后,审查者会请作者退一步,重新考虑这个方案。
  • 前几轮之后,只对 diff 进行复审。
  • 标记 bug 的类别, 这样作者会修复每一个实例,而不只是一行。
  • 不重复已经发布过的发现。

审查 agent 还会每晚对现有代码运行,帮助我们在旧 bug 于别处显现之前就抓到它们。

内部 PR 在收敛性改造前后的对比

一轮审查与修复后即通过 65% 78% 需要五轮及以上 8.9% 1.5% 平均通过所需审查轮数 2.08 1.32 改造前 收敛性改造之后

图 D 收敛性改造之后,每个内部 PR 所需的审查轮数都有所减少

人类在哪些环节仍处于回路之中

把一位资深工程师从深度工作中拉出来,只为了抓一个例行错误,代价远高于一次自动审查。团队中的人仍然负责:

  • 决定要不要做。 方向、品味和范围。有一个团队宣布其审查瓶颈已经解决,却仍然对来自团队外部的改动保留人工审查,为的是有人问一句“我们到底该不该做这件事?”
  • 审查高风险代码, 例如计费、自动更新器和 CI 工作流。
  • 审查架构, 尤其是那些会成为他人效仿范式的架构。
  • 阅读证据。 测试输出、运行的命令及其返回结果、一张截图、审查者的发现。如果你说不清这些检查会漏掉什么,那就要求一次真正的审查,而不是快速点一个通过。
  • 对结果负责。 每一个上线的改动都由一个人负责,不管它是谁敲出来的。

在团队内共享上下文

代码审查的第二项职责,一直是传播对代码库的理解。阅读同事的 diff,正是你了解改了什么、各部分如何拼接的方式。如果大部分阅读都由机器完成,人们又该如何学习代码?

我们对此没有正式的流程。团队里没有人脑中装着 Claude Code 的完整心智模型,我们也不认为有谁需要这样。当有人想理解代码库的某一部分时,他们会请 Claude 专门为那一块写一份讲解。这只需几分钟,而且反映的是代码当下的样子。

标题为“如果大部分阅读都由机器完成,人们又该如何学习代码?”的示意图。左侧是一个代表代码库的网格,其中少数方格被高亮标示为“托管设置”(managed settings),也就是你想理解的那部分;没有人在脑中保留它的完整心智模型。一个标注为“就这一块去问 Claude”的箭头,配着示例问题“Claude Code 是如何获取托管设置的?”,指向两种结果:Claude 写出一份讲解,这需要几分钟,并反映代码当下的样子;以及,对某些人来说,Claude 会构建一个小工具,把代码的这一部分可视化,并以那个人喜欢的学习方式来讲解它。底部一行:逐行阅读琐碎的 diff——一行文案改动几乎教不了你什么;而针对你即将改动的模块的讲解,能教给你很多。

标题为“如果大部分阅读都由机器完成,人们又该如何学习代码?”的示意图。左侧是一个代表代码库的网格,其中少数方格被高亮标示为“托管设置”(managed settings),也就是你想理解的那部分;没有人在脑中保留它的完整心智模型。一个标注为“就这一块去问 Claude”的箭头,配着示例问题“Claude Code 是如何获取托管设置的?”,指向两种结果:Claude 写出一份讲解,这需要几分钟,并反映代码当下的样子;以及,对某些人来说,Claude 会构建一个小工具,把代码的这一部分可视化,并以那个人喜欢的学习方式来讲解它。底部一行:逐行阅读琐碎的 diff——一行文案改动几乎教不了你什么;而针对你即将改动的模块的讲解,能教给你很多。

我们中的一些人更进一步,让 Claude 构建一个小工具,把代码的那一部分可视化,并以那个人喜欢的学习方式来讲解它。

如果目标是让人力注意力花得值,这比逐行阅读琐碎的 diff 要好得多。一行文案改动几乎教不了你什么,而针对你即将改动的模块的讲解,能教给你很多。我们默认在公开 Slack 频道里工作的文化也有帮助,因为你能在别人构建的同时就看到他们在做什么。

“你合入,你负责”

如果你合入了一个小改动或中等改动,而事情仍然出了问题,团队惯例是通常由你来修复它。有几点让这件事不至于变得糟糕:

  • 改动都很小, 因此易于审查、调试和回滚。
  • 一切都是可逆的。 开关(flag)、回滚和分阶段发布能在几分钟内撤销大多数错误。
  • 影响范围决定门槛。 代价高昂的代码会得到更多人和更多检查。
  • 回归问题会很快浮现, 靠的是遥测数据和内部使用。
  • 这不是追责。 负责修复意味着你掌握最多的上下文;事故复盘审视的是系统。

归属权也是共享的。团队中很多人都在公开频道里工作,任何人都能看到别人在构建什么、接手一个讨论串,或者让 Claude 去处理某个缺口;反馈也来自那些并不拥有这段代码的人。这种开放性是质量得以在不逐行设卡的情况下保持高水平的重要原因。

当我们改变方向时

agent 让启动一件事变得很便宜,因此有些工作不该合入。通常频道里会有人问为什么、指向早先的某个决定,或者建议一条更简单的路径,然后作者关闭或重塑这个 PR。

由于代码写得很快,关闭 PR 并不会让人觉得是损失。诀窍在于尽早在这个讨论串里提出不同的方向,而不是在 PR 已经完成后才以阻塞性评论的方式提出来。

你如何采用这一流程

与你的团队一起落地这套流程所需的绝大多数组件,今天就已经存在了。

提交拉取请求之前

  • 给智能体提供它可以自行运行的检查,把它们列在一份简短的 CLAUDE.md 里,再加上运行格式化工具和 linter 的 hook。
  • 在本地运行 /code-review 和 /security-review,或者让一个全新上下文的子智能体来做评审。
  • 添加一个 REVIEW.md,把重复出现的评审意见搬进去。

每个拉取请求

  • 要求必须通过 CI,并在 PR 模板中加入「Testing(测试)」「Risk(风险)」和「How to revert(如何回滚)」。
  • 先在一个仓库上接入 AI 评审者(Code Review for Claude Code,或 Claude Code GitHub Action),花几周时间调优 REVIEW.md,然后再扩大范围。
  • 加入自动化安全评审,并要求合并前每一条评审线程都已解决。
  • 让评审投入与风险相匹配:常规改动快速过一遍,漏掉 bug 代价高昂的地方则深度评审(Code Review 中的 Quick、Standard 或 Deep)。
  • 限制评审轮次;前几轮之后,只对新增的 diff 复审。

审批

  • 把风险等级写下来,按改动本身而非作者来设定;起步从严,再依据证据逐步放宽。
  • 对你信任的作者保留更轻量的审批路径。
  • 使用 CODEOWNERS 和分支保护,确保认证、计费、CI 与发布工作流以及安装程序始终需要真人把关。
  • 绝不能让机器人的批准替代必需的人类批准;有新提交时作废旧批准。

合并及之后

  • 使用合并队列、功能开关、先内部使用、分阶段发布以及一键回滚。
  • 把事故和回滚转化为一套 eval 数据集,并在评审者的提示词、工具或模型发生变化时重新打分。
  • 衡量人类投入的时间,并以回滚率和事故率作为护栏指标。

这一方向将走向何处

Code Review 的发布公告中提到,批准一个拉取请求仍然是人类的决定;该产品只发布发现的问题,不做批准。我们自己的仓库走得更远:让所有者自行决定哪些低风险改动可以仅凭自动化检查就合并,而这条界线还在不断移动。

当评审漏掉某些问题时,我们会追问模型当时手上缺了什么,并把补救措施写进 CLAUDE.md、一个 skill 或一项新的检查。目标从未改变:让每次改动消耗的人类时间更少,同时回滚次数不比从前更多。

如果你的团队允许智能体提交拉取请求,我们很想听听:你自动化了哪些检查、哪些仍然需要真人把关,以及你认为我们哪里做错了。

感谢 Emma Webb、Michael Segner 和 Amie Rotherham 的评审。

评论