代码评审(code review)做到最后,拼的其实不是谁的眼神更毒,而是流程有没有把工具和人结合起来。最近我花了不少精力整理一套叫 open-code-review 的开放式代码审查方案,说是工具也行,说是方法论也对,核心思路就是把自动化的硬检查、人工评审的 checklists 和整个过程的数据沉淀打通。很多团队不是不愿意做 code review,而是做着做着就变成了"看一眼、点个赞、合掉",问题根本没法在合入前暴露。这篇文章我会从为什么需要这样一套方案讲起,再把动手搭建的每一步拆开来说,适合正在为代码评审流程发愁的技术 Leader、后端/前端工程师,以及想在自己开源项目里加上必备审查环节的独立开发者。
1. 为什么需要一套"开放"的代码审查方案
1.1 code review 在真实团队里为什么容易流于形式
先聊一个很常见的场景:PR 一开,@ 了几个同事,过了一小时收到一个 LGTM,然后合并按钮变成绿色,皆大欢喜。Review 真的起到了作用吗?大多数时候没有。我发现团队里 code review 流于形式,通常不是因为大家不认真,而是因为缺少三个东西:没有统一的审查标准、没有自动化防线兜底、也没有对评审质量的反馈。
没有统一标准时,每个人只能凭个人经验去挑毛病。风格问题、边界条件、安全隐患经常是"看心情"才提的。而人工审查本来就不适合做重复性的机械检查,把大量精力花在找空格、吐槽命名上,真正需要人脑判断的并发问题、接口设计问题反而没人关注。所以最后评审意见越来越水,开发者越来越不重视,形成恶性循环。
open-code-review 这个名字里我特别看重"open"这个词,不只是开源,还包括流程开放、规则开放、数据开放。审查规则不是某个人脑子里的经验,而是仓库里能直接看、能改、能积累的配置文件;审查结果不是 review 完就消失的聊天记录,而是能统计、能复盘、能改进的过程数据。这是普通 Git 平台自带 review 功能做不到的事。
1.2 open-code-review 想解决的三个核心问题
这套方案从设计第一天起就盯着三个问题。
第一,把重复劳动从人身上剥掉。空格、缩进、明显的死代码、常见的错误处理遗漏,这些都应该由静态分析工具在 CI 阶段直接拦住,而不是等人去 review 的时候发现。PR 评论里如果只剩下一堆 lint 提示,开发者会愤怒,评审者也会越来越没耐心。
第二,给人工审查提供明确抓手。这里不是让大家照着模板机械打钩,而是用一份可持续完善的 checklists 让评审者知道该看什么方向:有没有做边界判断,异常路径有没有处理,日志信息是否够用,变更范围是否可控。这样做还有额外收益:新人第一次参与 review 时,不用靠"跟着感觉走"。
第三,让 code review 的过程看得见。每次 PR 花了多少轮、每轮耗时多久、通常在哪类文件/哪类规则上反复被提意见,这些数据如果完全靠人脑记是不现实的。open-code-review 会把审查插件收集到的信息统一沉淀下来,隔一段时间复盘,你能清清楚楚看到团队代码质量的短板到底在哪。
1.3 这套方案适合什么样的团队
先说结论:小到个人开源项目,大到几十人的业务团队,这套方案都能用,只是接入深度不同。个人项目最需要的是"自动化兜底",因为你没有同事帮你 review,ESLint、tsc、测试和 reviewdog 就是你的“虚拟评审团”;小团队可以在此基础上加入人工 checklists,让每次 PR 至少有一个人的深度参与;再大一点的团队,可以继续接入数据周报和规则增量管理。
不太适合什么场景?极少数追求极致效率、一天要合几十个热修的团队。如果业务火急火燎到连 CI 都不愿意等,那么任何审查流程都会被绕过。不过话说回来,这种团队最需要的也不是 review 流程,而是先解决开发节奏失控的问题。
2. 核心设计思路与工具选型逻辑
2.1 先给代码审查分层:自动化、规范、人工
我在设计 open-code-review 时,一开始就拒绝把工具堆在一起,而是把审查内容拆成三层。
第一层是自动化的硬性检查,定位是“机器能干好的事绝对不让人干”。这一层包括编译、测试、依赖安全检查、代码格式、静态规则。比如前端项目里不写key的循环、React Hook 依赖项缺失、明显的any滥用,这些都应该在流水线里直接 fail,而不是要求 reviewer 去肉眼发现。
第二层是规范性评审。这里仍然是机器辅助,但是规则更贴近团队自身约束。比如要求所有对外接口必须有 JSDoc 注释,新增接口必须带鉴权处理,数据库变更必须附带上线脚本说明。这类规则最适合沉淀为可配置的插件或者脚本。传统 lint 规则覆盖不了的,可以用 reviewdog 结合自写脚本去检查。
第三层才是人工评审。它负责的是真正需要人理解上下文的部分:接口设计是否合理、模块边界有没有被破坏、业务逻辑是否覆盖了所有分支、未来的扩展成本能不能接受。人脑应该花在这些高价值判断上,而不是陷入到琐碎格式问题里。
这个分层是整个方案的骨架,后续所有工具选型都围绕它展开。
2.2 工具链选型:我为什么选这几样
整个 open-code-review 的默认技术栈很朴素,都是多年沉淀下来的成熟项目。
- 代码托管:GitHub 或 GitLab,两者都支持 CI 触发和 PR/MR 评论。
- CI 引擎:GitHub Actions 或 GitLab CI,好处是零额外部署,push 和 pull_request 事件天然集成。
- 静态分析:按语言选。前端/Node 用 ESLint + typescript-eslint;Python 用 Ruff + mypy;Go 用 golangci-lint。它们几乎是社区默认标准,插件生态丰富。
- 评论机器人:reviewdog,这是我强烈推荐的组件。它能把 lint 输出转成 PR 上的行级评论,还支持
level=error控制是否阻塞合入。 - 人工评审导航:一份放在
docs/目录下的代码评审 checklists,用 Markdown 写,入库管理。 - 数据展示:一个简单的 Python/Shell 脚本,按时拉取 PR 数据,生成周报表格。
有人会问,为什么不用那些商业代码审查平台?我用过,体验确实好,但对很多团队来说太重了。要单独部署、要维护账号体系、要接内网权限,代码还得出库。open-code-review 的立足点是"轻"和"业务无感",CI 里加一个 job 就能跑,不改变开发流程,不强迫任何人开新页面去点按钮,只在 PR 内部完成闭环。
2.3 审查规则必须入库:配置即文档
很多团队的问题是不缺规则,缺规则的可执行性。团队 wiki 里写满了代码规范,但实际 review 时没人真去对着文档一条条核查。open-code-review 的做法是把规则分成两类,能自动化的全部写进 CI 配置文件,不能自动化的写进 checklists,同样放进 Git 仓库。
配置文件就是规则本身。任何人 review 配置变更时,都能看到团队之前为什么加了这条规则、什么情况下会放行、什么情况下必须拦截。这比一段没人看的 wiki 有用得多。例如.github/workflows/reviewdog.yml里写死了level: error的规则,就意味着这些是合入门禁,不是建议。另一个level: warning则只提醒不拦截。
checklists 最初可以简单一点,十项左右就够,之后再根据踩坑经历慢慢加。一旦仓库里有了这些文件,新成员入职第一周就能看懂团队代码审查的"游戏规则",不用追问别人。
2.4 不引入重量级平台的三个理由
我承认,正规商业 Code Review 平台的功能比这套组合拳强大太多,但综合权衡后我还是选择 open-code-review 这套轻量方案,主要三个理由。
第一个理由是成本。商业平台按人头收费,团队二十人以下还好,上百人之后费用非常夸张。而 open-code-review 用到的组件全部是开源的,跑在已有 CI 里,额外成本几乎为零。
第二个理由是数据隐私。很多团队的代码是放在内网或用私有仓库托管,不希望额外把代码内容送到第三方审查服务去分析。开源工具链可以在内网 GitLab + Runner 上完整落地,数据不出内网。
第三个理由是自定义能力。商业平台能提供的自定义规则通常受限于官方插件体系,而 open-code-review 里我用一个 30 行的 Python 脚本就能实现一个团队专属检查,并让它在 PR 评论里输出结果。这种自由度对流程建设期非常重要,因为团队规则本来就需要快速迭代。
3. 从零搭建 open-code-review 的实操过程
3.1 准备阶段:项目结构与环境要求
在动手前,先明确一个最小目录结构。我建议把审查相关文件统一放在.github/和docs/下:
your-repo/ ├── .github/ │ ├── workflows/ │ │ ├── ci.yml │ │ └── reviewdog.yml │ └── reviewdog.toml ├── docs/ │ └── code-review-checklist.md ├── scripts/ │ └── extra-checks.py ├── .eslintrc.cjs └── package.json环境要求其实很低:代码托管在 GitHub 或 GitLab,能跑 GitHub Actions/GitLab CI 就行。本系列示例用 GitHub Actions 做演示,GitLab 的写法也类似。如果仓库里已经有 CI,那只需要在现有流程里加一个 reviewdog job,完全没有必要另起炉灶。
3.2 配置 CI 工作流:一个最小的审查流水线
以一个 Node.js + TypeScript 项目为例。我习惯先在.github/workflows/reviewdog.yml里放一个最基础的 workflow,它要做两件事:安装依赖并跑 lint,把 lint 结果交给 reviewdog 写评论。
name: open-code-review on: pull_request: types: [opened, synchronize, reopened] permissions: contents: read pull-requests: write jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npx eslint . --format=checkstyle --output-file=eslint-checkstyle.xml continue-on-error: true - uses: reviewdog/action-eslint@v1 with: reporter: github-pr-review level: error fail_on_error: true这段配置有几个关键点值得说。
permissions里必须给pull-requests: write,否则 reviewdog 没有权限往 PR 里写评论。很多同学第一次接入时评论死活出不来,八成就是栽在这里。
eslint命令必须加--format=checkstyle并输出到文件,因为 reviewdog 的 ESLint action 默认从这个格式解析结果。continue-on-error: true是为了让 lint 这一步的“失败”不会阻止后面的评论步骤执行,真正是否拦截合入交给fail_on_error: true控制。
level: error表示所有 error 级别的问题都会在 PR 上显示为错误评论并导致检查失败,warning 级别的问题则只提示不阻断。这样团队可以优先解决硬伤,再慢慢消化建议类问题。
3.3 用 reviewdog 把静态检查结果变成 PR 评论
集成 reviewdog 是整套方案里收益最明显、动作最少的一步。以 GitHub 为例,我在.github/workflows/reviewdog.yml中把上面 lint job 跑完后,工作流会自动在 diff 对应的行上留下评论。开发者不需要离开 GitHub 页面,就能看到"这个位置有个 error,原因是 xxx,原文出自 eslint rule"。
如果你们用的语言不是 JavaScript,思路完全一样。Python 项目用reviewdog/action-ruff,Go 项目用reviewdog/action-golangci-lint,Shell 脚本用reviewdog/action-shellcheck。它们共享同一套 reporter 机制,区别只是解析器不同。这让我很喜欢 reviewdog 的一点:一旦你理解了一个语言接入方式,其他语言几乎不需要重新学习。
除了直接接现成 action,还可以接自定义命令。比如我用 Python 写了一个脚本scripts/extra-checks.py,专门检查某些不符合团队规范的内容,然后以 diff 格式输出,同样交给 reviewdog:
python scripts/extra-checks.py | reviewdog -f=diff -reporter=github-pr-review -level=warning这样团队里任何能写脚本的人都能很快给审查流程加一条新规则,不需要等官方插件支持,这正是 open-code-review 里"开放扩展"的体现。
3.4 编写人工审查 checklists:让评审有章可循
自动化只能覆盖一部分问题,真正决定代码质量的是人工 review 的深度。我在docs/code-review-checklist.md里维护着一份清单,原则是"少而聚焦,持续迭代"。下面是一个可以参考的初始版本:
- 功能逻辑:代码是否符合 PR 描述?是否存在未处理的边界情况?
- 错误处理:异常路径是否清晰,是否会导致状态不一致或数据丢失?
- 安全:是否涉及用户输入、鉴权、越权访问?敏感信息是否落日志?
- 性能:新增循环/查询是否可能成为瓶颈?是否产生不必要的重复计算?
- 兼容性:改动对老版本、其他模块、第三方依赖有无影响?
- 可测性:改动是否有测试覆盖?缺失测试的情况是否给出明确理由?
- 可读性:命名和注释是否准确,能否让三个月后的我看懂?
- 变更范围:PR 是否仍然聚焦,有没有混入无关修改?
我不建议一次性把清单拉得很长。超过二十项以后,评审者会麻木,最后又是全部打勾走流程。更好的做法是每次复盘时把实际踩过的坑浓缩成新条目,并且为每一条补充一句"为什么",让后来者知道这不是纸面文章。
3.5 审查数据的沉淀与周报
流程运转起来后,会积累大量 PR 数据。哪怕只是简单统计,也能看出不少问题。我每周会跑一次脚本,从 GitHub 拉取本周已合并的 PR,整理成一张 Markdown 表格,包含字段:PR 标题、作者、评审轮次、首次评审耗时、是否被 CI 拦截过。
生成方式不复杂,可以用 GitHub CLI:
gh pr list --state merged --limit 100 --json number,title,author,createdAt,mergedAt,reviews然后丢给 Python 做聚合统计。周报作用不是为了追责谁,而是帮助大家看趋势:如果某个模块的 PR 评审轮次总是最多,说明该模块需要技术债治理;如果所有 PR 都被 lint 拦截过,说明本地开发环境没跑 lint,应该把 hook 加到 pre-commit 里。
4. 常见问题与排查技巧实录
4.1 reviewdog 不评论 PR 怎么办
这个问题出现的频率最高。reviewdog 静默不干活,多数时候不是工具坏了,而是权限或触发条件没写对。先按下面顺序排查:
先确认 workflow 有没有在 PR 事件中运行。pull_request事件类型需要是opened、synchronize、reopened之一,如果只配了push事件,它当然不会在 PR 上评论。然后看 job 的permissions,pull-requests: write必须显式写出来。默认的GITHUB_TOKEN权限是只读的,写不了评论。再从 Actions 日志里搜reviewdog关键字,看有没有reporting相关的报错。最后还要看代码仓库是不是 fork 分支的 PR,第三方 fork 过来的 PR 在首次运行时需要维护者手动批准,这是 GitHub 的安全限制。
4.2 静态检查误报太多如何安抚团队情绪
自动化工具刚上线那几天,团队最烦的就是 PR 上一堆红字,其中有一部分还是误报。处理这个问题我是分三步走的。
第一步,把所有非致命建议全部降为level: warning,只对明确会造成 Bug 的规则使用level: error。第二步,给存量代码建立基线,新代码必须干净,老代码不强制一次性改完。ESLint 有--no-ignore之类的手段,也可以直接对老文件加一行// eslint-disable-line并注明原因。第三步是最重要的——每周和团队一起看 reviewdog 的评论命中率,如果某条规则打开后实际拦截的 90% 都是误报,就直接关掉它,别舍不得。规则是为开发效率服务的,不是为了凑数量。
4.3 评审意见写得像废话怎么治
很多人收到过类似"这里建议优化一下"的 review 评论,看完之后完全不知道要改什么。这种废话多了,code review 的价值瞬间归零。我在 open-code-review 的 checklists 里同时定义了一条"评论撰写规范",可以贴在团队里:
- 必须给出具体位置,最好指到行号。
- 必须说明问题是什么,而不是只给方向。
- 必须给出修改建议,哪怕只是一个示例。
- 如果只是风格偏好,请用非阻断方式提出,别伪装成 error。
另一个有效动作是:要求 review 者在点 Request Changes 时必须关联 checklist 中的条目。没有关联就不允许合入。这样做能倒逼大家把评论落到规则上,减少无意义的情绪化反馈。
4.4 小改动也走全流程太慢,怎么提速
曾经有同事问我,改一个文案错别字也要跑 lint、测试、再等一轮 review,是不是有病。这个问题很现实。open-code-review 的解法是给 PR 分级。
最简单的方式就是看 diff 规模和变更文件类型。例如在 workflow 里加一个步骤,判断 PR 是否属于"可直接合并"类型:只修改了 Markdown、配置文件里的文案、测试 fixture 数据且改动行数小于 20 行,就自动打上skip-review标签,由 maintainer 一人确认即可。实现可以用 GitHub Actions 里自带的dorny/paths-filter等工具,判断后再决定是否运行完整 review job。
但要注意,分级不是给所有小改动开后门,而是放行那些确实不涉及业务逻辑和风险的改动。安全相关、数据库变更、核心模块改动哪怕只有一个字符,也一定要走完整流程。
4.5 新人参与评审时总找不到重点
新人最怕面对一个陌生模块的 PR,不知道从哪看起。我的解决方法是在仓库里放一份"新手指南",明确给出评审顺序:先看 PR 描述和 diff 全貌,弄懂这次改动想干什么;再按 checklists 从最容易发现的边界条件、错误处理开始;最后看测试覆盖是否与改动匹配。
另外,我会在 open-code-review 的初始化脚本里给新人配置一个"观察者模式"。在他们加入团队的前两到四周,review 评论只以 warning 级别发出,不阻塞合入,让他们可以安心提问题,不用怕说错。等积累了一些经验后,再切换到标准模式。实际跑下来,这个过渡期能明显降低新人的心理门槛。
5. 一段时间跑下来之后的一点体会
整套 open-code-review 方案我陆陆续续用了一年多,最直观的变化不是 PR 上的评论变多了,而是"无意义评论"明显变少,真正能拦住问题的评审意见出现了。自动化的硬检查和人工 checklists 分工之后,大家反而更愿意做 review 了,因为要盯的东西很明确,不用像无头苍蝇一样乱找。最后还是想提醒一句:别一上来就追求大而全,先把最小闭环跑起来,再加规则、加脚本、加周报。工具的价值从来不是堆得越多越好,而是每加一样东西,都要能回答清楚"它替人省下了哪块注意力"。这套方案以后还能往两个方向扩展,一个是和本地 IDE 检查联动,让问题在提交前就被发现;另一个是把历史评审数据训练成团队自己的风险提示规则。这些都是后话,先把流程跑顺,比什么都重要。