Claude Code 团队如何进行代码审查

Anthropic 用「七层自动审查+人工只兜高风险」重构代码审查:PR 量翻倍、回滚率反降 40%,人的时间只留给只有人能做的事。

ADDY OSMANI(@ADDYOSMANI)深度报道 · 已翻译 · 约 18 分钟
阅览室 · AI 最佳实践#审查#代码#Claude Code#PR#团队#流程原文

我们之前提到过,Claude Code 约 90% 的代码由 Claude 编写。那么在这种规模下,我们如何做代码审查?到底谁在读代码?如果代码出了问题会怎样?

本文介绍我们团队在 Claude Code 的代码审查流程,以及我们如何改进它。从六月到九月,合并进 Claude Code 代码库的拉取请求数量增长了一倍多,而不得不回滚的拉取请求占比下降了 40% 以上。

我们并没有停止做代码审查。相反,我们把大部分阅读工作自动化了,并对其进行了调优,以优化人类投入的时间和精力。我们还投入了大量资源来提升回滚错误的速度和便捷性。

重新思考代码审查的目标

在智能体编写代码的情况下,变更量会迅速增长。在我们的单体仓库中,五月份每天大约有 2,500 个变更被合并,而到今年九月,这一数字已超过 30,000。

虽然 Claude Code 不在我们的单体仓库中,但它同样承受着很大压力。当你以这样的速度推进时,逐行手动进行代码审查会变得越来越困难,甚至不可能。

代码审查有两个目的:

  1. 在发布前发现并纠正缺陷
  2. 建立对代码库的共同理解

虽然本文主要关注第一个目标,但我们会在文末讨论第二个目标。

我们认为,“有没有人读过代码?”并不是一个精确的衡量标准;更准确的问法是:“这个变更可能出什么问题,而哪种检查——自动化的还是人工的——才是发现它的正确方式?”

这个问题也改变了代码审查的时机。传统上,代码审查是一种事后检查:代码写完了、PR 提交了,然后有人等着另一个人来看。而我们则把代码审查融入了工作流,它更像是你在写作过程中持续进行的拼写检查。

大多数改动在请人来查看之前,已经由 Claude 至少审查并修正过一次。这样一来,我们就能捕获大部分常规缺陷,把人的注意力留给更高层面的考量。

人的时间与注意力

我们希望把人的时间和注意力留给只有人类才能处理的事情,同时确保有足够的自动化来覆盖其余部分。即便以我们目前的节奏,人类仍有大量工作要做,团队几乎每天都会遇到新的、前所未有的挑战。

因此,人的时间是一种双向成本。我们不仅计算人们花在代码审查上的时间,也计算他们花在修复劣质代码所引入缺陷上的时间。如果我们只减少了前者而没有减少后者,那并没有真正节省人的时间。

所以,我们的目标是:我们 Claude Code 各仓库(CLI、桌面端与 Web 应用、移动应用)中的大多数拉取请求,应当在最多只有一个人参与(此人可以是作者)的情况下合并,且回滚或事故数量不增加。

我们以每个 PR 的人工批准数中位数(以及第 95 百分位数)、人工评论数量、以及 PR 等待某个人处理的时间来衡量人的时间。我们用回滚率和事故率作为护栏:如果这些指标上升,说明我们并没有减少人的时间,只是把它转移到了别处。

一次变更的旅程是怎样的?

一次典型的变更,无论来自工程师的笔记本电脑,还是来自 Slack 中的 Claude 会话,都要经过多层审查。

图A:一项变更的生命周期
图A:一项变更的生命周期

上图展示的是一次典型的 Claude Code 变更,从作者的环境一直到生产环境。这个过程大部分是自动化的,人的注意力集中在有风险的部分。

我们有六层检查,外加一套回滚机制,用来处理任何漏过检查的问题。

图B:多层把关,而非单一闸门
图B:多层把关,而非单一闸门

每一层都能捕获一些 bug,同时也会漏掉另一些。很少有 bug 能穿过所有层;如果真的出现了,我们也有回滚机制兜底。

1.PR 前的验证:

我们的大部分审查都在创建 PR 之前完成。我们会让 Claude 去做一些可运行的事情,比如跑测试、跑构建,或者把截图和设计稿做对比。我们不会只说“任务完成了”,而是要求它把结果展示出来。然后,一个子代理会用全新的上下文审查 diff。这两种策略都写进了我们的最佳实践指南。

在开 PR 之前,我们中的许多人还会跑几个内部技能——查找 bug、精简 diff、端到端验证,以及对照设计规范检查 UI 变更。

据 Claude Code 的创造者 Boris Cherny 所说,为 Claude 配备一套验证流程,可能是获得更高质量结果的最大关键。

他估计,验证流程能把 Claude 输出的质量提升两到三倍。

不同领域的验证流程可能差别很大:

有时它简单到只是跑一条 bash 命令。有时是跑一整套测试。有时是在浏览器或手机模拟器里测试应用。

如果自动审查器反复标记同一个问题,我们就从源头修复——通过技能本身、lint 规则,或更新以下文档文件:

CLAUDE.md——项目描述文件,你可以用它向 Claude 提供项目背景(技术栈、约定、目标、非目标)。Claude Code 在每一次审查中都会引入这个文件,因此对于大型代码库,我们保持它简短,并在此处引用其余文档。当代码违反了该文件中的约定,或某次更新使其中某些内容过时,审查器就会标记出来。

REVIEW.md——代码所有者用来告诉审查器其负责的代码部分中哪些内容重要的文件。我们会单独验证审查器是否确实遵循了这些准则。好的准则简短,并聚焦于:

定义项目失败(文档不应说谎;配置不应错误路由)限制吹毛求疵定义忽略的路径要求提供文件 + 行号引用,而非猜测文件名要求提供顶层摘要,包括问题数量以及明确声明“无问题”

2. 每个拉取请求上的 AI 审查

每个 PR 在人工审查之前都会收到 Claude 审查。

差异会对照整个代码库以及 PR 作者所声明的 PR 意图进行分析。它会检查:变更是否确实解决了该问题、变更是否融入了更大的系统、变更是否未破坏代码库的其他部分

在审查缺陷修复时,机器人会尝试找到根本原因。

我们的公开产品 Code Review for Claude Code 现已面向所有 Team 和 Enterprise 客户开放。还有一个供本地使用的轻量版本——/code-review。

自从我们发布研究预览版以来,内部 PR 获得实质性评审意见的比例已从 16% 提升到 54%。代码评审能在大多数超过 1,000 行的 PR 上发现问题,而对大多数较小的 PR 则返回无问题的结果。管理员可以为仓库配置 Quick、Standard 或 Deep 模式。当漏掉一个 bug 的代价高于评审代码所花的时间时,Deep 模式就很有用。

在人工评审之前,发现的问题会由多个其他 agent 进行审核:

多个 agent 并行运行以查找 bug

由单独的投票决定某个发现是有效还是误报,只确认获得足够票数的发现

仅与风格/诊断/清理相关的发现会被过滤掉(在推广之前,我们在自己的 PR 上对这些过滤器做了 A/B 测试)

剩余的发现以行内评论的形式呈现,按严重程度排序,并标记为:

Important Nit Pre-existing(这样我们就不会因为并非由该 PR 引入的 bug 而不公平地惩罚作者)

所有发现都必须得到处理——要么修复,要么在讨论串中说明理由后将其解决。在许多情况下,agent 会在一夜之间修复问题,因此作者回来时会看到一个已经经过另一轮评审的 PR。

3. 可阻止合并的安全评审

我们还会运行自动化的安全评审。只要仍有未解决的发现(无论是来自代码评审还是安全评审),任何 PR 都不能合并。作者可以修复问题,或用简短的理由关闭讨论串(我们就是这样处理误报的)。

如果后续提交解决了该问题,发现会被自动关闭。你可以通过开源安全评审 GitHub Action 或 /security-review 命令设置同样的机制。关于如何设置,请参阅 Claude Code 中的自动化安全评审指南。

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

标准检查:lint、类型检查、全平台测试、构建。(我们曾在这里写过如何扩展 CI。)一个典型的 CLI PR 在任何人查看之前,就可能要跑几十项检查。PR 模板中设有问题,询问本次改动是如何测试的、以及如何回滚。Claude 可以替你回答这些问题,但作者有责任对它们进行审核。

5. 人工审批,按风险分级

有些改动比其他改动风险更高(例如文档修复与自动更新器改动),因此需要不同级别的审核。

图C
图C

级别由改动本身决定,而非由作者决定。一个分类器根据改动判定风险级别。另一个分类器则判定某项具体改动是否实际上还需要另一次审批,即便级别并未如此要求。某些路径始终需要另一次审批——任何被所有者标记为敏感的路径,以及任何 CI 工作流文件。

只有位于仓库信任名单上的作者才能享受放宽的审核要求,而这通常就是拥有该仓库的团队。其他所有人都需要第二个人批准。智能体的批准不算作人工批准。自动审核可以放行低风险改动,但在需要人工时,它不能替代人工。

改动的作者对其负责,因此如果由智能体撰写 PR,智能体的操作者始终被计为必需审核人之一。一次 PR 批准仅对一次提交有效。如果落入一个不可信的新提交(例如智能体推送了内容,或有人手动解决了合并),该 PR 必须重新批准。

6. 合并队列、功能开关与分阶段发布

合并队列确保所有检查都在应用了该改动的 main 分支上运行。这可以防止将两个各自成功的 PR 合并成一个损坏的 main。通常,新功能会在功能开关之后发布,Anthropic 员工会最先获得访问权限。由于我们大量使用该产品,我们往往能比用户更早发现回归问题。

当我们公开发布时,变更会先进入最新渠道,稳定渠道则滞后跟进。

7. 快速反馈与回滚

有些糟糕的合并难免会漏过去。这是我们的最后一道防线,重点在于尽快识别出这些问题。位于功能开关之后的变更可以在不部署的情况下被禁用。

Claude Code 实行每周轮值的 on-call 制度,任何事故复盘都会回溯查看本应由哪一层拦住问题。除了监控回滚和事故仪表盘之外,发现糟糕合并仍然要靠作者和 on-call 人员。我们目前还没有根据这些情况调整审批要求的熔断机制,但正在研究,因为我们在朝着减少合并中人工干预的方向推进。

一位团队成员指出,放慢速度、减少产出实际上能提高准确率,但这主要是因为整体工作量减少了。我们的优先事项其实是提升快速识别并纠正问题的能力。

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

我们的代码审查流程是一个持续改进的循环:

我们把事故作为评估指标。团队收集了引发事故的 PR、它们的修复前滚和回滚,并让理解代码库的工程师核实每个缺陷的位置和正确性。审查系统每天都会对照这个集合进行评估。

图D:我们的故障就是评估
图D:我们的故障就是评估

每一起事故都会加入这个集合,而对审查系统的每一次改动都会对照整个集合进行评估。

为了衡量噪声,我们观察人类对审查评论的反应。如果我们愿意报告大量非缺陷,那么提高缺陷发现率很容易,所以这一点需要加以约束。我们根据 PR 上的人如何处理每条审查评论来分类:修复了问题、拒绝了、澄清为有意为之,或认定无关紧要。此外,我们会在已知干净的 PR 上运行审查系统,观察系统报告出本会导致有害改动的问题的频率。

我们声明的优先级是:第一,检测真实缺陷;第二,减少审查循环次数。

当作者是智能体时,处理一条审查评论的代价更高。人类驳回一条小意见只需一秒钟,但当智能体必须处理它时,就会产生又一次提交,以及又一轮审查。

审查流程必须收敛。智能体作者和智能体审查者有可能无限循环。我们观察到 CLI 项目中的一个 PR 在一夜之间循环了五十多次,原因是智能体作者和智能体审查者之间的来回往复。这个循环被手动停止了,但它本可以无限持续下去。

当我们对审查系统反复标记的内容运行 git blame 时,约 59% 是自首次审查以来就一直存在的代码。本质上,审查系统很少发现新 bug,而是在每个周期中发现一些更多的旧 bug。

我们让情况变得更糟,因为我们养成了默认运行多个审查周期的习惯,因为每个周期都可能发现不同的 bug。虽然对于高风险变更来说,迭代多个审查周期可能有用,但并非每个 PR 都需要。我们的团队开始除了召回率之外还跟踪通过所需的周期数,并将重新审查更新为:

限制周期数。审查系统在三个周期后要求作者重新评估方案。在初始周期之后,将重新审查限制为仅 diff。指定 bug 类别,以便作者可以纠正所有 bug,而不仅仅是一行。避免重复报告已经观察到的问题。审查代理还会在夜间对当前代码库运行,帮助我们在历史 bug 传播之前识别它们。

图E:收敛式评审
图E:收敛式评审

Convergence 之后内部拉取请求平均审查轮数的减少。

人在回路中

与自动化流程相比,让资深工程师停下工作去审查一个常见错误,成本高昂。不过,对于某些任务来说,人工审查仍然不可或缺:

  • 我们到底该不该做这件事?决定是否构建某个东西,设定产品的愿景、品味和目标。

有一个团队声称他们已经解决了代码审查的瓶颈,但对于来自团队外部的变更,他们仍然会进行人工审查,以确保要构建的东西是正确的。

  • 审计危险代码(计费、自动更新器、CI 工作流)。
  • 架构审查,因为这些选择会确立其他人遵循的约定。
  • 验证证据(测试输出、运行过的命令、截图、审查者评论)。如果你说不清这些检查会漏掉什么,那就别求一个 👍,你需要的是真正的审查!
  • 承担责任。每一次上线的变更都要有人负责,无论代码是谁敲的。

团队上下文

代码审查一直肩负着双重目的:沟通代码库的变更。阅读同事变更的 diff,是人们通常了解发生了什么变化、以及代码库如何运作的方式。如果大部分阅读工作都由机器完成,那么学习代码库的方式又会变成什么?

我们没有任何正式的机制来处理这件事。团队中没有任何成员在脑子里装着 Claude Code 的完整心智模型,我们也不认为有谁需要这样。相反,当有人想了解代码库的某一部分时,他们会请 Claude 针对那个确切的切片生成一份讲解。这只需要几分钟,而且讲解始终是最新的。

图F:如果大部分阅读由机器完成,人们如何学习代码?
图F:如果大部分阅读由机器完成,人们如何学习代码?

对我们中的一些人来说,我们会更进一步,让 Claude 生成一个小工件,把这个区域可视化,并以我们偏好的风格来讲解它。

相比逐行阅读琐碎的 diff,这是集中我们人类注意力的更好方式——逐行读 diff 学不到多少东西(比如只改了一行文案)。为你即将改动的模块写一份讲解文档,能让你学到很多。

此外,我们默认开放,在公开的 Slack 频道里工作,能看到别人正在构建什么、进展如何。

“你合入,你负责”

如果你合入了一个小型或中型改动,之后仍然出了问题,普遍的预期是你通常要自己修复它。这不会失控,原因有几点:

  • 改动小,容易审查、调试和回滚。
  • 一切都可以回滚。功能开关、回滚和分阶段发布能在几分钟内逆转大多数错误。
  • 影响范围决定门槛。代价越高的代码,获得的审查和验证越多。
  • 回归问题会被遥测和内部使用快速暴露出来。
  • 这不是追责游戏。修复问题的人拥有它,因为他们掌握最多的上下文。事故复盘关注的是系统。
  • 此外,团队中很多人通过在公开频道里工作来分担责任。任何人都能看到别人在构建什么、加入讨论,或让 Claude 查找缺口。反馈也来自非代码负责人。这种开放性是质量得以保持高水平、而无需对每一行都设关卡的重要原因。

调整方向

有了智能体,启动新工作的成本很低,但有时你并不该这么做。这种情况通常出现在频道里有人指出所提议的改动是条无效路径(援引先前的决定,或建议更好的路径),而作者要么关闭 PR,要么更改实现。

正因为一开始写代码很容易,关闭 PR 并不会让人觉得是损失。关键在于要在讨论线程的早期就转向,而不是等到最后才对已定稿的 PR 提出阻塞性评论。

如何实施

这套方法的多数组成部分,你的团队已经具备。

在拉取请求之前

  • 给智能体一份它可以运行的检查清单。把这些检查列在一个简短的 CLAUDE.md 文件里
  • 设置智能体通过钩子调用格式化和 lint 工具
  • 在本地运行 /code-review 和 /security-review,或让一个全新上下文的子智能体审查改动
  • 创建一个 REVIEW.md,用它来跟踪反复出现的审查问题

在每个拉取请求上

  • 强制执行 CI
  • 更新你的 PR 模板,加入“测试”、“风险”和“如何回滚”
  • 在一个仓库上添加 AI 审查者(Code Review for Claude Code,或 Claude Code GitHub Action),并迭代你的 REVIEW.md 几周,然后再推广到更多仓库。
  • 添加自动化安全审查
  • 要求所有审查线程在合并前都已解决
  • 根据风险施加相应的审查严格度:常规改动快速过一遍,高风险区域(出 bug 代价高昂)则彻底审查。Code Review 有 Quick、Standard 和 Deep 三个级别。
  • 限制审查轮数,几轮之后,只审查新的 diff。

批准

  • 定义风险等级,风险等级依据改动而定,而非依据作者。起步从严,看到证据后再逐步放宽。
  • 为可信作者保留宽松的批准路径。
  • 机器人批准不能用于满足必需的批准。推送新提交时,撤销所有批准。
  • 指定人类作为审批人,负责审批涉及认证、计费、CI 和发布工作流以及安装程序的代码与策略。为此使用 CODEOWNERS 和分支保护。

合并与合并后

  • 设置合并队列
  • 试用功能开关
  • 先向内部用户发布
  • 分阶段逐步推出
  • 建立一步式回滚机制
  • 通过记录事故和回滚来构建一套评估集
  • 每当更改审查器的提示词、工具或模型时,重新对其评分
  • 跟踪人力投入,以回滚率和事故率作为护栏

未来方向

Code Review 发布时的设想是,最终审批决定仍由人类做出。该产品所做的只是发布其发现。但在我们自己的代码仓库中,我们走得更远:负责人正在决定哪些低风险变更可以在自动检查通过后自动合并。而这个边界还将继续演变。

因此,每当我们的审查器漏掉某些问题时,我们都会问自己:模型的上下文中缺少了什么,并将其转化为 CLAUDE.md 文件、一项技能或一项新检查。我们并未改变目标:减少每次变更所需的人力投入,同时保持回滚率不高于当前水平。

因此,如果你也允许智能体创建拉取请求,请告诉我们你自动化了哪些检查、哪些需要人工介入,以及我们哪里做错了。

本文是对我们原始 文章 的重写,并已通过 Pangram 检查。

已读完 · 本文由熊猫易读翻译重排