用open-code-review把代码评审从“人肉检查”变成“人机协作”
发布时间:2026/10/19 13:20:02 锦皓数字建站

评审会上的三个小时一半浪费在“机器能搞定的事”上。这是我坚持做代码评审的第十年也是我决定把一部分评审工作交给 open-code-review 的第六个月。促使我做这个决定的是一次特别典型的评审会一个改动只有三百行的 PR团队五个人围在一起花了一个半小时讨论的重点不是接口设计合不合理、异常处理边界有没有漏洞而是“这个变量名是不是应该叫retryCount而不是retry_num”“这里空行多了一行”“那个 magic number 是不是应该抽成常量”。当然这些问题本身没有错但它们根本不值得五个人坐在那里争论。真正的问题——一个我在会后自己又看了二十分钟才发现的并发边界——反而被挤到了会议的最后十分钟。那次之后我彻底想明白了人工评审的最大浪费不是人不够认真而是大量低层次、可被规则描述的问题占用了评审者最宝贵的注意力。人应该去盯架构、盯业务语义、盯那些机器看不懂的东西而不是反复争论命名和格式。我花了两周时间调研了几个开源的代码评审工具最后决定深度使用 open-code-review并且在生产环境里跑了六个月。这篇文章就从头讲一讲这个工具到底能做什么、怎么部署、规则怎么定制、以及我踩过的那些坑。如果你也受够了评审会上没完没了的“风格之争”这篇文章应该能帮你省下不少时间。1. 评审会上的三个小时一半浪费在“机器能搞定的事”上1.1 先分清评审意见的两种类型我在团队内部做过一次简单的统计过去一个季度里的评审意见如果按性质分类大概能分成下面这样。意见类型占比典型例子机器能否自动发现风格与格式约 35%命名不规范、行长超过限制、空行缺失能代码异味约 25%重复代码、过长函数、复杂度过高能潜在缺陷约 20%空指针、资源未关闭、异常被吞部分能架构与设计约 10%模块边界不合理、扩展方式错误很难业务语义约 10%需求理解偏差、规则遗漏很难这个统计结果非常直白接近三分之二的评审意见本质上都是可以被规则描述、被工具自动发现的问题。剩下三分之一才需要真正的人类智能。所以问题的关键不是“要不要用工具替代人”而是“怎么把机器能干的活先干完把人从重复劳动里解放出来”。open-code-review 这个工具就是围绕这个思路设计的。它要做的事情不是像某些 AI 评审工具那样直接给你一个“整体评分”而是老老实实地把每次提交的 diff 拉下来逐行分析针对具体的问题给出具体的评审意见并且把意见作为评论直接回写到代码平台的 MR/PR 下面。1.2 为什么我不直接用 SonarQube 之类的重量级方案你可能会问代码静态分析这件事SonarQube、CodeClimate 不是早就做了吗我也用过但实际用下来的感受是它们更像是“代码体检报告”而 open-code-review 更像是“评审搭档”。前者给你一堆图表和统计数据告诉你“这个项目的坏味道指数从 3.2 涨到了 3.5”但这对一次具体的 MR 评审来说帮助有限。代码评审的核心载体是 diff而不是全量代码库。一个改动引入了什么问题应该在那个改动的上下文里说而不是等合并之后再去翻报告。open-code-review 的工作方式天然贴合这个场景它针对每一次 MR/PR 拉出新增和修改的行在这个范围内跑规则、做分析、给意见意见直接挂在对应的代码行上。评审者打开 MR 的时候看到的是“这一行有什么问题应该怎么改”而不是一个冷冰冰的分数。2. open-code-review 到底怎么工作一次审查请求的完整旅程2.1 核心模块拆解open-code-review 的架构并不复杂拆开来看就四个模块。对接层Integration负责和各代码托管平台打交道。GitHub 的 Pull Request、GitLab 的 Merge Request、Gitea、Bitbucket它都支持。这个层做的事情就是拉取 MR 的元信息、获取 diff 数据、在分析完成后把评论回写。分析引擎Analyzer这是核心。负责把 diff 拆成带行号的数据结构然后跑规则。规则分内置和自定义两类内置的是一些常见问题的启发式检查自定义的则是团队根据自己的项目特点写出来的插件。规则加载器Rule Loader负责加载、配置、编译规则。它支持一个 YAML 配置文件可以控制哪些规则启用、哪些规则的严重级别、哪些路径要被排除。输出层Reporter把分析结果整理成统一的评审意见结构根据平台 API 的能力选择以“单条评论”还是“批量 Review”的方式回写。这四个模块之间通过一个核心的“审查请求”对象串联。这个对象包含MR 编号、变更元信息、每个文件的 diff 内容、每条新增行的原始行号和新文件行号。规则插件不直接接触平台 API只处理这个对象。好处是你写一个规则在所有支持的平台上都能跑不需要关心 GitHub 和 GitLab 的 API 差异。2.2 从 Webhook 到评论回写一条完整链路我以 GitHub 平台为例讲一下它处理一次 PR 的完整过程。你理解这个流程之后部署和排错都会容易很多。触发当 PR 被打开或者有新的 commit 推上来时GitHub 会发出pull_request事件的 Webhook。open-code-review 的 webhook 服务收到这个事件后会先做一次基础判断这个 PR 是不是 draft、目标分支是不是被配置排除、有没有携带[skip review]之类的关键字。如果命中跳过条件直接结束。拉取上下文通过 GitHub API 获取 PR 的详细信息和完整的 diff。这里有一个细节大 PR 的 diff 可能非常长如果一次性全量拉取分析耗时和 API 超时风险都会很高。所以它按照 commit 为基础单位找出当前 PR 相对目标分支的完整差异集再用 Git 的原生对象语义来处理避免把整个仓库 clone 下来。这也是当时打动我的一个点——它不需要完整代码库的 clone分析速度非常快。结构化解构拿到原始 diff 文本之后分析引擎会把它解析成结构化数据。每一个 hunk 里都包含改动前后的行号和具体内容。这一步非常关键因为后面所有规则都是在这些带行号的行上做判断的行号错一个评论就会挂错位置。执行规则解析完成之后规则加载器把启用的规则全部跑一遍。每条规则接收到的是“这个文件里改动的所有行”以及相关上下文。规则输出一个或多个Finding每条 Finding 带严重级别、具体行号、问题描述和修改建议。去重与过滤所有规则跑完之后分析引擎会把重复的意见合并比如同一行被三条规则报出问题就合并成一条综合意见。还会把“已经被解决过”的意见过滤掉——如果某条意见在之前的 commit 里提过而新 commit 已经改了就不再重复提。回写最后输出层把意见格式化通过 GitHub API 以review评论的形式提交到 PR 下面。每条评论挂到具体代码行上同时整个 PR 会收到一个总概览。这套流程跑完平均耗时在十几秒到一分钟之间。相比我们以前人工评审动辄几十分钟的节奏这个速度是质变。2.3 它支持哪些平台怎么选我这半年主要在 GitHub 上重度使用同时在一个内部项目里接了 GitLab。两个平台的行为逻辑有些差异接的时候需要注意。平台事件触发方式评论回写形式注意点GitHubWebhookpull_request以 bot 账号发起reviews评论支持对指定 commit 发起 reviewGitLabSystem Hook / Webhook以 bot 用户提交评论评论里需要带上位置信息否则会挂到全局GiteaWebhook以 bot 用户提交评论行内评论支持较晚建议升级到新版BitbucketWebhook以 bot 用户提交评论权限模型比较特殊token 需要单独配权限GitHub 上它的表现最好因为 GitHub 对 line-level review 评论的 API 支持最成熟。GitLab 上跑起来问题也不大就是回写评论的时候如果平台版本较老行内评论可能挂不上去会退回全局评论。所以如果你也是 GitLab 用户建议确认一下实例版本太老的话先升个级再接。3. 二十分钟接入 CI部署细节与最小权限配置3.1 Docker 部署是最省心的方式open-code-review 官方提供了 Docker 镜像我团队生产环境用的就是这种部署方式。镜像把 webhook 服务和分析引擎打包在一起你只需要挂一个配置目录进去。docker run -d \ --name open-code-review \ -p 8080:8080 \ -e PLATFORMgithub \ -e GITHUB_TOKENghp_your_token_here \ -v /data/ocr/config:/etc/ocr \ open-code-review:stable简单解释一下几个参数PLATFORM告诉它以什么模式运行。这里填github就是 GitHub 模式默认也会开启对应的路由路径。GITHUB_TOKEN是访问 GitHub API 的凭证。后面会说权限怎么给最小化。挂载目录里的ocr.yaml是主配置文件所有规则开关、路径排除、跳过策略都放这里。配置文件的骨架长这样server: port: 8080 webhook_secret: 某个随机字符串 review: enabled: true skip_labels: [skip-review] exclude_paths: - generated/** - **/*.pb.go max_files_per_pr: 200 platform: github: # 拉到所有文件还是只拉 diff include_context: truewebhook_secret这个字段要特别重视GitHub 在发 Webhook 的时候会带上一个签名头服务端拿这个 secret 做校验。如果不配置任何人都能往你的服务端点伪造请求触发一堆无意义的分析。这个 secret 需要跟 GitHub Webhook 配置里的 Secret 保持一致。3.2 接入 GitHub Actions部署好服务之后接入 CI 其实有两种姿势。一种是直接用 GitHub Actions 在每次 PR 时拉一个容器起来跑另一种是平时常驻一个 webhook 服务。我在团队里两种方式都试过结论是GitHub Actions 的临时跑法更省钱但常驻 webhook 更合 TC 评审的习惯。原因是常驻模式里bot 是持续在线的它可以在你每推一个 commit 之后立刻更新评审意见而不是等整个 CI 流程跑完再重新起一个容器。别小看这个差别大仓库的 CI 动辄几分钟如果多等一轮评审意见就失去了“即时感”大家会慢慢习惯不看。如果你还是想用 Actions 方式模板大概是这样的name: open-code-review on: pull_request: types: [opened, synchronize] jobs: review: runs-on: ubuntu-latest steps: - name: Run open-code-review run: | docker run --rm \ -e PLATFORMgithub \ -e GITHUB_TOKEN${{ secrets.OCR_BOT_TOKEN }} \ -v ${{ github.workspace }}:/workspace \ open-code-review:stable \ --pr${{ github.event.pull_request.number }}但注意这个方案有一个绕不开的问题它是在 runner 上拉一个临时容器跑runner 要能拿到完整的仓库上下文所以前面还得先加一个actions/checkout步骤。而且它只对“当前 runner 里的代码”分析如果仓库比较大checkout 本身也是不小的时间开销。3.3 关于 Token 权限我踩过最深的坑第一次部署的时候我图省事直接给 bot 配了一个仓库的write权限 token。用了三天就发现不对GitHub 的告警邮件开始提醒我这个 token 有仓库管理权限而且因为 token 权限太大任何人只要拿到这个 token就能直接往目标仓库推代码。正确做法是申请一个fine-grained personal access token权限范围只勾选Actions只读读取仓库的工作流信息。Pull requests读写让 bot 能以 review 评论的形式回写意见。Contents只读读取代码内容做分析。只给这三个别的一律不勾。Token 归到一个独立的 bot 账号下不要用任何团队成员的私人账号。这一点非常关键因为 bot 的评论会以账号身份展示在 PR 里如果用的是私人账号看评论的人还得猜这是谁提的而且离职换人的时候 token 也不好交接。4. 规则引擎不是写死的检查器如何把团队的规矩沉淀成插件4.1 内置规则覆盖了哪些问题open-code-review 内置的规则分三大类我在表里列一下类别规则示例严重级别防御性检查未判空的直接调用、资源句柄未关闭、捕获异常后不处理error代码风格函数过长、嵌套过深、命名不符合项目规范warning仓库卫生提交了密钥、遗留调试打印代码、TODO 堆积过多warning其中最有价值的是“提交了密钥”这条。它不光是检查文件名和内容里的普通字符串还会去扫描类似的模式AWSAKIA开头的 access key、GitHub 的ghp_token、私钥的 PEM 块。我们曾经有一个同事不小心把一套测试环境的数据库连接串提交进了仓库被这条规则拦了下来避免了一次事故。不过内置规则在大部分项目里只能覆盖基础场景。真正让这个工具发挥价值的是自定义插件能力。4.2 写第一个自定义规则一个完整的插件长什么样open-code-review 的插件机制支持用 Python 写。每个插件就是一个继承Rule基类的类实现match方法。我拿我们团队的一个真实场景举例我们要禁止在业务代码里使用print()做日志但允许在测试和命令行工具里用。from ocr import Rule, Finding, LineContext class NoPrintInProduction(Rule): name no-print-in-production severity warning description ( 业务代码里不允许用 print 输出日志 请通过项目的 logging 模块记录。 ) def should_run_for(self, file_path: str) - bool: # 测试和脚本目录不检查 if file_path.startswith(tests/): return False if file_path.startswith(scripts/): return False # 只检查 Python 文件 return file_path.endswith(.py) def match(self, diff_lines, context: LineContext): for line in diff_lines.added_lines: stripped line.content.strip() if stripped.startswith(print(): yield Finding( lineline.new_number, message业务代码里发现了 print() 调用, suggestion用 logger.info() 或 logger.warning() 替代, detail( 示例\n from your_project.logging import logger\n logger.info(这里写你的日志内容) ) )写完之后把文件放到配置目录下的rules/目录然后在ocr.yaml里启用它rules: enabled: - no-print-in-production packages: - /etc/ocr/rules这里的核心设计是should_run_for和match分离。前者决定“这条规则要不要管这个文件”后者只处理真正需要检查的行。我一开始没写好should_run_for结果测试文件里到处都是print被规则炸了几十条意见后来才明白路径过滤这件事必须放在规则自己的逻辑里而不是靠全局排除去兜底。4.3 规则里能拿到什么上下文很多想写插件的人会问我match里到底能拿到哪些信息简单说open-code-review 给插件提供的是一个LineContext对象它包含当前文件路径和语言类型。所有新增行的内容以及对应的新文件行号。该行所在的函数或类名如果有能力推断的话。相邻的几行未改动内容用于处理上下文相关的判断。这个设计比单纯给一个“当前行”要实用得多。举个例子我们的规则里有一条是“禁止在交易状态机里直接返回结算结果”这个判断如果没有函数名做锚点根本无从下手。4.4 AI 通道它真的有用但要会用前提open-code-review 在近几个版本里加入了一个可选的 AI 分析通道可以接入本地或远程的 LLM 服务。我测试过之后结论是AI 适合抓语义类问题不适合做规范类检查。规范类问题比如命名、格式、复杂度用规则引擎做又准又稳定。AI 反而可能因为模型本身的随机性给出不一致的结论。但语义类问题比如“这段重试逻辑漏了一个退避策略”“这个并发场景下资源可能被重复释放”规则引擎写不出来AI 能给出有价值的提示。所以我现在的用法是规则引擎的检查结果作为“必须关注”的意见AI 的输出默认标记为“建议”。AI 意见还要经过一个置信度过滤——低于阈值的直接丢弃避免把不确定的猜测变成噪音。这也解释了为什么 open-code-review 没有一开始就把 AI 作为默认主力而是作为一个可选的补充通道。用对了地方它是加分项用错了地方它会给整个评审流程带来更多的噪音。5. 调教三个月后我处理掉的四类误报噪音工具刚上线的时候团队里反馈最多的不是“它发现了什么问题”而是“它怎么又报了这种不相关的东西”。误报率直接决定了工具能不能在团队里长期活下去。我在这里把这半年处理掉的四类问题完整复盘一下你在接的时候可以直接避掉。5.1 第一类生成的代码和第三方代码没有被排除第一次全量跑下来generated/目录下爆了三十多条意见。这些文件是 protobuf 编译器生成的或者前端自动生成的类型定义规则再准也不应该管它们。解决方式是在配置里加上排除规则review: exclude_paths: - generated/** - **/*.pb.go - **/*.g.cs - vendor/** - node_modules/**这里要特别提醒一点排除规则不要写得太宽。有些团队图省事把**/*_test.py都排除掉后果是测试文件里大量无意义的断言检查反而没人管了。合理的做法是单独排除生成物和依赖目录测试代码仍然保留在评审范围内只是给它单独配置不同的规则级别。5.2 第二类diff 行号错位评论挂到了错误的代码行这是最影响信任感的问题。某条规则明明说的是 A 行它却挂到了 B 行上团队里有人反馈“这个工具是不是疯了”。排查之后我定位到了根因diff 文本里的 hunk header 格式是 -起始行,行数 起始行,行数 而不同平台给出的行号基准不一样。GitHub 的 diff 默认用新文件的行号但有些平台或某些 API 模式下返回的可能是旧文件的行号。open-code-review 虽然在这个问题上做了兼容但如果你接的是冷门平台或者自己在规则里使用了line.number而不是line.new_number就非常容易踩中这个坑。我的建议是写自定义规则时一律用new_number来定位新代码除非你明确知道自己要定位的是旧代码的行号。这行 bug 当时花了我们一个下午去排查最后发现就是我在一个规则里用错了字段。5.3 第三类规则太激进把“建议”当成“必须”我们团队的新人写过一段代码用dict.get()之后直接对返回值做了二次操作。这个写法在某些场景下确实不安全但规则直接报成了error并且 bot 会在 PR 上挂一个“Changes Requested”的状态。结果新人很紧张PR 评审会还没开就慌了。后来我调整了策略严重级别为 error 的规则一定是会导致线上事故或明显 bug 的问题。像“建议用更安全的写法”这类的统一降到warning不阻塞合并。这个调整看着小但对团队心态的影响非常大。一个工具的留存活率很大程度上取决于它让人“烦”的程度。如果它三天两头因为一些可改可不改的问题 block 合并最后的结果一定是大家集体把它绕过去。5.4 第四类重复意见在同一行反复出现最初版本里如果一个函数里既有复杂度过高的问题又有命名不规范的问题bot 会在同一个函数区域刷出七八条评论。后来我研究了一下它的合并逻辑发现同一个文件里的意见会按行号聚合成一条综合评论但跨文件的意见仍然会各自独立出现。对于大 PR可能一次就刷出上百条。解决方式是配置“单 PR 最大意见数”和“单文件最大意见数”。review: max_comments_per_pr: 50 max_comments_per_file: 10超出上限之后bot 不会把后面的意见发到 PR 里而是在总概览评论里提示“还有 N 条意见被截断”。这个上限的默认值是 80 和 20按我的使用经验团队小型 PR 一般碰不到上限大 PR 的截断保护是件好事避免把 PR 变成刷屏现场。其实工具自带的“作为 Review 评论回写”模式已经帮了大忙所有意见打包成一次 review不会像单个评论那样刷屏。但我还是建议把上限配置开起来因为一个没有任何截断机制的评审工具迟早会在某个大型重构 PR 上把所有关联人都惹毛。6. 这套流程跑下来团队评审发生了什么变化6.1 评审时间的真实变化最后说点软性的观察。接入 open-code-review 之前我们团队每周的评审时间加起来大概是人均 4 到 5 小时。这包含了会前自己看代码的时间以及坐到一起开评审会的时间。工具跑了三个月之后我做了一次简单的数据盘点纯评审会议的时间缩短了大约一半人均每周降到了 2 到 3 小时。不是大家不认真了而是那些风格、格式、明显的坑bot 已经提前标记好了。评审者打开 PR扫一眼 bot 的意见重点看它没发现的部分——业务逻辑、架构设计、异常边界。这个变化其实很有意思。以前大家开评审会前一个小时都在讨论低层次问题等到真正关键的架构问题浮出水面时注意力已经消耗得差不多了。现在低层次问题在会前就被 bot 清了一遍开会时大家的精力是满的讨论质量明显提高了。6.2 新人上手反而更快了一个我没想到的副作用是open-code-review 意外地成了新人的“评审导师”。新同事刚进团队对公司代码规范的理解几乎为零。以前他要通过一次次被 reviewer 纠正来学习“我们这里应该怎么写”这个过程既漫长又容易产生挫败感。现在 bot 会在他提交代码的几分钟内给出具体的修改建议而且很多建议里带了可以直接应用的代码片段。在新人还没面对真人 reviewer 之前他已经通过 bot 把一批常见问题改掉了。我带新人的时候还会刻意帮他们过一次 bot 的规则配置告诉他们这些规则不是随便来的每一条都对应着团队踩过的坑。前辈在评审时说的每一句话、让改的每一次代码如果频率够高、足够客观就应该沉淀成一条规则。这样写出来的规则既不是书上的教条也不是某个人拍脑袋的偏好而是踩着真实的 bug 堆出来的经验。6.3 规则不是越多越好如果只给你一个建议我的建议是规则宁缺毋滥。每加一条规则之前问自己三个问题这个问题是否客观、可描述、可验证这个问题是否在过去的评审里反复出现这个问题是否值得打断一次代码提交三条都满足才值得沉淀成规则。规则越多误报率越高噪音越多团队对工具的信任就越低。我见过有团队接了一个类似的工具第一个月就启用了八十多条规则结果 PR 下面密密麻麻全是机器评论真正的人工评审意见反而被淹没了最后团队集体要求把工具关掉。我自己的策略是每一次规则评审都当成一次正式的代码评审来对待。规则要过代码评审要有开发者签字确认要写清楚它想要解决的问题对应的真实案例。这样做下来我们团队的规则库从最初的内置二十条加上自研的十几条到现在稳定在三十条左右每一条的产出都很高。6.4 一个理解工具但也要理解人的岗位分工说到底代码评审真正值钱的从来不是“指出你这里多了一个空行”的能力而是“我担心你这个重构会破坏支付回调的幂等性”的能力。open-code-review 把前者自动化了之后人和人之间那些更重要的讨论才有了被看见的机会。在一次内部复盘会上我听到团队里一个背景比较资深的工程师说了一句话我至今印象很深“以前代码评审像在检查作业现在检查作业的活有工具干了我们终于可以聊点真正的设计了。”作为这个工具落地的推动者这句话是对我这几个月工作最好的肯定。如果你也想在自己的团队里跑起来从今天写一条排除规则开始就行。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。