☰
从橡皮图章到真防线:代码审查的工程实践指南
2026/9/26 1:15:19 网站建设 项目流程

1. 为什么要为一个"看起来谁都懂"的事写套方案

先讲一件让我彻底改变对代码审查看法的旧事。

三年前我们上线一个订单状态同步服务,代码评审记录里干干净净,两位 reviewer 都点了 approve。结果上线第四天,线上出现了一整批订单违约金计算错误,凌晨两点被监控电话叫醒。我打开那条已经合入的提交,问题一目了然:一个 for 循环在集合为空时直接返回了默认值 0,而业务上这时应该抛错并触发告警。这条提交通过了所有人工审查,因为所有人都默认"给别人看的代码应该没问题",而那个提交恰好看起来"好像没问题"。

排查到凌晨五点多的时候,我跟搭档说了一句至今记忆犹新的话:我们不是没有代码审查,我们是有的审查比没有更危险——它给了所有人一个"已经确认过安全"的错觉。

后来我很认真地研究了一圈业界的东西,也参考了 Google 那篇经典工程实践文档里关于 code review 的章节,再结合自己在多个团队推行的经验,把整套做法沉淀成了一个叫open-code-review的开源方案。它不是一个插件,也不是一个 SaaS 工具,而是一整套关于"怎么让代码审查在一个团队里真正生效"的开放方法论,配套了分支保护配置、Pull Request 模板、审查清单、评审人指派机制和指标采集脚本。

不管你是三五人的小团队,还是几十上百人的技术组织,这套方案里的东西基本都能直接抄走用。这篇文章我会把核心内容摊开来讲,包括我踩过的坑和从数据里看到的现象,希望能帮你的团队把代码审查从"走形式"变成"真防线"。

2. 审查流程为什么常常沦为橡皮图章,以及怎么从入口处堵住

2.1 橡皮图章审查的心理机制

很多团队不是不重视 code review,而是审查的质量长期处于"假性有效"状态。approve 按钮按下去的成本太低了,低到 review 一个 PR 的时间可能比刷一条短视频还短。但真正的问题不是成本低,而是认知上的错觉:审查者默认"作者已经自测过了",作者默认"审查者会帮我把关"。两个默认一叠加,所有提交都变成了一种无人真正负责的流程体操。

Google 的工程实践文档里提到过一个很重要的观点:代码审查的主要目的不是找 bug,而是保证代码和整个代码库处于一致、可持续维护的状态。这个定位差别非常大。如果你把审查的目标只定义成"找 bug",那审查者确实容易懈怠,因为绝大多数改动根本没有明显 bug 可找。但如果你把审查目标定义成"让每个进入主干的分支都对得起后续维护它的人",那审查的内容和标准就完全不一样了。

open-code-review的第一步,就是在流程入口处逼着所有人改变这个心理预期。

2.2 入口收紧:分支保护与提交粒度控制

一个代码库如果要长期维护,主分支应当是"受保护"的,这个没什么争议。但保护到什么程度,不同团队的差异就大了。我们当时在主分支上强制了四种必过检查:最新代码拉取、自动化测试全绿、静态检查零错误、至少两个符合条件的 reviewer 显式 approve。前三条靠 CI 就能做到制度化,最难的是最后一条。

配置分支保护本身不难,我这里直接给一份在主流 Git 托管平台上通用的规则模板:

  • 禁止直接向主分支推送代码,所有变更必须通过 Pull Request 合入
  • 要求 PR 内所有对话(comments)必须 resolve 才能合入
  • 要求至少 2 个 approve,且批准者不能是作者本人
  • 要求 CI 里的构建、测试、静态检查全部通过
  • 要求分支与主分支保持同步(防止合并时引入旧代码覆盖新逻辑)

这些规则看上去平平无奇,但它们组合起来的力量在于:它把"碰运气型审查"变成了"过五关型审查"。即使某个环节真的是敷衍的,至少还有别的环节在做事实兜底。

随后是 PR 的粒度控制。这是我觉得比分支保护更影响审查质量的一件事。一个超过一千行改动的 PR,reviewer 看完前三百行就已经开始走神了,剩下的部分基本只会看个大概。这不怪任何人,人类的工作记忆本来就没法长时间维持对陌生代码的高强度注意力。所以我们在open-code-review里定了一条硬性参考标准:一个 PR 的净改动尽量控制在 200 到 400 行之间。超过这个范围,建议拆分成多个有依赖顺序的 PR 提交。

这条规矩在推行初期阻力不小,很多人都觉得"拆分 PR 好麻烦,一次改完多痛快"。但从实际效果看,一旦 PR 变小,reviewer 的评论质量肉眼可见地上升了,不再只是"LGTM"或"有一个小问题请改一下",而是能明确说出"你在某个边界条件下漏掉了错误处理"这种具体结论。

2.3 审查人指派:轮值制与专家制的平衡

谁来做 reviewer,是一个经常被忽略但直接影响审查质量的设计。很多团队的默认做法是:谁创建的 PR 就随机或者就近拉一两个人来看。随机带来的后果是,后端的人给你审前端代码,资深的人给新人兜底,但自己真正擅长的领域没人管。

我们最后采用的是混合制:

指派方式适用场景优点缺点
模块负责人强制核心模块、基础设施变更领域专家把关,质量可靠模块负责人可能成为瓶颈
轮值审查日常业务迭代、低风险改动人人参与,知识面扩散轮值者可能不熟悉模块上下文
兴趣认领团队周知、公告类 PR参与感强无法保证有人认领

每个 PR 至少要有两个 reviewer,通常一个是熟悉这块业务的"领域人",负责逻辑正确性和边界条件;另一个是站在全局视角的"架构人",负责可维护性、命名、抽象层次和是否引入了重复代码。双人组合的初衷很朴素:一个人看树,一个人看林,总有一个会发现问题。但要注意,这必须是显式指派,而不是"大家有空就来看看"。

这套机制刚落地的时候大家有点不适应,觉得繁琐。但坚持跑了两三个迭代之后,一个直接可感知的变化是:合入主干后需要紧急修复的问题明显变少了。代码审查从来不是单点技巧的问题,它是一个系统工程,入口处的设计决定了后面所有环节的有效性上限。

3. 可执行的审查次序:从 diff 顺序到"先理解后判断"

3.1 为什么 review 的顺序比速度重要

很多有经验的工程师在审代码时,习惯从第一个文件开始往下看。这个习惯在文件少的 PR 里没问题,但一旦 PR 动到了多个模块,线性阅读就很容易陷入"只见树木不见森林"的困境。

open-code-review里给出的建议是,按照"先高后低"的层次来读 diff:

  1. 先看 PR 描述和关联的 issue,弄清楚这个改动想解决什么问题
  2. 再看测试,理解作者期望的输入输出行为是什么
  3. 然后看接口或函数签名层面的变化,判断抽象边界是否合理
  4. 最后才深入具体实现,检查逻辑细节

这套顺序的核心逻辑是:你首先要理解"这个改动为什么存在",然后才能判断"它做得对不对"。如果一上来就钻进实现细节,很容易被局部技巧吸引注意力,忽略掉整体设计上的问题。

有一个真实案例很说明问题。一位同事的 PR 改造了内部的消息队列消费逻辑,把原先同步确认改成异步确认,目的是提高吞吐。如果按文件顺序往下看,会先看到消息处理函数里的重试代码,写得确实漂亮。但当时一个 reviewer 先看了测试,发现测试里的异步场景根本没有覆盖"进程在确认之前崩溃"的情况,于是他回头去查实现,很快就确认了这是语义级别的漏洞,推进了修复。如果沿着文件从头读到尾,这个问题大概率会被淹没在"代码很精致"的印象里。

3.2 审查请求里强制要有的三类信息

open-code-review的 PR 模板里明确要求每个 PR 在描述区带上三个部分:改动动机、影响面、测试方法。这三类信息缺失的 PR 直接打回,不进入 review 阶段。

改动动机不是把 issue 标题复制一遍,而是要说明"为什么必须这样改"。这个要求会让作者在做变更之前先想清楚。影响面则要求作者标记出本次改动涉及哪些模块、是否有数据迁移、是否有配置变更、是否需要回滚预案。测试方法要求写明"人话"层面的验证,比如"本地起了一个三个节点的集群,验证了消息不丢失"。

这三类信息的强制化有两个作用。第一,它逼着作者在点击创建 PR 之前先自我检查一遍,很多低级问题在这一步就被拦截了。第二,它大幅降低了 reviewer 的阅读理解成本,reviewer 不需要在代码里猜作者的意图,脑子里省下的认知资源全部可以用在真正的质量判断上。

我见过不少团队在推行这项模板时担心"增加作者的负担",但从实测看,认真填这三段的 PR,整体返工次数反而更少,因为作者在填模板时暴露出来的思路偏差,往往比 reviewer 看完代码提的两三条评论更早地纠正了方向。

3.3 自查阶段:作者提交前先把 diff 读一遍

有一件事几乎不需要任何成本,却经常被忽略,那就是作者在提交 PR 之前,先以 reviewer 的心态把自己生成的 diff 从头到尾读一遍。

这个习惯第一次是我在一个开源项目贡献代码时被维护者要求的。当时我在提交前读了一遍自己的 diff,至少发现三处问题:一处是忘记删掉的调试日志,一处是复制粘贴出来的重复代码块,还有一处是命名完全语义不符的临时变量。这些东西如果直接发出去,reviewer 需要在评论里逐一提,浪费双方时间。

open-code-review在这方面给出的要求很具体:作者提交 PR 前,自己对变更做一次"冷读"(像第一次看陌生代码一样),至少检查注释是否有误导、调试代码是否残留、是否有比当前实现更简单的写法三个点。这十几分钟的自查投入,换来的是 PR 被 reviewer 秒批的概率大幅度提升。

4. 审查清单:把经验变成可验证的条目

4.1 为什么清单比天赋可靠

很多优秀工程师的审查能力来自常年积累的经验,但经验的问题是它不可被复制,也不容易被量化。一个团队里如果只有一个具备"毒辣眼光"的资深大佬,那大佬请假的时候,审查质量就会显著下滑。

open-code-review的做法是:把经验拆解成清单,让普通工程师也能按图索骥地完成高质量的审查。这个思路其实是从航空业借鉴来的,飞行员的 checklist 不是一个智商测试,而是确保在高认知负荷下仍然不遗漏关键项的行为约束。

我们沉淀的审查清单总共分五个维度,每个维度下有几条关键的验证行为。在open-code-review仓库里,这份清单被维护成 Markdown 文件,且允许团队按自己的项目类型增删条目。

4.2 五个核心维度互相之间的关系

程序正确性是最基础的维度,但它的覆盖面非常广,远不止"逻辑对不对"。实际审查时至少要覆盖:

  • 数据为空、集合为空、字符串为空时,代码是否做了符合业务语义的处理
  • 并发场景下,是否有竞态条件或死锁隐患
  • 重复调用是否具备幂等性
  • 超时和错误路径发生时,是否有显式的失败信号而不是静默吞掉
  • 是否有防御性代码存在但逻辑错误,比如if (a && b)写成if (a || b)

代码可维护性审查关注的是抽象层次和依赖方向。一个长期可维护的代码库,应该是高层策略依赖底层接口,而不是反过来。审查者要问的是:这个改动是否让模块之间的依赖关系变得更乱?是否出现了为了省事而直接在业务层调用底层存储类的情况?是否引入了一个看起来能解决当前问题但会限制未来扩展的设计?

可测试性也是一个独立维度。代码在提交前是否配套了该有的单元测试?测试是验证了真实行为还是只是让覆盖率数字好看?很多项目的测试越写越"表演化",只覆盖主流程、不覆盖异常分支,本质上是因为作者觉得"有测试这个动作比较体面",而不是真的想用测试守住工程质量。对这个现象,我们会在清单里明确要求 reviewer 检查测试断言是否有效,比如一个测试是否真的会失败——如果一个测试去掉断言仍然能通过,那它就不该存在。

性能与安全维度,容易被非专业领域的人忽略,比如N+1查询问题、循环内的耗时操作、拼接 SQL 的入口是否经过参数化、敏感信息有没有被打印到日志里。这一类问题单靠 reviewer 的领域经验很难全覆盖,所以我们在清单里以"高频问题集"的形式沉淀了常见的性能和安全反模式。

4.3 一份可以直接抄走的清单模板

下面这份清单是从open-code-review里摘出来的精简版,5 个维度,每维度 6 到 8 项,适合大多数业务后端项目:

维度关键检查项
正确性边界条件处理、异常路径、并发安全、幂等性、日志是否输出有效上下文
可维护性函数是否有单一职责、命名是否表意、是否存在复制粘贴的重复逻辑、依赖方向是否合理
可测试性是否有对应的测试、测试是否覆盖关键分支、断言是否有效、测试命名是否描述了期望行为
性能与安全是否出现循环内 IO、是否存在 N+1 查询、敏感信息是否被记录、外部输入是否有校验
兼容性与迁移数据库迁移是否可回滚、配置变更是否向后兼容、有无破坏 API 契约、有无灰度开关

这份清单不需要每次审查都全部硬过一遍。对于超低风险改动,比如改一个文案、加一个前端字段,reviewer 可以只跑其中两三个维度。但清单的存在价值在于,当一个改动碰触了敏感模块时,reviewer 可以根据清单逐项核验,而不是凭感觉给"看起来差不多可以"的结论。

我个人的经验是,把这份清单打印出来贴在显示器旁边,连续坚持两个月,审查时的脑补环节会大幅减少,评论质量会明显提升,因为人一旦知道自己要验证什么,就不会只用"感觉"下判断。

5. 分歧处理与审查数据:让流程活下来的关键机制

5.1 技术分歧的本质和收敛方式

代码审查永远绕不开人,而人一多,分歧就不可避免。很多团队最后的式微,不是死于没有流程,而是死于分歧处理不当:要么权威压制导致新人不敢说话,要么为了和气什么都放行。

open-code-review里收敛分歧的原则只有三条:

  1. 正确性问题用事实说话
  2. 风格偏好引用团队规范
  3. 双方争执超过 15 分钟,拉第三个人或升级讨论

先解释第一条。如果一个 reviewer 说"这里会抛异常",而作者说"不会抛异常",那这个问题根本不是辩论出来的,是用代码事实来验证的。可以要求作者补一个测试来证明当前行为,或者 reviewer 直接跑一下复现场景。把分歧落到可验证的实验上,是效率最高的解决方式。

第二条针对的是那些没有对错之分的风格问题。代码里具体是函数式写法还是命令式写法,新的对象是配置注入还是直接 new,本质上没有绝对正确答案。这时唯一的依据是团队已经约定的规范文档。没有规范怎么办?那就把分歧作为一条新规范提到团队例会讨论,而不是当当场攻讦。

第三条是最容易被忽视的。两个资深工程师对同一个抽象方案各有坚持,谁都觉得自己才是对的,这种争执持续半小时以上时,继续争下去只会消耗双方精力。此时应该由初始审查人引入一个项目组外但对架构有判断力的人,或者把两种方案分别做成小原型,用代码量、后续扩展成本等硬指标来定结论。

回顾我这边多年碰到的历史分歧,几乎每一个伤感情的案例,都是缘于把风格偏好问题无限上升为正确性问题,或者把正确性问题无限搁置为风格问题,两个方向都不健康。

5.2 采集什么指标,以及怎么避开 KPI 陷阱

把审查跑起来之后,下一步会自然产生的需求是:如何衡量这套机制到底有没有用?指标一定要采集,但要小心,指标一旦变成 KPI,就会立刻失真。

我们实际记录并且长期跟踪的指标有四个:

  • 审查耗时:一个 PR 从创建到合入经过多长时间
  • 评论密度:每 100 行有效评论数量(去除"LGTM"、"空行"这类无内容评论)
  • 平均修改轮次:一个 PR 从提交到合入之间,作者收到几轮修改意见
  • 缺陷逃逸率:合入后线上发现的问题,回溯时有多少是曾经被 review 过的提交引入的

这四个指标里,前三个用来衡量过程的健康度,最后一个用来衡量结果的收口程度。它们之间的联系值得反复观察:如果评论密度高但修改轮次也高,说明过程中抓出不少问题,这是正常的;如果评论密度低但缺陷逃逸率高,说明 reviewers 大概率在走过场,需要警惕。

我特别建议不要做的一件事是:把评论数量直接挂钩绩效。很早我试过一次,把每个人当月的 comment 数作为积极性的参考指标,结果不出一个月就开始有人在不该发言的场合也强行发言,为了评论而评论,评论区被垃圾信息淹没。复盘之后,我们立刻取消了这一指标,只把它作为一种团队内部匿名可见的参考数据,效果反而回到正常。

5.3 用线性总结让 flow 持续迭代

open-code-review最后收口在这个机制上:每季度对 review 数据进行一次小的复盘,把线上缺陷样本回填到审查清单里,作为下季度的新增检查项。

比如上面提到的线上问题如果发生在你们团队,季度复盘时就会在正确性清单里新增一条:空集合或空结果集时,要有显式的错误信号而非默认值。下一个季度的 review,所有人都会被这条新规则约束住。如此反复迭代下去,清单会越来越贴近这个团队真实踩过的坑。

有一个隐含的好处是,新人对这套流程的适应成本很低。他不需要在入职前就拥有很多年经验,只要老老实实按照清单过完,就能做到大部分人做不到的仔细程度。审查经验从此从个人天赋变成了组织能力,"技术最强的人审得最准"不再是团队的脆弱依赖点。

用一句话总结我这几年围绕 open-code-review 最大的收获:代码审查要解决的核心问题不是"找 bug",而是"让团队对代码质量产生统一的、可持续的认知"。流程、清单、指标、分歧处理机制,全部服务于这个认知的建立。每个人看代码的视角天然不同,但有了这套框架,所有视角都在朝同一个方向用力,这是它和我之前见过的所有高复杂度审查工具在本质上的区别。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询