我接手过不少团队的代码评审流程,也见过各种"评审全靠吼、合并不等人"的场面。时间久了你会发现,代码审查难的根本不是"谁审谁"的问题,而是整个流程里充满了太多低价值的重复劳动,让人既提不起劲,又不知道该从哪里下手。所以当我自己动手整理一套可以长期跑的审查机制时,给这个项目起了个名字叫open-code-review——它不是一个高深莫测的框架,而是一套把"机器该管的交给机器、人该管的人来管"的审查工作流,把所有能固化的规则沉淀成看得见、摸得着的配置和模板。
这篇文章我会从为什么团队需要一套软件化审查流程讲起,带着你走一遍自动化审查流水线的搭建过程,再聊聊人工审查环节怎么靠模板和约定从"自由发挥"变成"有章可循",最后分享一些真实跑起来之后踩过的坑和调整经验。不管你是技术负责人、还是天天在 PR 里被 @ 的普通开发者,这套思路都可以直接复用到自己的团队里。
1. 为什么我把代码审查推进到"半自动化"阶段
先说一个我观察到的现象:很多团队嘴上说着要做 Code Review,实际执行起来却是一堆 PR 躺在列表里没人看,或者偶尔有人点进去,扫一眼发现没什么大问题就点了 Approve。真正的问题并不是大家不愿意花时间,而是审查本身缺少一个清晰的入口——打开一个改动后,你不知道该重点看什么,也不知道哪些问题到底该由谁来兜底,最后只能凭感觉。
在我重构这套流程之前,团队里每季度的代码审查满意度调查结果都不太好看。有同事直接跟我说:"我不是不愿意看,而是每次拉下来几百行改动,光是找有没有明显的低级错误就花了大半天,等找到了真正值得讨论的设计问题时,精力已经耗光了。"这句话给了我很大的触动:我们一直在要求大家"认真审查",却没有给"认真"这件事搭建一个可以落地的环境。决定引入 open-code-review 这个思路的初衷,就是想让机器把那些确定性强、重复度高、一眼就能判定的问题过滤掉,让人力集中到真正需要经验、判断力和上下文理解的部分上。
1.1 人工审查最耗时的 80% 都花在"机器能做的事"上
你回想一下自己平时审查一次 MR 的流程,大概率是这样的:先大致扫一遍文件列表,看看有没有格式错乱、明显的命名问题、丢失的异常处理,然后再往下读具体的逻辑分支,推断这次改动有没有破坏旧的行为。前半个环节其实消耗了大量注意力,但它带来的信息增量极低,因为这些问题完全可以由静态检查和规则引擎兜住。
我做了一个粗略的估算:一次正常的 MR,假设改动 300 行代码,开发者手动审查大约需要 30 到 60 分钟。其中将近 40 分钟都花在"找茬"上——找缩进、找拼写、找遗漏的空指针判空、找不符合团队规范的 import 顺序。这些工作如果交给脚本和 linter,几十秒就能完成,而且不会漏。省下来的时间,如果还有 20 分钟留给人工,你至少可以认真读完所有核心逻辑,针对边界条件和未来扩展性提出真正有建设性的意见。
这不是理论推演。在 open-code-review 落地后的第二周,团队里一位后端同事就在群里说:"我今天终于有完整的一小时去思考一个接口的参数设计该怎么调整了,而不是在 500 行 diff 里读那些多余空行。"
1.2 引入 open-code-review 之前的准备:先定义"审查标准"
很多团队以为上了工具就等于有了流程,其实是反过来的。没有明确的审查标准,工具只能替你产出更多无人理会的噪声;有了标准,工具才能成为执行标准的加速器。
我做的第一件事,是拉着组里的核心同事开了一次"代码审查规则盘点会"。每个人把自己在过去三个月的评审意见翻出来,分类统计哪些意见是重复出现、且规则明确的,哪些是一次性的设计讨论。结果很清晰:重复出现且规则明确的占比接近七成。比如"方法命名过长""缺少单元测试""未处理异常边界""使用了被团队明令禁止的 API"这一类。我们把这一批整理成一份《open-code-review 规则基线》,之后做的所有自动化配置,都是围绕这份基线展开的。
这一步很容易被跳过,但它恰恰决定了后面整个流程的成败。标准不清晰之前,自动化工具跑出来的结果只是"一堆建议",而当团队对标准达成共识,工具输出的每一条提示背后都有了共同语言。
2. 搭建审查流水线:机器先跑一遍,人再看增量
有了规则基线,下一步就是搭建真正的自动化流水线。我选用的技术栈没有追求什么新奇玩意儿,都是成熟且可以直接拼装的开源组件,核心思路是:在开发者每天都会看的 PR/MR 页面里,嵌入机器审查结果,并且只展示增量问题。
我当时选定的组合是Reviewdog + ESLint / 自定义 shell 检查脚本 + CI 机器人评论。Reviewdog 是一个能把各种 linter 输出转换成代码注释的工具,它最大的价值不是发现问题的引擎,而是"以 diff 为边界进行评论"的能力,这正好匹配我们要解决的核心痛点。下面这段配置就是我实际使用的 Reviewdog 配置文件的核心片段:
runner: eslint: cmd: npx eslint -f checkstyle ${target_files} format: checkstyle level: warning filter_mode: added fail_on_error: false name: eslint这里面最关键的是filter_mode: added,意思是只在本次改动新增的代码行上报告问题,存量代码里的历史遗留问题一概不打扰你。我之前走过一段弯路,刚开始没加这个配置,结果机器人一上来就把仓库里积累了几年的老问题刷了满屏,开发者要么无视、要么烦躁,效果极差。
除了 ESLint,我还接入了一个自定义的脚本检查器,主要处理一些工具链上跨语言约定,比如"是否在修改核心配置时同步更新了文档""是否新增了 TODO 票据编号"这类规则。Custom runner 的写法也很简单,只要让脚本输出符合 Reviewdog 能解析的格式就行。
2.1 细粒度自动评论:机器只报"新增问题",别拿存量骚扰人
这一点值得单独拿出来说,因为它决定了整个自动化方案的用户体验。如果机器每次都在 PR 下面刷几十条评论,其中一大半还是历史代码里的旧账,开发者一上来就产生对抗情绪,后面说什么都听不进去了。我做过对比测试:开启filter_mode: added之后,一个中等规模的 PR 评论数量通常从 30 条以上降到 3 到 8 条左右,每一条都精准指向前端改动里的实际问题,处理起来非常有动力。
实现增量过滤并不复杂,Reviewdog 和 GitHub Actions 都原生支持这个逻辑。关键是团队要立一个约定:存量问题不等于本次责任,但也不能放任不管,应该单独走"技术债清单",而不是混在代码评审里。这样做既照顾了开发者的心理体验,又保留了后续清理债务的可能。
2.2 核心规则怎么挑:少而精,避免噪声淹没信号
很多团队配置 lint 规则时容易走极端,要么全开,恨不得官方规则表里所有项都勾上;要么全关,只留几个保守的默认值。全开的结果就是规则之间互相冲突、误报频出,全关的结果是工具形同虚设。
我的建议是按照"频次高、判定客观、修复成本低"这三个维度来挑选规则。举个例子,在 JavaScript/TypeScript 代码里,我优先开启的是no-unused-vars、no-undef、eqeqeq、prefer-const这一类,它们几乎不会产生误判,一旦报出来就真有问题。对于像max-lines-per-function这类偏风格化的规则,我反而没有在 CI 里开启,因为这类规则的判定跟具体业务场景强相关,报出来容易引发无意义的争论。
每种语言都有一套这样的"高信噪比规则集",找到它并不难,难的是取舍时的克制。open-code-review 的配置仓库里我把每一类规则都写了备注,说明为什么会开、遇到误报时怎么调整。这份文档后来成了新人入职时理解团队规范最快的入口。
3. 人工审查环节的"系统化":让共识沉淀成模板
把机械问题交给机器之后,剩下的工作就是真正的人工审查了。但人工审查也有自己的痛点:每个人对"什么才算严重问题"的标准不同,不同审查者的意见风格差异巨大,有的人喜欢在代码里发一堆小作文,团队管理成本很高。
open-code-review 在人工环节做的事情,是把那些"大家本来就认同的共识"沉淀成模板和约定,减少每次沟通的重复表达。业界流行的Conventional Comments格式就是一个很好的起点。它把评审意见分成nit、suggestion、issue、question、praise等类型,审查者通过前缀标明问题的性质,开发者收到意见后能快速判断优先级,而不是在几百字的长评里猜测对方到底想要自己做什么。
我还在团队内部推广了另一个实用工具:PR 描述模板。很多人把 PR 描述当成走过场的表单,随便写一两句话就提交。我设计了一份模板,强制要求填写"这次改动要解决什么问题""关联的 issue 链接""测试覆盖情况""有一定设计取舍的地方",并且把这些字段放在 PR 页面的最显眼位置。这样审查者打开一个 PR 时,首先看到的是上下文而不是代码,理解成本直线下降。
3.1 约定一套原子化的提交粒度
讨论完"怎么看",接着就要聊"怎么提交"。我见过最让人崩溃的 PR,是那种把十多个不相关文件的改动全塞在一个 commit 里,有重构、有修 bug、有加需求、还有顺手改的配置文件。审查者在这样的 diff 里别说找问题了,连理清逻辑顺序都费劲。
我在团队里推动的约定非常朴素:一个 PR 尽量只解决一个明确的问题,如果确实有多个改动,用多个 commit 把不同性质的改动分开。同时要求 commit message 遵循 Conventional Commits 规范(feat、fix、refactor前缀),这样 Reviewdog 的 CI 脚本甚至可以根据 commit 类型选择不同的检查重点。比如refactor类提交只做回归检查,不强制要求补充新测试;feat类提交则必须有测试增量,否则直接拦截。规则一旦和提交类型绑定,团队的执行力立刻提升了一个档次。
3.2 审查清单放在哪里?放在 PR 模板里
审查清单是另一个容易被忽略却能大幅降低沟通成本的设计。团队里经常出现"审查者不知道该项目导出的约定,开发者也忘了对照"的情况,一张显眼的 Check List 能同时提醒双方。
我的 PR 模板精简到了十五项,分为"功能性验证""代码质量""测试覆盖""发布影响"四个区域。每一项都是一个简短的问题,比如"是否检查过改动对旧数据的影响""是否补充了负责锁外开发的异常分支测试"。这些问题不是凭空想出来的,是从过去半年真实发生的线上事故和评审失误里提炼出来的。每过一段时间我会把新发生的问题补充进去,让模板跟着团队的经验一起进化。
文档里的效果很直接:过去时常出现的"改了配置忘了通知运维""改了接口忘了同步前端 Mock"这类问题,出现的频率明显下降,因为开发者提交之前必须逐项打勾,审查者也会逐项核对。这不是靠自觉,而是靠流程兜底。
4. 机器评论和人工意见之间的信任关系,怎么逐步建立
任何自动化工具投入实际使用的前一周,都会面临团队成员的质疑:"这个机器人到底靠不靠谱?"如果一开始输出了一些让人摸不着头脑的评论,或者漏掉了明显重要的问题,大家立刻会对整个体系失去信心。
我在 open-code-review 项目里花了不少精力处理"信任建立"这件事。方法不算复杂:第一,所有机器评论都明确标注产生规则和出处,让人能追溯到底是因为哪条规则、哪段逻辑被触发的;第二,设置了灰度观察期,在接入的第一周内,CI 只对规则报告(warning)不拦截合并(error),让开发者先感受一下评论的准确率;第三,任何规则误报都允许以"关闭条目 + 说明原因"的方式快速反馈,我每周会统计一次误报率,连续两周误报率偏高的规则会被降级处理。
这套机制运作下来,机器评论的采纳率从最初的 50% 左右逐步升到了 85% 以上,开发者开始把机器当"认真读过代码的同事",而不是"没事找事的 CI 程序"。信任一旦建立,后续规则的增加和调整就变得顺理成章了。
4.1 常见翻车点一:规则改得太猛,导致 CI 常红
第一次尝试大规模接入自动化审查时,很容易犯一个错误:把一堆规则一股脑全丢进 CI,并且设置成不通过就不让合并。结果就是每个 PR 都被一堆小问题卡住,开发者的体验极其糟糕,纷纷来找我"说理"。
后来我调整了策略,把规则分成三档:block(必须修复,不修不能合并)、warning(建议修复,允许在 PR 中讨论)、silent(只做统计,不上报给个人)。新引入的规则一律先以warning档位跑半个月,观察误报率和命中率之后再决定是否升级为block。这样既保证了 CI 不会变红到让人崩溃,又保留了规则的强制力,是一个渐进式增强的安全做法。
4.2 常见翻车点二:机器评论太多,反而没人看
另一个极端是规则开得不够克制,评论频率高但大多价值不大。开发者打开 PR,看到机器评论比人工评论还多,而且不少是"这个变量可以改成 const"类的琐碎建议,久而久之习得性无助,就彻底不看了。
解决这个问题,我强烈推荐"评论降噪三原则":一是只报告新增代码上的问题,上面已经说过;二是同类型问题聚合到一条评论里,不要每个文件都单独刷屏;三是把"必须要做"和"可以不做"两类意见明确分开,让开发者可以放心忽略掉非阻塞性的内容。Reviewdog 的report-style有github-pr-check和github-pr-review两种模式,前者会把检查结果呈现为 PR 页面的批量 Check,后者则是逐条评论。我实际对比后更推荐前者,因为它同时满足"人能看到"和"不刷屏"两个要求。
5. 人工审查本身的运行方式,值得像软件一样迭代
自动化工具跑顺畅之后,很容易掉进下一个误区:以为所有评审问题都可以被工具定义,人工只需要看看工具输出就完事了。但代码评审本质上是人的活动,它涉及沟通、争议、拍板,这些完全无法被脚本替代。open-code-review 项目走到后期,我把更多精力放在优化人工审查的"运行方式"上。
一个很实际的经验:评审要考虑疲劳管理。人的注意力是有限资源,如果规定所有 PR 必须在工作日下午集中评审,效果往往很差。我们后来把审查拆成了两个环节,上午安排半小时快速扫评(主要看自动化报告和 PR 描述),下午预留一小时深度评审(处理真正需要思考的问题)。这样安排之后,评审质量有肉眼可见的提升。
另外,团队要明确处理争议的机制。人工评审中出现分歧是很正常的,但如果没有仲裁规则,分歧容易演变成"谁的资历深谁说了算"。我给团队定的规则是:设计类问题先不阻塞合并,拉一次设计评审会专门讨论;规范类问题以团队约定为准,不搞个人偏好。这个简单的规则让很多原本僵持不下的评审快速收敛。
5.1 审查节奏和注意力分配:不搞"一次性大杂烩评审"
开发者经常犯的另一个错误,是把大量时间集中到某一天去"补审查"。比如周末囤了十几个 PR,周一打开一起看,结果每一个都看得不深入,流于形式。这和代码提交一样,同样需要碎片化处理才能保证质量。
我在实践中摸索出一个适合自己的节奏:每天早到后留 20 分钟扫办公室合并的请求里的自动化报告,把明显没问题的先处理掉,只把需要深度思考的留下来;下午集中一小时进行真正的逻辑走读。这样一来,每个 PR 等待审查的时间变短了,评审者也不用因为面对一堆阻塞需求而焦虑。配合 CI 的自动报告,大多数 PR 从提交到获得首轮反馈的时间控制在 2 小时以内,团队的整体交付节奏明显变快了。
5.2 自动化报告的演化:度量不等于目标
最后还想提醒一句:给自己的审查流程加度量指标要非常小心。我看到过一些团队喜欢统计"每位开发者的评审条数""每百行代码发现的 bug 数",然后用这些数据去排名或者考核。这几乎是必然会把流程带向形式主义的做法。
open-code-review 的做法是只保留少量用来判断"流程是否健康"的指标,比如 PR 平均周转时间、自动化规则误报率、被关闭的 warning 比例等。这些指标帮助我判断这套流程是不是还在正常运作,而不是给任何个人打绩效。度量是用来发现系统问题的,不是用来挑人的,一旦搞反了,再好的工具也会被用坏。
6. 跨语言团队落地时,怎么把方案扩展出去
如果你的团队不止一种技术栈,自动化配置的通用性就成了必须解决的问题。我所在的小组同时维护 Node.js、Python 和一部分 Go 服务,每种语言的 lint 工具和生态完全不一样,如果每个仓库都独立配置一套规则,维护成本会高到你无法坚持。
我采用的策略是建立一套统一的"审查编排层"。简单说,每个仓库的 CI 里只调用一个统一的脚本入口,这个脚本负责发现项目使用的语言、选择对应的规则集和 linter、并输出统一格式的结果给 Reviewdog。这样一来,团队里新增一个仓库时,只需要注册它的语言栈,就能自动获得默认规则集,完全不用重复配置。这个编排层的代码量并不大,核心逻辑如下:
#!/usr/bin/env bash # scripts/review.sh # 根据仓库语言分发到不同的 lint 引擎 LANG_STACK=$(detect_language_stack) case "$LANG_STACK" in "node") run_eslint ;; "python") run_flake8_and_mypy ;; "go") run_golangci_lint ;; *) echo "unsupported language stack: $LANG_STACK" exit 0 ;; esac检测语言栈的逻辑也很直白,看根目录下有没有package.json、pyproject.toml、go.mod这类标记文件即可。这套方案加上不同语言各自的高信噪比规则集,让团队其他成员接入 open-code-review 的时间从一整天缩短到了半小时以内。
6.1 跨语言规则集的维护机制:定期的规则评审会
规则集不能只停留在"刚配置好的那一刻"。随着团队业务的变化,有些规则会失效,有些规则需要收紧,还有新出现的错误模式需要加入。我把规则集的更新当成一个周期性的技术活动,每半个月花 15 分钟过一遍最近产生的误报记录和漏报记录,决定哪些规则要改、哪些要删。
这种定期的规则评审会还有一个额外的好处:它记录了团队对代码质量认知的演进过程。刚开始大家可能还停留在"别写错别字、别留未用变量"这种层面,几个月之后,规则集里开始出现"必须为对外接口补充重试逻辑""禁止在循环里同步请求外部依赖"这类和业务紧密绑定的事项。规则演化的深度,本质上反映了整个团队工程能力提升的速度。
6.2 终极边界:自动化永远替你跑不完设计评审
尽管自动化体系做得再完善,有些事情它依然做不了——比如判断一个模块的边界划分是否合理、一个接口的抽象层次是否恰当、一个技术方案投入的成本是否值得。这些需要业务上下文、需要团队历史背景、甚至需要一些行业经验才能做出的判断,是代码评审中最有价值的部分。
open-code-review 的定位从来不是"用机器替代人",而是"用机器把人的时间从琐碎中解放出来,投入到真正需要人的地方"。如果有一天你的团队发现自动化报告已经把所有能发现的问题都发现了,剩下的都是设计博弈和权衡讨论,那说明这套流程真正跑通了。
最后再分享一点个人的实际体会:任何流程改造,最难的不是技术方案本身,而是人的习惯和预期。open-code-review 从设计到稳定运行,花的时间大概是三到四周,但真正让团队愿意信任这套玩法、愿意按规则走下来的,其实是在前期花掉的那一周"共同制定标准"的准备工作。工具是死的,标准是活的,而标准只有被所有人认可才叫标准。所以如果你也想在团队里推一套这样的流程,我会建议你从最基础的讨论开始,而不是从装 CI 插件开始。