在团队里一提 code review,一般都会出现两种极端:一种是把评审当走过场,看一眼标题点个 approve,等到上线出问题才发现代码根本没人真正看过;另一种是评审卡成瓶颈,一个 MR 挂了三天没人理,催一次动一次,最后大家为了赶进度干脆绕开流程直接合代码。我做了十几年开发,也带过技术团队,自己维护了一套叫 open-code-review 的项目,本质上就是一套把代码审查流程透明化、自动化、可持续化的实践配置集。这篇文章会把我在落地这套流程过程中的设计思路、工具选型、实际参数配置、模板脚本、以及踩过的坑全部梳理出来。不管你是研发效能工程师、技术负责人,还是想改善自己评审体验的普通开发,都可以直接拿来参考。
1. 为什么团队需要一套更开放的代码审查机制
1.1 先说说 code review 最常见的三种病
第一种是“形式主义病”。团队有强制评审规则,但评审者只看 diff 标题或者干脆不打开,主流程走完就 approve。这种情况在自动化检查不足的团队特别常见,因为人心里清楚:就算自己不认真看,也没有任何机制会发现。更可怕的是,这种走过场会形成习惯,最后评审记录全部变成噪音,真正需要关注的问题全部被埋在“LGTM”里。
第二种是“救火式评审”。代码要上线前的最后一刻,作者到处拉人评审,评审者在完全没有上下文的情况下被迫快速浏览几百上千行改动。这种评审基本只能看出语法问题,不可能看出设计问题、边界问题或者安全隐患。更糟糕的是,救火式评审天然带情绪,作者急、评审者也急,一旦意见有分歧,很容易演变成争吵而不是讨论。
第三种是“精英主义病”。只有资深工程师的意见被采纳,新人的评论没人理,或者新人压根不敢在别人的 MR 里留言。这样一来,代码审查就变成了少数人的特权,而不是团队的知识沉淀过程。我待过一个团队就是这样,两三个核心工程师每天花大量时间给别人 review 代码,其他人在旁边看着,长期下来那几个核心工程师累到不行,其他工程师的代码品味也一直没建立起来。
1.2 开放不是工具的事,是流程和心态的事
很多人觉得“开放审查”就是把代码仓库对所有人可见,或者用开源社区那套方式公开评审。但我在 open-code-review 项目里想说的“开放”,是有四个具体标准的。
第一是可见性。所有评审意见、讨论过程、合入决策都必须留在系统里,任何时间回看都能知道当时为什么这么改。第二是低门槛。团队里任何人都有权发起评审,也都有权对任何 MR 提出意见,意见质量由讨论来解决,而不是由职级来决定。第三是可讨论。评审里不应该有“指令”,只应该有“建议加理由”,评审者和作者是合作关系。第四是自动化兜底。凡是机器能干的事,比如格式检查、静态分析、单元测试、覆盖率门槛,全部交给流水线先跑,让人把精力花在机器做不了的设计判断上。
这四个标准缺一不可。少了可见性,讨论变成私下闲聊,知识撒在地上捡不起来;少了低门槛,评审永远是少数人的玩具;少了可讨论,评审变成命令,作者没有思考空间;少了自动化兜底,人工评审被琐碎问题淹没,自然没人愿意认真看。我见过很多团队买了一堆商业工具,配置了复杂权限,最后 code review 还是没做好,原因就是只解决了“工具”没解决“机制”。
2. 开放代码审查的整体设计与工具选型
2.1 角色分工:谁发起、谁评审、谁合入
很多团队在代码审查上出问题,根源是把三个角色混在一起。作者自己既是评审人又是合入人,或者让不相关的人做技术决策,责任边界非常模糊。我在 open-code-review 里把角色定得比较简单,就三类。
作者负责提供足够的评审上下文,包括改动背景、验证方式、风险点,并且对评论逐条回应,哪怕是拒绝也要给出理由。评审者负责在约定时间内给出有依据的意见,重点关注逻辑正确性、可维护性、安全性和性能,而不是去改排版。合入者负责做最终决策,通常由模块维护者或 tech lead 担任,他不仅要看代码本身,还要看评审讨论是否收敛、自动化检查是否通过、是否有未解决的高优先级评论。
这里面最容易漏掉的是“合入者”这个角色。GitHub 或者 GitLab 的权限体系里,默认有 write 权限的人都能 merge,但实际团队里应该约定一个主轴负责人。我有一次在一个几十人的仓库里,发现每个人都点了 merge,结果代码风格越来越散,架构也慢慢没人守护。后来明确了每个目录的 maintainer,情况才好转。
2.2 从提交到合并的完整流程链路
我设计完整流程时,把代码审查拆成了五个阶段,从源头上减少混乱。
第一阶段是提交准备。作者在本地完成代码改动,自测通过,按规范写好提交信息,然后推送分支并创建 MR/PR。这个阶段最重要的是提交信息规范,因为后续所有评审和回溯都是以你提交的描述为入口。第二阶段是自动化检查。代码推上去之后,流水线自动做编译、静态检查、单测、覆盖率甚至在关键路径上跑安全扫描。这一步不需要人工介入,检查结果是后续人工评审的地基。第三阶段是人工评审。评审者阅读 diff、查看自动化结果、留言提问,作者回应并可能继续修正。这个过程可以往复多轮,但要有时间上限。第四阶段是合入决策。合入者确认所有阻塞性问题都关闭、测试通过之后执行合并,并填写合入备注。第五阶段是上线与复盘。代码合并不是终点,上线后的监控、回滚预案、周期性的评审质量回顾都算流程的一部分。
这五个阶段里,最容易被跳过的是第一阶段和第五阶段。很多人直接推一个大分支上来,整个 MR 改了四十个文件,评审者根本无从下手。也有团队合并之后就不管了,等到线上炸了再回来翻代码才发现当时有个评审意见没被重视。所以我做 open-code-review 的第一件事,就是把这几条流程固化成模板和脚本,而不是靠口头约定。
2.3 工具选型:GitHub 协作、GitLab 协作还是 Gerrit
关于工具,我用过 Gerrit,也重度用过 GitHub 和 GitLab。下面这个表格是我在实际工程里面对比出来的真实感受,不是抄文档。
| 维度 | Gerrit | GitHub Pull Request | GitLab Merge Request |
|---|---|---|---|
| 评审模型 | 类似 Patch Set,按提交版本逐版评审 | 基于分支差异,统一 MR 内多次修改 | 基于分支差异,MR 内多次修改 |
| 评论体验 | 评论绑定具体代码行,历史清晰,但界面偏程序员向 | 行内评论体验好,讨论线程直观 | 行内评论体验好,讨论线程直观 |
| 权限粒度 | 非常细,适合大仓库 | 主仓库加仓库规则 | 主仓库加分支保护规则 |
| 自动化集成的便利度 | 需要配置 Jenkins 等,门槛偏高 | Actions 生态成熟,合入门禁方便 | CI/CD 集成方便,内置了很多规则 |
| 对新人的友好度 | 学习曲线陡峭 | 资料多,易上手 | 资料多,易上手 |
| 适用的典型团队 | 大规模开源项目、强管控团队 | 大多数创业团队和中小团队 | 大多数团队,尤其是使用 GitLab CI 的团队 |
我的结论是,如果你在搭一个全新团队或者改造现有团队,优先选 GitLab 或 GitHub 的 Merge/Pull Request 模式。原因是这套模式的学习成本低,工具的可见性和讨论体验都很好。Gerrit 不是不好,但它把太多控制项暴露给使用者,对一个小团队来说,维护成本远大于收益。
我实际选用的是“GitLab MR + GitLab CI”的组合,所有检查都写在 .gitlab-ci.yml 里,人工评审依托 MR 的讨论线程。这样做的好处是工具链全都在一个平台里,评审者不需要切换到多个系统去查结果,心理负担少很多。如果你偏爱 GitHub,流程也是完全一样的,后面我给的模板和脚本可以平移到 GitHub Actions。
3. 从零搭一套可落地的审查流程
3.1 分支策略与提交信息规范
分支策略直接影响 code review 的粒度。主流选择有两种:一种是 Git Flow,功能分支从 develop 拉出,只有发布时合入 main;另一种是 Trunk-Based,所有人频繁合入主干,配合特性开关。从评审效率来看,我强烈建议普通团队往 Trunk-Based 靠,因为这条策略下分支生命周期短,一个 MR 的改动量规模更小,审查节奏自然就好控制。
你可以把分支策略理解成切菜。一把大萝卜不切直接炖,需要炖很久才能入味;切成小块再下锅,每一块都熟得快。Git Flow 的长期分支养出来的一堆开发,可能一个月都不合一次主代码,MR 动不动就几百个文件;Trunk-Based 逼着大家小步快跑,每次改动都能被及时 review,出问题也能快速定位。
提交信息规范上,我用的是业界主流的 Conventional Commits 风格,格式是“类型(影响范围): 动词短语”。类型一般用在 feat、fix、refactor、perf、docs、chore、test 这几种里面选。举个例子,feat(auth): add refresh token rotation,这条提交信息甚至不需要打开代码都知道做了什么。要求团队成员都按这个格式写提交,不仅是给 code review 提供上下文,也是给后续自动生成 changelog 铺路。
3.2 一份让审查者省心的 MR 描述模板
空 MR 描述是评审者最大的敌人。我见过大量 MR,只有一张截图或者一个链接,评审者打开之后一头雾水,不知道这次改动是修 bug 还是加功能,也不知道哪些地方是重点。所以我给 open-code-review 设计了一套五段式 MR 描述模板,直接放在仓库的 .gitlab/merge_request_templates/ 或 .github/PULL_REQUEST_TEMPLATE.md 里。
模板如下:
## 背景 这个 MR 解决了什么问题?为什么非改不可? (用两三句话说清楚,不要写小作文) ## 改动范围 - 改了哪些目录/模块? - 新增了哪些文件?删除了哪些文件? - 哪些逻辑是对外部行为有影响的? ## 验证方式 - 本地/测试环境是否跑过? - 单测覆盖了哪些场景?是否新增了用例? - 如果涉及接口变更,是否和调用方确认过兼容性? ## 风险点 - 这次改动可能影响哪些现有功能? - 是否依赖外部服务或数据迁移? - 出现异常时有没有可执行的回滚方案? ## 关联信息 - Issue/Ticket 编号 - 相关 MR 链接别小看这段模板。有一次我评审一个支付相关的 MR,作者只写了“修复偶现超时”,我反问他改了哪个环节、对超时的判断逻辑做了什么调整,他才补充了细节。用了模板之后,这类来回扯皮明显少了,因为作者在打开 MR 的那一刻就得把问题想清楚。填写描述的过程,本身就是一次强制自审。
3.3 自动化检查流水线配置
自动化检查是开放审查的“安保员”,它做的是把明显问题挡在人工评审之前。我以一个 Node.js 项目的 .gitlab-ci.yml 为例,讲一讲我实际跑的最小配置长什么样,每个字段的用意是什么。
stages: - lint - test - coverage - security lint: stage: lint image: node:20-alpine script: - npm ci - npm run lint artifacts: expire_in: 1 week test: stage: test image: node:20-alpine script: - npm ci - npm run test -- --runInBand coverage: '/All files[^|]*\|[^|]*\s+([\d.]+)%/' coverage: stage: coverage image: node:20-alpine script: - npm ci - npm run test:coverage - npx jest-coverage-threshold --threshold 80 rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' security: stage: security image: node:20-alpine script: - npm ci - npm audit --audit-level=high rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'这里 coverage 阶段的 jes-coverage-threshold 会强制全仓库覆盖率不能低于 80%,security 阶段用 npm audit 拦掉高危依赖漏洞。注意 test 和 coverage 是分开跑的,因为 test 阶段的重点是快速反馈,coverage 阶段则更耗时,放在 MR 事件里触发,不让它拖慢本地测试和普通 push。
我的习惯是:lint 和 test 是必经门槛,没通过就不能请求评审;coverage 和 security 是兜底检查,可以放行但要在 MR 里展示结果。自动化的目的不是把人工评审全部替代掉,而是让人工评审不再被“忘了加分号”“有个接口被调用错了”这类问题消耗注意力。那些一眼能看出的低级错误,机器比你我在行。
3.4 人工审查清单:不要泛泛而看
自动化检查过了,人工评审看什么?很多工程师 review 的时候就是顺着 diff 往下读,读到哪里卡住了就评论一句,完全随机。我用的是一张结构化的检查清单,按优先级排列,这样不管谁来 review 都不会漏掉最关键的部分。
第一项是逻辑正确性。对照需求,看这次改动是不是真正解决了问题,有没有漏掉某个分支。第二项是错误处理。重点是外部调用、文件操作、网络请求这些最容易出异常的环节,检查有没有 try-catch、有没有合适的错误提示、日志有没有把关键信息打出来。第三项是边界条件。空数组、null、0、超长字符串、并发请求,这些在测试用例里经常被放过,但在生产环境一定会出问题。第四项是安全性。特别关注用户输入是否被校验、SQL 拼接、权限校验是否在正确的位置、敏感信息有没有被记录到日志。第五项是可维护性。变量名是否表达真实意图、函数是否职责单一、有没有明显的重复代码可以复用。
这些检查项不需要每次都从头到尾过一遍,你可以根据 MR 的规模和影响面挑重点。但有一条原则我不会妥协:所有评审意见都必须给出“为什么不通过”的理由,不能只说“这里有问题”,要说清楚“这里有问题,因为……,建议……”。这点后面我会展开讲。
3.5 合并策略与上线后的复盘
合入代码的方式有三种:Merge Commit、Squash and Merge、Rebase and Merge。很多人觉得这只是 Git 历史格式的差异,其实它对 code review 的影响很大。
Merge Commit 会保留完整的合并记录,适合需要追溯“哪些提交属于哪次合并”的大型协作项目,历史是真实的但可能很乱。Squash and Merge 会把整个 MR 的多次提交压缩成一条,历史非常清爽,适合小步迭代的团队,这是我个人比较喜欢的模式。Rebase and Merge 会改写提交历史,保持线性,但对操作纪律要求高,如果多人协作用同一个分支容易翻车。
我建议普通团队直接上 Squash and Merge。理由很简单:在 Trunk-Based 分支策略下,每个 MR 本身就是最小逻辑单元,把单元内的全部提交压缩成一个提交,既能保证主分支历史整洁,又不丢失完整单元的信息。配合前面提到的 Conventional Commits,主分支会变成一份非常流畅的“改动说明书”。
关于复盘,我不建议每个 MR 都开会,那是灾难。我的习惯是每周抽一次 30 分钟的“评审评评审”小会,把本周合并的十几个 MR 快速过一遍,只看三个问题:有没有线上问题和 MR 直接相关?有没有评审意见被忽略但最后造成损失?有没有哪些模块反复出现同类问题?这个会不需要全员参加,维护者参加就够了,但结论要同步给团队。
4. 让评审真正活起来的协作技巧
4.1 控制单次审查体量,别让评审变成苦役
心理学上有个概念叫“决策疲劳”,表现在代码评审里就是:当一个人连续看到几百行 diff 之后,注意力会急剧下降,剩下的评论基本都是敷衍的。所以我在 open-code-review 里规定了一个硬指标:每次 MR 的纯代码净变更量建议控制在 400 行以内,核心模块建议 200 行以内。
为什么是 400 这个数?不是我拍脑袋定的,而是我在团队里统计过:当单个 MR 改动量超过 400 行时,评审者提出有效问题的数量会明显下降。这里说的有效问题是指能引起代码修改、发现真实缺陷的意见,不是“要不要换个变量名”这种吹毛求疵。你可以把 code review 想象成考试阅卷,一个老师连续判三十份作文,和判五份作文,质量完全是两回事。既然人没办法无限保持注意力,那我们就在流程上控制每次评审的量。
如果需求本身很大怎么办?我不会要求你为了控制行数而做虚假拆分,而是建议按模块、按阶段拆成多个可独立合入的 MR。比如一个功能包含后端接口、数据库迁移、前端页面,就可以分三到四个 MR,每个 MR 只改一个层次。这样做还能带来一个额外好处:任何一个中间环节出问题,回滚影响面都很小。
4.2 怎么写一条不惹人生气的评论
评论的语气直接决定评审是产生建设性讨论还是引发冲突。我见过太多评审意见写出来像法官判决,比如“这个函数写得太烂了,重写”。这种话除了让作者反感,没有任何价值。
我常用的评论结构是:位置 + 问题 + 理由 + 建议。分成四个部分:先说问题在哪个地方;再说现象是什么;然后解释为什么这是一个问题,最好结合具体的场景或未来的维护风险;最后给出你的建议或者询问作者的设计意图。举个例子,同样是发现一个变量命名有问题,我原来的写法是“这个命名有误导性”,好的写法是“a 值的命名是 temp,但它的作用是在 xx 地方缓存结果,temp 并不能表达这个含义,建议改成 cachedResult,下次别人维护的时候不用花时间看实现。”
还有一个必须注意的原则:评论要有“可行动性”。如果一条评论让作者看完之后不知道接下来要干什么,那这条评论就没用。比如“这段逻辑有点绕”就是典型的不可行动评论,正确做法是把“绕在哪里”和“怎么改更清晰”都讲出来。实在讲不出建议也没关系,可以改成一个问题:“这段循环的算法复杂度是多少?我看数据量大时有可能会超时,你用哪种方式验证过?”这种提问式的评论同样有效,而且能推动作者自己深入思考。
4.3 评论分歧升级机制
代码评审永远避免不了意见分歧。作者觉得方案 A 简洁,评审者觉得方案 B 更稳。这类分歧如果不找到合理的解决机制,很容易变成“谁资历高听谁的”或者“谁嗓门大听谁的”。
我的处理原则是四个升级步骤,从低成本到高成本逐级递进。第一步,双方在 MR 讨论区各自摆出理由,每条理由里必须有具体事实,比如“这个写法在并发场景下会丢更新,我们已经出现过一次类似事故”。第二步,如果讨论超过两天或者来回超过五轮仍然僵持,拉上第三个人(一般是模块维护者)做评审裁判,用 15 分钟梳理双方论点。第三步,如果还是解决不了,就把问题上升到架构层面,由整个技术委员会或者架构组一起讨论,这种讨论通常不需要很多次,因为到了这一步已经是架构方向的问题了。第四步,如果仍然无法达成共识,决策权交给合入者,但合入者必须在 MR 里写明最终决策的原因,备查。
这套机制的核心理念是“把分歧当信息而不是冲突”。一次激烈讨论,往往暴露的是团队对某个技术点的认知差异。如果这些差异被藏起来,以后还会在别的地方冒出来;如果被公开讨论并形成决策记录,团队的知识库就会一点点长出来。
4.4 把评审做成新人最快的成长通道
我之前在“精英主义病”里提到过这个问题。code review 如果只有资深工程师能评论,那新人永远学不会怎么写好代码。开放审查的价值之一,就是让新人以最低的成本接触真实项目的决策过程。
我鼓励新人从一开始就参与评论,哪怕只是提一个关于测试覆盖的小问题也没关系。团队约定:禁止在 review 讨论中嘲笑任何人的问题,禁止因为“这个问题太基础”就不让提问。同时我也会安排 senior 工程师在评论里解释“为什么”而不只是“改什么”。新人通过看资深工程师的 review 评论,能学到的东西比读文档多得多,因为那些评论背后是真实的场景、踩过的坑和判断依据。
反过来,我也要求新人的 MR 必须有资深工程师的 review 通过才能合入。这样做不是为了“卡”新人,而是确保新人能在第一时间获得专业反馈。很多团队等新人试用期过了才开始 review 他们的代码,这是很大的浪费。代码坏味道在前三个月长出来,之后要花十倍的精力去修正。
5. 常见问题与排查技巧实录
5.1 高频问题速查表
我把实际落地过程中遇到的典型问题整理成了速查表,每个问题后面都附了排查思路,方便你在团队里快速定位。
| 现象 | 可能原因 | 排查与解决办法 |
|---|---|---|
| MR 挂了很久没人 review | 没有明确负责人,或者评审者不知道如何评论 | 给每个 MR 指派默认 reviewer,设定最长等待时间(比如工作日 4 小时内必须表态),超时自动提醒 |
| 评论很多但作者不回复 | 作者觉得评论只是建议,不是阻塞项 | 用标签区分 Must Fix / Should Fix / Optional,明确哪些评论必须得到回应 |
| 评审变成“无脑 approve” | 团队没有检查清单,评审者找不到重点 | 引入检查清单模板,让评审者逐项打勾后才允许通过 |
| 大 MR 堆积严重 | 分支生命周期过长,拆任务不足 | 规定一个 MR 只解决一个问题,超过 400 行就要求拆分 |
| 自动化检查太多导致提交流程很慢 | CI 配了太多阶段性任务 | 区分“必过项”和“报告项”,把耗时任务放到后台定时跑,MR 阶段只跑核心检查 |
| 评审讨论变成吵架 | 评论语气带攻击性,或缺少决策机制 | 用“位置+问题+理由+建议”的评论结构,执行分歧升级机制 |
| 代码合入后出事故 | 评审只看了逻辑,没看上线风险 | 在 MR 模板里增加风险点描述,合入前必须评估回滚方案 |
这张表做完之后,团队可以省掉很多“凭感觉管理”。质量的问题,先看流程;流程的问题,先看机制;机制的问题,先看工具是否支持。不要一上来就怪某个人不够认真,人性的问题是靠机制来解决的。
5.2 自动化审查和人工审查的边界怎么划
很多人担心自动化检查太严格会增加负担,或者自动化代替了人工。我自己的观察是,真正高级的开发团队会把自动化当成“守门人”,把人工当成“建筑师”。守门人和建筑师的分工,决定了整个评审体系的质量上限。
自动化应该负责五类事情:编译和构建、静态检查、单元测试、依赖漏洞扫描、覆盖率门槛。这些任务的特点是规则明确、结果可预测,机器做比人做更稳定、更快。人工审查应该负责另外五类事情:架构合理性、逻辑正确性、可扩展性、安全问题(尤其是业务逻辑层面的)、以及代码的可读性。这些任务没有标准答案,需要结合业务背景做判断。
把边界划清楚之后,团队成员都知道自己在评审环节里扮演什么角色。我记得有个版本我刚上线覆盖率门槛时,有人抱怨“为了数字写测试没意义”,我说覆盖率是底线不是目标,80% 挡住的是完全没测试的代码,剩下 20% 的缝隙由人工审查补。边界清楚了,抵触情绪自然消解。
5.3 团队推行 open-code-review 时的节奏建议
最后讲一讲推行节奏。我见过太多团队一上来就把全套自动化、模板、SLA、指标一次性砸下去,结果开发同学手忙脚乱,几周后全部反弹回老路。打开放的代码审查体系,应该像滚雪球,从最小可用闭环开始。
第一步,只做一件事:强制每个 MR 填写五段式描述模板,同时规定单次净改动不超过 400 行。这一步上线一天就能见效,评审者打开 MR 不再一头雾水。第二步,接入自动化防线:lint 和单测作为硬门槛,没有通过不许请求评审。这一步完成后,人工评审者能从大量低级错误里解放出来。第三步,建立评审纪律:设置默认 reviewer,规定响应时限,用标签区分 Must Fix 和 Optional。第四步,引入度量但不要迷信度量:记录 MR 从创建到合入的平均时长、评审人均耗时、每百行代码发现的问题数,用数据观察流程是否健康,但不要把数据变成 KPI 去考核个人。第五步,进入持续复盘阶段:每周 30 分钟的 review 回顾会,持续迭代检查和评审标准。
这个节奏我个人在多个团队试过,最快三周就能跑出稳定流程,最慢也不会超过两个月。关键不是配置多完美,而是让每个参与的人都从这套流程里尝到甜头:作者更快合入代码,评审者不用再看垃圾 MR,管理者能看到研发过程的透明数据。只有所有人都觉得“这套流程让我更省力了”,开放代码审查才算真正立住。
我在实际维护 open-code-review 的过程中最深的体会是:代码审查的意义远不止“找 bug”,它是团队知识共享、责任共担、质量共识最重要的载体。工具只是放大器,真正的杠杆是流程里每个人的参与感。如果你想开始,不用等团队全员达成共识再动手,先从一份 MR 模板和一条自动化检查开始,把墙凿开一个洞,光自然会慢慢照进来。