做code review这件事,我在团队里带了三年多,最大的体会是:审查本身不难,难的是让所有人愿意认真审、敢说问题、也接得住问题。后来我们内部开始用一套叫 open-code-review 的流程,把审查从“代码合并前的关卡”变成了“团队共同维护的质量基线”。它不是某个厂商的商业工具,而是一套偏开源、可配置的代码审查工作流,配合 GitHub/GitLab 的自动化能力,能把很多重复劳动交给机器,把人的精力留给真正需要判断的地方。这篇文章我就把这个方案从设计到落地完整拆一遍,适合正在搭 code review 体系的小团队,也适合想让已有流程更开放、更自动化的同学参考。
1. 整体设计与思路拆解
1.1 传统代码审查的五个痛点
传统的 code review 往往是从“老板要求”开始的:建一个仓库,要求所有合并必须有人审批,然后把一堆规则贴到 Wiki 里。看起来流程有了,但跑过一段时间就会发现,问题不是流程缺失,而是流程太封闭、太依赖个人自觉。
第一个痛点是审查成了形式。开发人员为了不等太久,会找关系好的同事点一下“Approve”,审查者甚至不看 diff 就直接通过。这种情况在团队里很普遍,尤其是 KPI 里没有质量指标的时候,review 就是走过场。
第二个痛点是反馈太慢。一个 PR 提上去,等了两小时没人看,开发急着联调,只能先合并再说。等到后面出问题,再回头翻提交记录,发现当时的 review 意见早就被淹没了。
第三个痛点是标准不透明。有的审查者特别在意变量命名,有的审查者只盯性能,同一段代码换个审查者就是两种结果。新人进来之后完全摸不到规律,只能“碰运气”式地提交代码。
第四个痛点是自动化没跟上。很多团队还在靠人肉检查缩进、空行、有没有 console.log,这些本来应该由机器在几秒内完成。人的精力是有限的,把时间耗在这些琐碎检查上,真正应该深入思考的架构问题反而没人看。
第五个痛点是反馈没有沉淀。审查意见散落在各个 PR 里,下一次还会犯同样的错。如果每次 review 之后没有人总结、没有把高频问题变成检查规则,那团队就是在用同样的方式反复交学费。
1.2 open-code-review 的核心思路
open-code-review 这套流程的核心就一个字:开。
规则是公开的,不是藏在某个人的脑子里;审查结果是可追溯的,不是一句“我觉得不好”就完事;自动化检查是开放的,任何成员都可以往里面加规则;流程本身是透明的,每个人都能看到当前 PR 卡在哪一步,距离合并还差什么。
所谓“开放”,并不是说所有人都可以随意合代码,而是把标准拉到台面上来。比如我们规定:静态检查不过不能合并,测试覆盖率低于阈值不能合并,至少一人人工 Approve 才能合并。这些规则写死在 CI 里,不需要 reviewer 用个人影响力去推动,机器先替所有人把关。
这种设计有一个好处:审查者不再扮演“警察”的角色,而是和开发者站在同一边。机器把低级问题都拦下来了,人工 review 只需要关注真正需要判断的设计、逻辑、边界、兼容性问题。这样一来,reviewer 的压力小了,开发者也更容易接受审查意见,因为意见不再像挑刺,而更像共同补漏洞。
1.3 为什么我不建议一开始就买商业工具
市面上有很多商业 code review 平台,界面漂亮、统计功能强,但我搭过几轮之后发现,小团队一开始就上商业工具,往往会陷入“用工具替代流程”的误区。
商业工具的价值在于把已有成熟流程数字化,但大多数初创团队的问题不是缺工具,而是连流程本身都没跑通。你花了很多钱买来一个强大的平台,然后发现大家还是不会好好 review,问题出在习惯和规则上,不在工具上。
开源方案的好处是低成本和低绑架。我们基于 open-code-review 的思路,用 GitHub 原生能力加上一些开源工具,就把流程搭起来了。后续就算团队规模变大、需求变复杂,也可以随时替换其中的组件,不会被某家厂商锁定。对我这种喜欢掌控感的人来说,这个自由度非常关键。
2. 核心细节解析与实操要点
2.1 审查规则定义:硬性红线与软性建议
open-code-review 落地时,最重要的一件事是区分硬性红线和软性建议。如果所有规则都一视同仁,reviewer 会累死,开发者也容易产生对抗情绪。
硬性红线是必须由 CI 和 reviewer 双重把关的项,一旦触发直接打回:
- 编译失败或启动失败,这个不用解释。
- 单测失败,或覆盖率相比目标分支下降超过 2 个百分点。
- 存在明显安全问题,比如 SQL 拼接、敏感信息硬编码、越权接口没做鉴权。
- 修改了公共接口或数据表结构,但没有同步更新文档或迁移脚本。
- 引入了明显无用的死代码、被注释掉的大段逻辑。
软性建议是 reviewer 可以提、但不应该阻塞合并的项:
- 变量命名不够直观。
- 某段逻辑可以抽取成函数提升可读性。
- 缺少必要的注释,但代码本身能从上下文看懂。
- 页面样式细节和设计稿有偏差。
为什么要把红线定得这么清晰?因为人有一个特点:面对模糊规则时,会倾向于“我觉得可以过”。当规则变成“覆盖率下降 2 个点就失败”这种数字,讨论就变成了对事不对人,没有模糊空间。
2.2 自动化检查与人工审查的分工
我见过最理想的分工是:机器负责“确定性”问题,人负责“判断性”问题。
自动化检查至少应该覆盖以下几个维度:
| 检查项 | 工具示例 | 拦截时机 |
|---|---|---|
| 代码格式 | Prettier、Black、gofmt | 提交时、CI |
| 静态检查 | ESLint、Checkstyle、golangci-lint | CI |
| 单元测试 | Jest、pytest、JUnit | CI |
| 覆盖率 | Istanbul、Coverage.py、JaCoCo | CI |
| 依赖安全检查 | npm audit、pip-audit、Trivy | CI 定时任务 |
| 提交信息规范 | commitlint | 提交时、CI |
人工 review 只负责四件事:整体设计是否合理、边界情况是否处理到位、有没有过度设计、将来的维护成本会不会失控。
这样分工以后,PR 的评论数量会大幅下降。以前一次 review 可能提十条意见,其中八条都是“这里多个空格”“这个变量应该用 const”,现在这些全部由机器完成,剩下两条才是真正值得讨论的内容。
2.3 Code Review 评论的写法:减少对抗,提高通过率
很多团队 review 文化差,不是大家故意找茬,而是说话方式有问题。
同样一句意见,换成不同说法,效果完全不同。
差评:“这里写得太烂了。”
好评:“这个循环把用户 ID 存成了字符串,我担心后面排序时会按字典序排,建议改成数字类型并补一个测试确认一下。”
差评:“这个函数名字不对。”
好评:“我第一次看这个函数时没明白它要返回什么,叫
parseConfig会不会更清楚?如果它是做校验的,也可以叫validateConfig。”差评:“为什么要用这个库?”
好评:“这个库已经一年多没更新了,我查了下有 3 个已知 CVE。我们是否能换用官方库?如果必须用这个,建议加一个说明。”
在 open-code-review 流程里,我还会强制要求 reviewer 在评论中给出“问题类型”前缀,比如 [BUG]、[PERF]、[STYLE]、[QUESTION]。这样开发者扫一眼就能判断优先级,不会被一堆措辞带偏。
3. 实操过程与核心环节实现
3.1 准备阶段:分支策略与保护规则
搭建 open-code-review 工作流,第一步不是装工具,而是把仓库的基础规则定好。
我们的分支策略非常简单:main主干永远处于可发布状态,所有改动从main拉分支,合并回main必须经过 PR。不在main上直接提交,这是铁律。
接着要在 GitHub 开启分支保护规则,打开仓库的 Settings -> Branches -> Add rule,配置:
- Require a pull request before merging:开启。
- Require approvals:设置为 1。
- Dismiss stale pull request approvals when new commits are pushed:开启。这个很关键,避免开发者推了新代码但旧 Approve 还在的问题。
- Require status checks to pass before merging:开启,并勾选 CI 状态检查。
- Do not allow bypassing the above settings:开启。
这些配置的表面作用是卡住合并权限,深层作用是给团队一个“安全的失败环境”。开发者知道代码不会因为他们疏忽而直接污染主干,审查者也知道自己的意见不会被忽略,双方的安全感都来自这里。
3.2 搭建自动化审查流水线
接下来是最激动人心的一步:把机器拉进 review 流程。
我们以 GitHub Actions 为例。在仓库根目录创建.github/workflows/ci.yml,核心配置长这样:
name: CI on: pull_request: branches: - main jobs: lint-and-test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies run: npm ci - name: Run lint run: npm run lint - name: Run tests with coverage run: npm run test:coverage - name: Check coverage threshold run: npx nyc check-coverage --lines 80 --functions 80 --branches 75这段配置的核心是:任何发往main的 PR 都会自动跑一遍。lint不过、测试不过、覆盖率不达标,PR 的 Status Check 就会是红色,合并按钮直接置灰。
如果你想让审查意见直接出现在 PR 的评论区,可以用 reviewdog 配合 GitHub Actions。比如下面这段是让 ESLint 的错误自动评论到对应代码行:
- name: Run reviewdog with eslint uses: reviewdog/action-eslint@v1 with: github_token: ${{ secrets.GITHUB_TOKEN }} reporter: github-pr-review level: warning fail_on_error: true注意这里的level: warning和fail_on_error: true是故意这样配的。warning 级别会评论出来让大家看到,但不会直接失败;而 fail_on_error 会让 error 级别的问题阻断合并。这样团队逐步清理存量问题,同时拦住新增的严重问题。
3.3 定义 open-code-review 的审查模板
机器检查搭好了,接下来要解决人和人之间的沟通规范。
我在仓库里放了一个.github/pull_request_template.md,每个 PR 提交时自动带出模板:
## 变更描述 - 这个 PR 解决了什么问题: - 关联 Issue/需求编号: ## 变更范围 - [ ] 新增功能 - [ ] Bug 修复 - [ ] 性能优化 - [ ] 重构 - [ ] 文档更新 ## 自测情况 - [ ] 本地编译通过 - [ ] 单测已补充/更新 - [ ] 相关接口已自测 - [ ] 变更了数据库结构/接口协议,已同步文档 ## 特别说明 - 需要 reviewer 重点关注的模块: - 潜在风险点:模板的存在不是增加负担,而是降低沟通成本。开发者填完之后,reviewer 打开 PR 就知道该从哪里看起,不用像考古一样翻 commit。
同时,我还会要求每个 PR 尽量控制在 400 行变更以内。这是硬性约定,超过就鼓励拆分成多个小 PR。原因很简单:人脑对超过 400 行的 diff 会出现“阅读疲劳”,注意力会断崖式下降。小 PR 看起来多,但每个都能被认真审完,整体反而更快。
3.4 落地人工 Review 的节奏
自动化检查通过之后,人工 review 的节奏也要定下来。我们团队的做法是:
每天上午和下午各安排一个“Review 时段”,每个时段 30 分钟。到点之后,审查者把待审 PR 按提交时间排序,从上往下逐个看。不在这个时间段内的 review 请求不要求立刻响应,但也不能拖超过一个工作日。
为什么固定时段?因为人一旦随时被 PR 评论打断,注意力会被撕碎。集中处理反而效率更高,而且开发者也心里有数:上午提的 PR,最晚下午有反馈。
人工 review 的流程我总结成四步:
- 先看描述和自测清单,理解这次改动的意图。
- 再从 diff 里找“和想象不一致”的地方,这些往往是问题藏身处。
- 针对每一处疑问,先写问题,不急着给结论。
- 最后全局看一遍,综合判断是否 approve。
如果发现中等以上问题,我会直接在 GitHub 上 request changes,并只保留真正值得讨论的意见。小问题合并到一次评论里,避免刷屏。
4. 常见问题与排查技巧实录
4.1 问题速查表
很多团队在落地 open-code-review 时会遇到类似的问题,我整理了一个速查表:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 开发说 CI 没过,但本地测试是好的 | Node 版本或系统环境不一致 | 在 CI 里固定 Node 版本,用 lockfile 和npm ci安装依赖 |
| reviewdog 评论刷屏 | level: info把大量建议都评论出来了 | 改成level: warning,只在 error 时 fail_on_error |
| 审查者总是拖很久才 approve | 没有约定 review 时限 | 固定 review 时段,超时自动升级提醒 |
| 开发者无视 review 意见直接合并 | 保护规则没开或者被 bypass | 关闭 branch protection 的 bypass,强制 status check |
| 覆盖率数字忽高忽低 | CI 合并覆盖率的方式不对 | 使用nyc merge合并多个 coverage 文件后再计算 |
| PR 太大,审查者不愿意看 | 没有提前拆分的习惯 | 约定 400 行上限,用里程碑拆功能 |
这张表不是万能药,但大部分团队初期遇到的问题都逃不出这几个。
4.2 两个真实排查案例
案例一:自动化评论太多,开发直接把通知屏蔽了。
上线 reviewdog 的第一周,PR 评论区全是风格建议,短短一个 PR 产生了 30 多条评论。开发者不堪其扰,直接在浏览器里屏蔽了 GitHub 通知,结果严重问题也没人看。
排查之后发现,我们的 ESLint 规则里开启了no-magic-numbers,要求所有魔法数字都抽成常量。这个规则对业务代码来说过于严格,导致大量破坏阅读体验的 warning。后来我们把这条规则的等级调低,并把 reviewdog 的level从info改成warning,评论数量立刻降到个位数。
这件事给我的教训是:自动化审查的“噪音量”必须严格控制。机器评论越少,越重要。
案例二:本地通过了,CI 却挂了。
有个同事提交 PR 后,CI 在 lint 阶段就失败,但他在本地跑npm run lint是过的。最诡异的是,同样的代码在他机器上是过的,在 CI 上就挂。
后来发现是他本地全局安装了另一个版本的eslint,覆盖了项目依赖里的版本。他执行npm run lint时调用的可执行文件来自全局,而不是项目本地。我们当时的处理方式是在 CI 的 lint 步骤前增加npx eslint --version验证版本,同时在项目里用pre-commithook 锁定本地命令。
这个案例让我深刻认识到:code review 流程里的自动化工具,必须在一致的运行环境里执行,否则它带来的不是安全感,而是噪音。
4.3 实施经验与避坑建议
最后分享几条我在实施 open-code-review 过程中踩过坑之后沉淀下来的经验。
别在第一天把所有规则全部打开。最好的方式是先只开红线规则:编译、测试、安全扫描。跑通两周之后,再加风格和覆盖率。规则一次加太多,开发会直接躺平。
reviewer 要轮换。不要总让技术负责人一个人审,其他人都不审,否则其他人永远学不会写高质量代码。我采取的方式是“每两周轮换一次审查搭档”,每个 PR 至少两个 reviewer,一个是熟悉该模块的人,一个是完全不熟悉该模块的人。不熟的人往往能发现“文档没写清楚”“这个接口太难用”之类的问题。
用数据说话。每季度我会统计一下 PR 平均提交次数、平均审查时长、被拦截的严重问题数量。这些指标不是为了考核人,而是用来发现流程的瓶颈。比如如果 PR 平均要改 5 轮才合并,说明模板自测清单不够清楚,而不是大家技术不行。
规则要定期重新评估。每半年我会把 CI 里的规则全部过一遍,凡是经常导致争议又确实没拦住真实 bug 的规则,直接删掉。open-code-review 的“开放”也体现在这里:规则不是某个人拍脑袋定完就不变了,而是团队一起不断修正的结果。
我个人在实际操作中的体会是:一个 code review 流程能不能跑起来,关键不在于工具选得多酷,而在于它能不能给每个人确定性的反馈。工具只是把确定性落到执行层面,真正让团队走到一起的,是“规则公开、反馈及时、对事不对人”这三种感受。如果你刚好也在搭类似的流程,别急着加规则,先把现状的痛点列出来,再一个萝卜一个坑地补。哪怕第一次只解决一个“CI 没人看”的问题,也比设计一套完美但没人执行的制度强。