工程 · Engineering

Claude Code 团队如何做代码审查

收录 Addy Osmani、Morgan Westlee Lunt、Qing Wang 14 分钟阅读

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

这篇文章讲的是 Claude Code 团队的代码审查流程,以及它是如何演进的。从 6 月到 9 月,合并进 Claude Code 代码库的 pull request 数量增长了一倍多,而其中不得不回滚的比例下降了超过 40%。

我们并没有取消审查。但我们把大部分「阅读」工作交给了机器,围绕人的时间和注意力做优化,并在「让错误能被快速、轻松地撤销」这件事上投入了大量精力。

重新思考代码审查的目的

当写代码的是 agent,变更数量会迅速上升。在 Anthropic 的 monorepo 中,每天落地的变更从今年 5 月的约 2,500 个增长到 9 月的 30,000 多个。

Claude Code 住在自己的仓库里,与 monorepo 分离,但它同样承受着巨大压力。以这样的速度交付时,逐行的人工代码审查即使并非不可能,也极其困难。

代码审查承担两项职责:

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

本文主要聚焦第一项,但最后我们会回到第二项。

我们认为,「有没有人类读过这段代码?」只是「这次变更可能出什么问题,以及应该由哪道检查——自动的还是人工的——来抓住它?」这个问题的一个并不精确的替代指标。

问出这个问题,也改变了审查发生的时间。以前,审查是放在最后的一道关卡:写代码、开 PR、等同事来看。

现在,审查是持续进行的,更像是你写作时的拼写检查。大多数变更在被要求人来看之前,已经至少被 Claude 审过、修过一遍。这拦住了大部分常规错误,也把团队注意力留给了更高层的问题与更细致的审视。

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

我们希望把人的时间和注意力保留给只有人才能解决的问题,并确保其余部分由自动化检查兜住。以我们当前的变更速度,人类仍有大量工作要做,团队几乎每天都会遇到新的、前所未有的挑战。

人的时间涵盖审查成本的两端:人们花在审查上的时间,以及人们花在修复烂代码造成的破坏上的时间。如果削减了前者,时间却在后者身上冒出来,那就没有意义。

因此我们的目标是:Claude Code 各仓库(CLI、桌面端与 Web 应用、移动端应用)中的绝大多数 pull request,在最多只有一个人参与的情况下完成合并——这个人可以是作者本人——同时回滚率和事故率不上升。为了衡量人的时间,我们跟踪每个 PR 的人工批准次数(中位数与 95 分位)、人工评论数量,以及一个 PR 等待某个人所花的时间。回滚率和事故率则是护栏:如果它们上升,我们就并没有节省人的时间,只是把时间挪了个位置。

一次变更的一生

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

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

左右滑动,查看完整图表 →

图 A一次典型的 Claude Code 变更,从作者的机器到用户。大部分路径是自动化的;人的注意力只去往风险所在之处。

逐层拆解

逐层设防,而非一道关卡
六层检查,最下面压着回滚

左右滑动,查看完整图表 →

图 B六层检查,最下面是回滚。每一层都能抓住一些问题、漏掉另一些。很少有人能穿过全部六层,剩下的交给回滚。

1在别人看之前,先验证工作本身

如今大多数审查发生在 PR 存在之前。我们会给 Claude 一些它可以运行的东西——比如测试、构建,或一张可与设计稿对比的截图——并要求它展示输出,而不是只说「做完了」。随后,一个 subagent 会以全新的上下文审查这份 diff。这两个习惯都来自我们的最佳实践指南。

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

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

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

2每个 pull request 上的 AI 审查

在有人看之前,每个 pull request 都会先得到一次 Claude 审查。它把 diff 放在整个仓库和这个 PR 声明的意图之下来读,并追问:这个变更是解决了它自己的问题,还是契合系统,又或者破坏了别的东西?

在 bug 修复上,它会去找根因。公开版本 Code Review for Claude Code 现已在 Team 和 Enterprise 套餐上正式可用,而在本地跑 /code-review 会得到更轻量的版本。

在我们推出研究预览版时,获得实质性审查意见的内部 PR 占比从 16% 上升到了 54%。超过 1,000 行的 PR 大多能被发现问题,而大多数小 PR 会干净通过。管理员可以为每个仓库设置为 Quick、Standard 或 Deep。在「漏掉一个 bug 的代价高于多花的审查时间」的地方,Deep 是值得的。

在人来审之前,发现的问题会先由其他 agent 验证。多个 agent 同时找 bug。随后另一组 agent 对每条发现投票,只有足够多的 agent 认为它是真的,它才会被发布出来。只涉及风格、诊断信息或清理工作的发现会被丢弃。我们在自己的 PR 上对这些过滤器做过 A/B 测试,才决定保留它们。剩下的以按严重程度排序的行内评论呈现。每条都标注为 Important、Nit 或 Pre-existing,这样不是这个 PR 引入的 bug,看起来就不会像是作者的责任。

每条发现都必须被回应。要么修掉它,要么带着理由把讨论串标记为已解决。通常会有 agent 在夜里接手修复,于是作者第二天回来时,PR 已经又过了一轮。

3能够阻止合并的安全审查

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

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

4能够阻止合并的自动化检查

就是那些常规项:lint、类型检查、各平台上的测试,以及构建。(我们写过一篇关于如何为这种体量扩展 CI 的文章,见这里。)一个典型的 CLI pull request 在有人看之前可能会跑几十项检查。PR 模板会问这个变更是怎么测的、怎么撤销。Claude 可以把它填好,但提出这个变更需求的人要对它签字确认。

5人工批准,按风险分级

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

风险示例人工批准
低文档、文案、仅内部使用的功能、开关后面的增量变更自动审查与安全检查全绿后,无需人工批准
中普通的产品变更,以及对敏感区域的变更,例如缓存、自动更新器或系统提示词一人
高风险最高的代码,以及一小批始终需要两人的路径两人

分级由变更决定,而不是由作者决定。一个分类器读取变更内容,判定它的风险层级。第二个分类器则追问:即使这个层级本不需要,这次变更是否仍需另一个人参与。有些路径永远需要人,包括所有者列为敏感的任何东西,以及任何 CI workflow 文件。

只有受信任的作者才能走更轻的路径。只有在仓库受信任名单上的作者才能走更轻的路径,而名单上主要是拥有这个仓库的团队。其他人都需要第二个人来批准。agent 的批准也永远不能算作人的批准。自动审查可以放行一个低风险变更,但它不能填补一个需要人来的位置。

谁提出的变更,谁就要为它负责。在一个由 agent 写出的 PR 上,指挥这个 agent 的人算作必需的审查者之一,就如同这些代码是他亲手敲的一样。

一次批准只覆盖一个特定的 commit。如果之后落入了系统无法担保的新 commit——比如 agent 推送的,或某人手工解决的合并——批准就必须重新给出。

6合并队列、开关与灰度发布

合并队列会在应用了该变更的基础上,对 main 重新跑所有检查,所以两个各自通过的 PR 不可能组合出一个坏掉的 main。新行为通常会先放在开关后面,先发给 Anthropic 内部员工;我们自己的重度使用,往往能在用户之前暴露出回归。公开发布先走 latest 通道,stable 通道随后跟进。

7快速发现,快速回滚

总还会有一些错误漏过去,所以最后一层是尽快抓住它们。开关可以不用部署就能关掉。Claude Code 有每周轮值的 on-call,事故复盘会追问本应由哪一层抓住这个问题。

发现一次糟糕的合并,仍然依赖作者、on-call,以及回滚和事故看板。我们目前还没有能在这些指标变动时收紧审批的熔断机制;在更多代码转向「零人工合并」之前,工程师们希望先有它。

一位同事说过,慢下来确实会降低出错率,但那主要是因为你在所有事情上都做得更少。我们更愿意在「更快地发现并修复问题」上变得更强。

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

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

任何事故现在都是一份 eval。团队收集了过去引发事故的 PR,连同它们的 fix-forward 和回滚,并请熟悉那部分代码的工程师确认每一个 bug 以及它所在的位置。审查者每天都会在这个集合上跑一遍。

事故就是我们的 eval
由历史事故和回滚构建,每天运行

左右滑动,查看完整图表 →

图 C由历史事故与回滚构建,每天运行。eval 随每次事故增长,审查者的每一次改动都会针对完整集合打分。

我们通过观察人们如何对待评论来衡量噪声。如果你不在意误报很多,想多抓 bug 是很容易的,所以我们需要对这一点加以检查。我们会按 PR 上那个人的反应给审查者留下的每条评论分类:他修了它、他说它错了、他说这个行为是有意为之,或者他认为它无关紧要。我们还会把审查者跑在一些我们已知是干净的 PR 上,统计它报告出的、会让人做出有害改动的问题有多频繁。我们写下来的优先级是:先抓真 bug,其次减少审查轮次。

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

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

当我们对审查者反复指出的内容跑 git blame 时,大约 59% 是审查者第一轮就已经存在的代码。审查者很少在发现新 bug,它只是在每一轮里多发现几个旧 bug。

这是我们自己搞出来的。我们已经习惯默认跑好几轮,因为每一轮都会抓到一点新东西。这在一次高风险变更上也许值得,但不该在每一个 PR 上都如此。团队现在把「过审轮次」和召回率放在一起跟踪,并修改了重审策略:

审查 agent 还会每晚在既有代码上跑一遍,帮我们在旧 bug 冒到别处之前抓住它们。

能够收敛的审查
收敛性改动前后的内部 pull request

左右滑动,查看完整图表 →

图 D收敛性改动之后,每个内部 pull request 的审查轮次减少了。

人类仍然留在环路中的地方

把一位资深工程师从深度工作中拉出来,只为抓一个常规错误,代价远高于一次自动审查。团队里的人仍然要对这些事情负责:

在团队中共享上下文

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

我们没有为此设立正式流程。团队里没有人脑中保留着 Claude Code 的完整心智模型,我们也认为没这个必要。当有人想理解代码库的某一部分时,他们会请 Claude 针对那一个切片写一份讲解。几分钟就能出来,而且反映的是当下的代码。

如果大部分阅读都交给机器,人们怎么学代码?
从代码库里的一个切片,到一份讲解,或者一个能教会你的小产物
配图读琐碎的 diff 学不到东西;为你要改的那个模块要一份讲解,那能教你很多。

我们中的一些人还会更进一步,让 Claude 做一个小产物,把那部分代码可视化,并按这个人喜欢的学习方式来讲解。

如果目标是把人的注意力花好,这比逐行阅读琐碎的 diff 强得多。一处一行的文案改动几乎什么都教不了你。为你即将改动的模块写一份讲解,能教你很多。我们「默认在公开 Slack 频道里工作」的文化也有帮助,因为别人在构建东西时,你能看见他们在构建什么。

「你合入,你负责」

如果你合入了一个小或中等规模的变更,而事情仍然出了问题,团队规范是通常由你来修。有几件事让它不至于变得糟糕:

责任也是共享的。团队大部分人都在公开频道里工作,任何人都能看到别人在构建什么、接手一个讨论串,或者把一个 Claude 指向某处空白;反馈也来自那些并不拥有这段代码的人。这种开放性是质量能保持在高位、而不必对每一行都设关卡的重要原因。

当我们改变方向时

agent 让开始一件事变得很便宜,所以有些工作不该落地。通常是频道里有人问一句为什么、指出一个更早的决定,或者提出一条更简单的路径,然后作者关闭或重塑这个 PR。

既然代码写得很快,关掉这个 PR 并不让人觉得是损失。诀窍在于尽早在这个讨论串里提出另一个方向,而不是把它作为一条阻塞性评论,留在一个已经完成的 PR 上。

你如何采用这套流程

和你的团队一起做这件事所需的大部分组件,今天都已经存在了。

▸在 pull request 之前

▸在每个 pull request 上

▸批准

▸合并与之后

这件事将走向何处

Code Review 的公告说,批准一个 pull request 仍然由人决定;产品发布的是发现,而不是批准。我们自己的仓库走得更远,让所有者决定哪些低风险变更可以仅凭自动化检查就合并,而这条线还在不断移动。

当一次审查漏掉了什么,我们会问:模型当时手上缺了什么?然后把修复写进 CLAUDE.md、一个 skill 或一项新检查。目标没有变:每个变更花更少的人类时间,同时回滚不比以前更多。

如果你的团队让 agent 开 pull request,我们很想听听你们自动化了哪些检查、哪些仍然需要人,以及你认为我们哪里搞错了。


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