1. 项目概述:代码审查为什么值得当成正经事来做
先说一个真实场景。某个周末晚上,我收到线上告警,用户下单后库存被多扣了一次。查到最后,根因是一段在 Merge Request 里被“简化”掉的事务边界。那段代码上星期过审查时,审查者只回了一句“lgtm”,因为改动看起来只有两行,就没有深究。两行代码,差点把整个库存系统带崩。
那次以后,我开始认真复盘团队的 Code Review 到底出了什么问题。结论很扎心:不是大家不认真,而是整个流程没有一个可依赖的框架。审查靠个人自觉,标准靠口头约定,工具链只是把 MR 创建出来,剩下的全靠缘分。后来我花了不少时间整理了一套相对完整的做法,并且把它做成了一个叫 open-code-review 的开源项目。
open-code-review 不是某个特定的插件或者某个平台的功能,它是一套“开放式代码审查”的实践方案。核心想解决三个问题:第一,审查标准不统一,同一个问题这个人提那个不提;第二,审查过程不可控,小改动没人看,大改动没人敢看;第三,审查经验不沉淀,每次审查都是从头开始。它适合正在搭建或者想优化 Code Review 流程的团队,也适合个人开发者想系统提升 review 能力。
我对这套东西的定义很简单:Code Review 不是找茬,而是大家一起把代码的上下文补完整。这句话听起来很虚,但把它拆成流程、清单、工具和度量之后,它是完全可以落地的。下面把每一个环节都展开讲。
2. 整体设计与思路拆解:从“查错”到“质量共建”
2.1 传统审查为什么容易失效
大多数团队刚开始做 Code Review 的时候,都是靠一群人围着一个 MR 看,凭感觉提意见。这种做法有几个天然的漏洞。
第一,审查者容易陷入“逐行找错”的误区。人的注意力是有限的,面对一个 800 行的 Diff,前 200 行还能集中精神,到 400 行之后基本就是在扫字符。结果是重点逻辑没人看,注释风格被反复揪着说。
第二,作者和审查者之间缺少共同的上下文。写代码的人脑子里有一整套背景:这个函数为什么存在、这个分支为什么这么处理、底层依赖有哪些约束。但审查者看到的只是一堆代码,没有这些上下文。于是审查变成了“猜谜”,猜不到就随便提点意见。
第三,没有优先级。一个 MR 里面可能同时存在线上隐患、逻辑漏洞、风格问题和命名争议。如果所有意见都被一视同仁地提出来,作者会陷入疲于应付的状态,真正的关键问题反而被淹没。
open-code-review 的设计思路,就是针对这些漏洞逐一打补丁。它不追求让审查变得更“严格”,而是让审查变得更“聚焦”。核心做法是把审查拆成四个层次:结构层、逻辑层、资源层、表达层。每一层有各自的审查重点和检查方法。
2.2 开放式审查的四层模型
结构层看的是代码怎么组织。一个 MR 是不是拆分得太大了?函数是不是做了太多事情?模块之间的依赖是不是合理?这一层解决的问题是“代码以后好不好改”。
逻辑层看的是核心业务逻辑。边界条件有没有覆盖?异常路径有没有处理?状态流转是不是完整?这一层解决的是“这段代码跑起来对不对”。
资源层看的是内存、连接、线程、存储这些容易被忽略的东西。连接池有没有归还?事务范围是不是正确?缓存有没有潜在的失效风暴?这一层解决的是“项目在压力下会不会塌”。
表达层看的是命名、注释、可读性。变量名是不是能看懂?注释解释的是“为什么”还是“是什么”?这一层解决的问题是“两个月后别人能不能维护”。
为什么要按这个顺序?因为结构问题影响最大,表达问题影响最小。审查的时候资源有限,应该把最贵的注意力放在最关键的位置上。实操中我也是要求团队按照从结构层到表达层的顺序过一遍,而不是看到哪算哪。
2.3 为什么强调“开放式”
名字里的 open 有两层意思。
一层是指过程开放:审查者不限于固定的几个人,任何对这块代码有上下文的人都可以参与。我见过不少团队把 review 权限锁在几个人手里,结果这几个人成了瓶颈。开放之后,新人可以看、隔壁组可以看、甚至 QA 也可以看,每个人关注的点不一样,反而更容易发现盲区。
另一层指的是标准开放:审查条目、检查清单、合并条件全部仓库化、文档化,而不是存在某个元老脑子里。新人进组第一天就能看到“我们是怎么审查的”,不用靠猜。这也是 open-code-review 项目本身做成开源的原因——标准越开放,执行越统一。
2.4 工具链定位与选型原则
这个方案本身不绑定特定平台,GitLab 的 MR、GitHub 的 PR、Gitea 的 Pull Request 都能跑。选型时我建议遵循三个原则:
- 能用平台原生能力就不额外引入工具,减少维护成本。
- 自动化先拦截客观问题,人工审查专注主观判断。
- 所有规则必须代码化、配置化,可以版本管理。
在这个前提下,实际搭建用的就是托管平台自带的审查流程、CI 里的静态检查、以及一组自定义的检查脚本。没有引入重量级的外部系统,降低项目门槛。
3. 核心细节解析与实操要点
3.1 审查清单长什么样
open-code-review 项目里最核心的资产是一份审查清单。这份清单不是从网上抄来的,而是从实际事故和 review 意见里反推整理出来的。这里直接分享我最常用的一组检查项,覆盖多数后端服务和前端项目。
| 类别 | 检查项 | 提问方式 |
|---|---|---|
| 结构 | MR 是否过大 | 这个 MR 能讲清楚一个完整的故事吗? |
| 结构 | 抽象边界是否合理 | 这个逻辑放在这里,以后改起来方便吗? |
| 逻辑 | 边界条件是否覆盖 | 空集合、null、超大输入会发生什么? |
| 逻辑 | 异常路径是否完整 | 失败时是走默认值还是抛错?调用方知道吗? |
| 资源 | 事务与锁范围 | 事务里有没有包含网络调用?锁的粒度合适吗? |
| 资源 | 连接与线程回收 | 异常分支下资源会泄漏吗? |
| 表达 | 命名是否真实 | 名字描述的是实现细节还是业务语义? |
| 表达 | 注释价值 | 注释解释了为什么,还是只是复述了代码? |
实际操作时,我不要求每次 review 都把整张表跑一遍。而是要求作者在 MR 描述里先自评,把每一项过一遍,写清楚结论。审查者的任务就是检查作者的自评是否成立。这个“先自评再互审”的流程,比直接丢一份清单给审查者有效得多。
3.2 小步提交:Diff 的合理规模
这是整个方案里最难执行、但效果最明显的一条:让 MR 保持在“300 行以内”的规模。超过 500 行的 Diff,审查质量会肉眼可见地下降。
为什么是 300 行?不是拍脑袋定的。有团队做过内部统计,当 Diff 行数在 200 到 300 之间时,审查者平均能覆盖到核心路径;超过 500 行之后,核心逻辑的评论显著减少,而风格类评论开始增加。原因很直接,工作记忆是有限的,看 1000 行的代码变化,脑子根本装不下。
要控制 Diff 规模,不是靠作者自觉,而是靠拆任务的方式。如果一个迭代需要改 1000 行,那就把它拆成 3 个 MR:第一个做数据模型,第二个做核心逻辑,第三个做接入和收尾。每个 MR 都能独立编译通过,独立合入,不会影响主干稳定性。
我在团队里还执行了一条硬规矩:一个 MR 只解决一个问题。如果一个 MR 既修 bug 又重构又改样式,就算每部分都没问题,也要拆开。理由很简单:一旦出了问题,回滚一个 MR 会牵扯到不相关的改动,排查成本会成倍上升。
3.3 审查者怎么选、怎么轮换
审查者是由作者自己选择,还是由管理者指派?这两种都试过,都有问题。作者自己选容易选熟人,审查质量不行;管理者指派容易变成“分配任务”,积极性不高。
现在用的方式是“责任矩阵 + 轮值制度”。每个模块在代码仓库里维护一个 OWNERS 文件,列出对该模块最有上下文的人。创建 MR 时,平台会自动从模块负责人里推荐审查者。同时,每个迭代安排一名“轮值审查者”,他的职责是兜底——如果模块负责人没有在约定时间内响应,轮值审查者要接手。
这个制度的好处是,既保证了审查的专业性,又不会让某个模块负责人被无限期卡住。还顺带解决了知识共享的问题,因为轮值审查者每次看的都是自己不熟悉的模块,被迫去读别人的代码,跨模块的熟悉度会慢慢建立起来。
3.4 评论规范:如何让意见被听进去
再好的 checklist,如果评论写得让人不舒服,效果也会大打折扣。我踩过不少坑,最典型的是以下两种失败评论。
第一种是“审判式”评论,比如“这里写错了”“你这个思路不对”。这种评论没有任何上下文,作者收到之后除了防御心理,什么也收获不到。
第二种是“代笔式”评论,比如“把这段改成 XXX 的实现”。直接给方案看起来很高效,但作者没有参与思考,下次遇到类似问题还是不会判断。
现在团队约定评论规范是“先给上下文,再给问题,最后给建议”。例如不会说“这里写错了”,而是说“如果库存被并发扣减,这段代码会出现负数,我担心这里的事务范围不够。要不要考虑对库存表加行锁或者引入乐观锁?”这样既指出了问题,也给出了方向,还留了讨论空间。
另外一条非常实用的约定:评论要区分“必须改”和“建议改”。现在的做法是用两个固定的前缀,[block]表示必须解决才能合入,[nit]表示小事、可选。这个约定看起来简单,但能极大地降低作者的焦虑,也避免了“每条评论都像拦路虎”的问题。
4. 实操过程与核心环节实现:完整搭建一套工作流
4.1 第一步:在托管平台上配置保护分支
先做基础工程。在代码托管平台上把主干分支设为受保护分支,要求所有合入必须经过 MR、至少一个审查者批准、CI 通过。
具体配置项因平台而异,但核心逻辑是一样的。拿 GitLab 来说,在项目的 Settings -> Repository -> Protected Branches 里将主干分支设为Allowed to merge仅限 Maintainer,Allowed to push设为“无人”。这样所有改动都必须走 MR 流程,直接推主干就会报错。
然后是 MR 审批规则。GitLab 的 Rules 里可以设置“需要至少一个批准”,这里我建议设置成两个:一个来自模块负责人,一个来自轮值审查者。如果团队规模小,至少也要保证“作者不能给自己批准”。
4.2 第二步:接入静态检查和格式统一
这一步的目标是把客观问题全部自动化,让人工审查专注于逻辑和设计。
前端项目典型的一套配置是 ESLint 做代码规范、Prettier 做格式统一、TypeScript 严格模式做类型检查。后端项目根据语言不同,对应的是各类 linter 和静态分析工具。工具不是重点,重点是把规则配置文件放进仓库,并用 CI 强制执行。
这里要特别强调一点:CI 检查失败必须能阻止合并,而且是要硬阻断。如果你的 CI 只是出个警告,那大家慢慢就会忽略它。我们在 MR 的合并条件里明确写了,CI 的所有 job 必须通过,否则无法点击合并按钮。
实际配置时,我还会加一个“无秘密扫描”的 job。用简单的正则匹配扫描代码里有没有类似password = "xxx"、api_key = "sk-..."这样的硬编码。这个 job 曾经在一次 MR 里真的拦下了一组写死的数据库连接串,帮团队避免了一次严重的泄露事故。
4.3 第三步:编写审查清单模板与 MR 描述模板
这一步是 open-code-review 的灵魂所在。在仓库根目录建一个.github或者.gitlab目录,放一套 MR 描述模板,强制作者在描述里回答几个问题。
一个可用的模板结构如下:
- 这个 MR 解决什么问题?预期行为是什么?
- 改动涉及的模块与影响范围?
- 自评清单:重点边界、异常路径、事务/资源是否处理?
- 测试记录:本地跑了哪些用例?有没有新增用例?
- 上线后需要关注哪些指标或日志?
别小看这个模板。它的作用不是增加作者工作量,而是逼着作者在提交之前自己先过一遍“审查视角”。事实上,很多问题在作者写 MR 描述的时候就已经被消灭掉了。如果作者发现自己写不清楚这个 MR 解决什么问题,那就说明这个 MR 本身可能有问题。
4.4 第四步:给 CI 加自定义检查器
除了现成的静态检查工具,open-code-review 项目里还带了一组自定义检查脚本,用 Python 和 Node.js 写的,核心检查以下几件事。
一个是“TODO 强制带责任人”。团队曾经出现过有人写TODO: fix this然后就再也没有后续的情况。现在的检查规则是:如果代码里出现 TODO,后面必须跟一个 @用户名 和日期,否则 CI 直接报错。这招很粗暴,但非常有效,因为每个 TODO 都有了明确的归属和时限。
另一个是“错误处理兜底检查”。这个实现起来没法做到 100% 准确,但可以做一个启发式检查:凡是调用外部服务的返回结果,要求不能静默吞掉错误。脚本会搜索类似except: pass或者空 catch 块,如果检测到了就提示“疑似吞掉异常”。虽然会有一些误报,但误报比漏报好,人工审查时顺手确认一下就行。
4.5 第五步:设置合并条件与自动清理
最后一步是把合并规则收口。以 GitLab 为例,在 MR 的合并条件里要求必须满足:CI 全部通过、至少一个批准、无 unresolved thread。另一个很有用的设置是“自动合入前更新分支”,避免目标分支有新提交时产生不必要的冲突。
公开项目还要考虑一个问题:外部贡献者的权限。开放审查不代表放权,我建议保留一个关于“外来 MR 必须由维护者复核并亲自执行合并”的规范,这样既保证开放性,也守住质量底线。
5. 常见问题与排查技巧实录
5.1 审查拖了三天没人理
这是最伤团队士气的问题。分析下来,无外乎三种原因:审查者没有收到通知、审查者没有时间、审查者觉得这 MR 跟自己无关。
排查思路是,先在平台里确认通知配置,MR 创建时是否正确 @ 到了模块负责人。如果通知正常但响应还是慢,那就说明是流程设计的问题——没有明确的 SLA。open-code-review 给出的方案是“SLA + 升级机制”:普通 MR 要求 24 小时内响应,超过时限自动提醒;大项 MR 要求 48 小时内响应;轮值审查者在 12 小时内没有动作就自动转发给模块负责人。
执行这个机制之后,团队里 MR 的平均响应时间从 4.6 天降到了 1.2 天。关键在于“自动升级”不是纸面规定,而是真正在 CI 的定时任务脚本里实现的,不需要人肉盯。
5.2 审查过了、合并了、线上炸了
这是最让人沮丧的情况。明明走了全套流程,结果还是出了事故,事后大家就容易说“审查就是个形式”。但实际上大多数这类情况,问题不在流程本身,而在流程没有被完整执行。
复盘过几次之后,我发现高频根因就那么几个:第一,审查者只看了 Diff 没有看上下文文件,忽略了某个调用方的假设;第二,测试覆盖不够,CI 虽然过了,但关键路径根本没有用例;第三,改动牵涉了外部系统的行为变化,这些变化无法在代码评审中看出来。
应对措施有三条。一是在 MR 模板里增加“影响面分析”段落,作者必须自己列出这个改动会影响哪些调用方和功能;二是 CI 里增加覆盖率门槛,核心模块低于 80% 不允许合并;三是把上线计划写进 MR 描述,甚至要求对应迁移脚本和回滚方案一并提交。这样即使后续出问题,回滚决策也能做得很快。
5.3 自动化检查噪音太多,团队开始无视警告
引入自动化工具之后,经常遇到的反而是“报警疲劳”。一打开 MR,满屏的 warning,但没几个能真正帮助发现问题的。结果就是开发者在潜意识里把所有检查都当成摆设,真正的错误也被忽略了。
这个问题在引入一套新检查器的时候尤其常见。排查的方法是统计每个规则的历史命中率和误报率。实践下来,最有效的办法是把规则分层:error 级规则必须硬阻断,warning 级只做提示,info 级直接屏蔽。新规则先以 warning 形式运行一周,收集误报反馈,达到预期之后再升级为 error。
我建议每个季度做一次规则评审,把连续三个月没有触发过的规则降级或删掉,把频繁误报的规则修正。审查流程本身也要持续维护,它跟代码一样会腐化。
5.4 新人不知道怎么参与审查
团队来了新人,第一批 MR 往往是最难熬的。他们不敢评论,不知道说什么,担心说错了被嫌。或者反过来,新人评论特别多,但大部分是风格类建议,淹没了关键问题。
针对第一个情况,我推荐“陪看式”机制:第一个月新人不独立承担审查任务,而是跟一位老手一起看 MR,老手负责指出自己看哪里、看什么、为什么。一个月之后再独立审查,但范围先限制在表达层和部分结构层。
针对第二种情况,则要靠评论规范来解决。上面提的[block]和[nit]前缀实际上就是在给新人的评论做层级引导。当新人发现几乎所有评论都是[nit]级别的时候,他自己就会意识到“我还没有抓到关键问题”,然后主动去学习更深层的路径。
5.5 一条实用的排查小技巧
最后分享一个排查审查质量时的“复查抽样法”。不用每个 MR 都复查,而是每个月随机抽取 5 个已合并的 MR,由项目里最资深的人重新审一遍。抽样发现的问题不追责,但要做归类记录。如果连续三个月都在同一类问题上翻车,那就说明现有流程对这类问题的拦截能力不行,应该把对应的检查项加进清单或者自动化脚本里。
这个做法的核心价值是“给流程本身做 Code Review”。团队里面的代码有人审,那审查流程也应该有人审。没有这个反馈环,流程优化就无从谈起。
6. 更深一层的经验:把审查流程变成团队资产
6.1 哪些指标值得看
度量代码审查质量一直很有争议。我不建议拿“审查者找到了几个 bug”这种数字来考核,因为 bug 发现率跟模块复杂度、业务阶段、历史包袱的关系太大,数字本身没有可比性。
我更关注过程指标和反馈指标。过程指标包括平均首次响应时间、平均审查通过时长、MR 的平均 Diff 行数、自动化检查的阻断率。反馈指标是团队定期做的不记名问卷,直接问三个问题:你觉得审查对你有帮助吗?审查中浪费最多时间的是什么?你希望流程里增加什么?
6.2 从审查记录里沉淀知识库
代码审查会产生大量高质量的讨论,但默认情况下这些讨论都散落在各种 MR 下面,时间一长就石沉大海。open-code-review 方案里面有一项“裁决案例归档”:每个迭代收集 5 条最有价值的审查评论,把上下文、问题、结论和后续动作整理成“一案一页”,放到团队的知识库里。
这个动作坚持半年之后,知识库里会积累几十个真实案例。它们比任何代码规范文档都更能指导新人。遇到拿不准的问题,优先在案例库里检索,往往能找到类似场景。这种“从真实争议中沉淀经验”的模式,效果远好过定期培训讲 PPT。
6.3 后续还能怎么扩展
open-code-review 的下一步方向是接进更多自动化能力。我目前已经在尝试把“代码语义层面的智能检查”接入流程,让机器人能自动标记出疑似空指针、疑似并发问题的位置,给人工审查做预定位。但我不建议完全依赖智能工具,它最大的价值是让人工审查更有侧重,而不是替代人工判断。
对于已经在稳定运行这套流程的团队,还有一个值得做的扩展:把审查标准推广到非代码资产,DDL 脚本、配置文件、部署清单都应该走同样的审查流程。很多线上事故并不是代码写错,而是配置变更没人审。把 open-code-review 的清单和规则复用到这些场景上,等于给整个发布链条补上了最后一块短板。
写在最后:一点个人体会
从第一次被两行代码坑到线上事故算起,我做 Code Review 流程折腾了差不多一年半。回头去看,最有价值的不是那套清单或者脚本,而是整个团队对“审查”这件事的认知发生了变化。
现在团队里最常听到的一句话是“你帮我看看这个 MR 有没有我没想到的上下文”,而不是“你帮我检查一下这段代码对了没有”。两者的差别,其实就是代码审查从“防守”变成“共建”的区别。open-code-review 这个项目,本质上是把这种转变固化成了可以复制的标准动作。
如果你打算在自己的团队里推进这件事,我的建议是从最小闭环开始:先加一个 MR 描述模板,再加一个硬性保护分支,最后把 Diff 规模控制在 300 行以内。这三件事做完,审查质量已经会明显改善。剩下的规则、清单、自动化,都可以在这个基础上慢慢长出来。