每增加一层代码审查,团队就慢一步
代码审查越多,代码未必越好。过度的审查流程如何制造瓶颈、消磨开发者热情,并反过来降低代码质量。

一家初创公司的线上版本出了个 bug,管理层的反应是:加一道代码审查。又一个 bug 漏了过去,于是要求两个人审。接着出了一次安全事故,又加了安全审查环节。然后设计风格不统一,又加了设计评审。两年之后,哪怕只改一行代码,也要经过四道审查,合并一次要三天。以前每天都能发版的开发者,现在花在审代码上的时间已经超过了写代码。
这种现象几乎成了组织行为的一条定律:每次事故都会催生一道新的审查环节,而审查环节却从来不会被删除。结果就是,审查流程拼命防范上一次事故,却把未来所有的进展都挡在了门外。
审查队列的数学
每一道审查环节的影响不只是叠加,而是倍增。假设一次审查平均要花 4 小时才能完成(这里说的不是真正审查的 20 分钟,而是 PR 在队列里等待审查人员腾出空来的时间),那么两道串行审查就要 8 小时,三道是 12 小时,四道是 16 小时。
但实际情况比这更糟,因为还有上下文切换的问题。开发者提交 PR 之后会转去做别的事情。几个小时后审查意见回来,他们得切回原来的任务,重新加载上下文,修改,再提交,然后再次等待。每轮审查除了排队时间,还要额外消耗 30 到 60 分钟的上下文切换成本。
还有连锁反应。如果审查人 A 提出修改意见,开发者改完重新提交。这时还没看过这个 PR 的审查人 B 进来审查,又提出了另外一套修改意见。开发者照着改完,A 又得重新审一遍,确认自己的意见有没有被落实,可 A 早就转去忙别的了,PR 又被丢回队列。
Timeline of a PR through a 3-reviewer process:
Day 1 9:00 — Developer submits PR
Day 1 14:00 — Reviewer A reviews, requests changes
Day 1 15:00 — Developer addresses feedback, resubmits
Day 2 10:00 — Reviewer B reviews, requests different changes
Day 2 11:00 — Developer addresses, resubmits
Day 2 16:00 — Reviewer A re-reviews, approves
Day 3 11:00 — Reviewer C reviews, approves
Day 3 11:30 — Reviewer B re-reviews, approves
Day 3 12:00 — PR merges
Elapsed time: ~3 business days
Actual review time: ~90 minutes total
Actual code change time: ~2 hours
Time waiting in queues: ~22 hours
Queue time is 80% of the total elapsed time.
质量悖论
增加审查层级的前提假设是:审查越多,代码越好。这在一定程度上确实成立,但超过某个临界点之后,效果就会反转。
一位认真负责的审查人能发现真正的问题:逻辑错误、遗漏的边界条件、安全隐患、API 设计问题。第二位审查人偶尔能补上第一位漏掉的问题,大概 10% 到 20% 的情况。第三位审查人则几乎发现不了前两位都没发现的东西。每增加一个审查人,边际收益都会急剧下降。
与此同时,合并变慢带来的质量损失真实存在,却没人去计算。长期存在的分支会逐渐偏离主干,需要 rebase,而 rebase 本身又可能引入合并错误。为了避开每个 PR 的审查开销,开发者会把更多改动攒进同一个 PR,结果 PR 越来越大,越来越难仔细审。审查人也会疲劳:当队列里躺着 15 个 PR 时,你只能扫一眼,而不是认真读。
悖论就在这里:为了提高质量而增加审查层级,反而可能降低质量,因为它催生了一些激励(更大的 PR、草率的审查、陈旧的分支),这些激励恰恰在侵蚀审查流程本身的效果。
繁重的审查流程究竟能防住什么
审查流程往往是因为某次具体事故才被加上的。「我们之所以出了 bug,是因为没人审代码。」但问「审查能不能抓住这个具体的 bug」,与问「审查要求能不能整体改善结果」,完全是两回事。
关于代码审查有效性的研究一致表明,审查大约能发现 60% 的缺陷,而且多半是表层问题,比如命名、格式和明显的逻辑错误。深层的架构缺陷、并发问题和安全漏洞很少能被代码审查抓住,因为它们需要理解整个系统,而不只是看 diff。而那些真正引发线上事故的 bug,恰恰很大比例属于审查难以发现的类型。
真正能防止线上事故的,是测试、监控,以及快速部署和快速回滚的能力。一个拥有良好测试、功能开关和即时回滚能力的团队,比一个设了四道审查、却没有集成测试、部署一次要等一小时的团队,线上事故要少得多。
审查的合适分寸
代码审查是有价值的。每个 PR 配一位审查人,并且明确他要审什么,这对大多数团队来说是最合适的做法。实践中可以这样做。
- 只配一位审查人,而不是两个或三个。 第一位审查人就能发现代码审查能发现的问题的 80%。第二位带来的边际价值有限,代价却不小。多人审查只保留给真正高风险的改动,比如数据库迁移、认证相关改动、公开 API 修改。
- 给审查队列设时限。 如果一个 PR 4 小时内还没人审,那是流程出了问题,不是审查人懒。团队需要优先安排审查人力,或者承认自己的开发人员已经多到审查流程撑不住了。
- PR 要小,不要大。 一个 50 行的 PR 十分钟就能仔细审完;一个 500 行的 PR 即使花 30 分钟,也只能草草过一遍。尽管审查时间更少,小 PR 反而得到更好的审查。把 PR 大小限制在 200 到 300 行以内,比增加审查人更能提升审查质量。
- 低风险改动可以跳过审查。 配置变更、文案更新、依赖升级、补充测试,这些不需要和业务逻辑改动一样严格的审视。定义一个「低风险」类别,允许自行合并,合并后再补审。
- 让机器做机器擅长的事。 Lint、格式化、类型检查、测试覆盖率,这些审查任务交给机器比人工更快、更稳定。不要把审查人的注意力浪费在 CI 检查就能搞定的事情上。
文化问题
减少审查层级很难,因为这感觉像是在减少安全感。没有人愿意成为那个在线上事故前夕还在主张少审一点的人。这更多是组织文化问题,而不是技术问题。
有一种说法会有帮助:审查只是众多安全机制之一,而且边际收益递减。为了防止 bug 而加第四位审查人,就像为了防盗而加第四把挂锁。第一把锁已经承担了大部分作用,之后每多一把,只是增加麻烦,安全性却不再明显提升。你不会装四把挂锁,那就也别加四位审查人。
那些既能快速交付、又很少出问题的团队,通常会把精力投在真正能防止事故的机制上:完善的自动化测试、用于灰度发布的功能开关、带告警的可靠监控、一键回滚,以及一种无责文化,把事故当作学习的机会,而不是追责的对象。这些投入会随时间不断复利,而审查层级却不会。
移除一个审查层级
如果你的团队积累了太多审查要求,下面是在不引起恐慌的前提下削减它们的办法。
先衡量当前的流程。一个 PR 从提交到合并要多久?其中排队时间和实际审查时间各占多少?任何时候审查队列里有多少个 PR?这些数字会让成本变得可见,大多数团队看到自己的 PR 平均要 3 天才能合并时,都会大吃一惊。
然后做个实验。连续一个月,只要求一位审查人,而不是两位,并追踪同样的指标。事故率有没有变化?代码质量(用缺陷率衡量,而不是凭感觉)有没有变化?几乎总是这样:事故没有增加,质量保持不变,而交付吞吐量明显提升。
目标不是零审查,而是在保证质量的前提下,用最少的审查换取最大的吞吐量。这个「最少」几乎总是比团队目前的做法要少,因为审查层级是通过事故响应一层层累积起来的,却从来没有通过流程优化被删减过。正如打造优秀软件的大多数事情一样,答案不是更多流程,而是合适的流程。


