2026/10/19 3:49:05

open-code-review:用规则引擎和AI辅助让代码评审不再扫雷

open-code-review:用规则引擎和AI辅助让代码评审不再扫雷 身边的后端同学经常跟我抱怨一件事代码评审Code Review这活儿越来越像“人肉扫雷”。PR一多起来团队成员既要赶业务进度又得逐行看别人写的代码经常是扫一眼、点个“看起来没问题”就合了。等上线之后出了事故大家再回过头翻diff才发现问题早就写在明面上只是没人发现。我太理解这种状态了因为我自己也经历过好几轮“评审内耗”花了一下午看几十个文件结果真正有价值的反馈没几条。后来我花了一些零散时间做了个开源项目 open-code-review目的很直接把代码审查里那些机械化、重复性高、纯靠人眼去盯的部分用自动化工具先过一遍让人力聚焦在真正需要思考的地方。它不是要替代人工评审而是在PR/MR提交之后、人工介入之前先做一层静态分析、规则扫描和可选的AI辅助建议。这套流程可以跑在本地Git钩子里也可以接入GitHub Actions、GitLab CI这类流水线最后自动生成带文件级、行级定位的Markdown报告甚至可以以机器人的身份直接在PR下挂评论。如果你正处于“团队评审流程形同虚设”“代码规范靠口口相传”“新人写代码没人细看”这几种状态这篇内容应该能帮上忙。我会从工具的整体设计思路开始再拆核心模块的细节然后讲一套可以照着抄的实施流程最后把我在实际使用中踩过的一些坑和排查思路一并聊一聊。1. 整体设计与思路拆解1.1 为什么不等现成的工具而是自己做一个市面上并不是没有类似的方案。SonarQube很重适合公司级统一平台但私有化部署、规则库维护都需要专人跟进。GitHub官方自带的Code scanning功能也够用但它跟仓库绑定得比较死换到GitLab、Gitea或者其他自建Git服务时能力就打了折扣。CodeRabbit、Qodo这类AI评审服务效果好但有隐私顾虑而且按量计费对于内部项目来说不一定划算。我当初定下做 open-code-review 这个工具时目标很明确轻量、可移植、规则可控、部署成本趋近于零。容器化之后本地跑也好、CI跑也好本质上都是“扔进去一个diff拿回来一份报告”。不想被某个平台绑定也不想为了一套规则库去养一个维护团队更不想让源码离开自己的机器。这一点对于很多中大型团队和注重代码资产安全的公司来说是刚需。1.2 目标场景和使用群体这个工具主要面对三类使用场景。第一类是个体开发者。自己在本地写代码用Git钩子在push之前自动扫一遍把低级问题提前暴露出来省得开PR之后被同事挑出一堆格式问题。第二类是中小型技术团队。团队规模不大没有专职的DevOps或者质量保障人员但是对代码质量有要求希望用最少的维护成本建立一个自动化的前置检查关卡。第三类是已经接入CI平台、但还缺一道“规则审查”的团队。他们把构建、测试、部署都自动化了唯独代码审查还是纯人工。open-code-review刚好可以卡在测试之后、部署之前作为流水线里的一个静态检查环节。适合参考这套方案的读者我建议是那些已经写过一段时间代码、知道Code Review大概是怎么回事、但还不太清楚怎么把这项活动“工具化”的人。不需要你有算法基础你只需要会用Git、会看YAML配置剩下的事情都是配置文件的事。1.3 项目结构上做了哪些取舍整体结构上我把它拆成了五个模块diff解析器、规则引擎、AI建议模块、报告生成器、平台适配层。模块拆分的原则很简单每个模块只做一件事模块之间用JSON而不是对象直接耦合。这么做的好处是极大的方便了后续扩展比如你要支持更多的语言、接更多的Git平台只需要替换对应模块而不会牵一发动全身。这里有一个取舍要讲很多同类工具会把“解析diff”和“规则检查”糅合在一起导致加一种语言的检查要改动核心逻辑。我在设计时直接把diff解析独立出来了先把Git diff转成结构化的文件变更列表再交给规则引擎处理。规则引擎只面对“文件路径、变更行号、变更前后的代码内容”这三样数据跟Git本身彻底解耦。这样一来本地Git和CI平台调用的其实是同一套核心逻辑只是入口不同。1.4 为什么“先分析、后匹配”比“逐行扫描”更高效我在最初版本里犯过一个错误就是直接把整个diff按行拆开然后逐行跑正则表达式。结果问题很多误报率高因为字符串里的内容也会被命中性能差某些大文件几千行变更逐行跑一轮正则要几秒上下文信息几乎丢失看报告的人根本不知道这行代码处于一个什么函数里。后面我全部推翻重来改成了两阶段架构第一阶段把diff按文件和代码块hunk组织起来重建出每个变更块的“前后语境”也就是变更行周围没变的那几行也一并保留。第二阶段规则引擎先把“代码块整体”过一遍预处理识别出它属于函数、类、条件分支、循环、字符串拼接还是其他结构再去做规则匹配。这样正则表达式有了上下文支撑误报率降低很多。举例来说如果只是逐行匹配一个字符串里的URL分段会被当成可疑的硬编码但有了结构信息后就能判断出这是字符串字面量可以直接跳过。这类细节问题在写规则的时候不觉得真正用起来的时候影响非常大。2. 核心细节解析与实操要点2.1 diff解析模块所有检查的数据基石diff解析是做一切检查的地基。Git原生的git diff输出格式是给人看的里面有文件头、hunk头、上下文行、新增行、删除行并且带有各种转义规则直接拿文本去解析很容易翻车。例如一个文件路径里如果带有空格Git会做特殊处理如果文件内容本身包含和---也会干扰解析逻辑。我在这块花了很多时间最终的实现思路是先调用git diff --unified3 --no-prefix --src-prefixa/ --dst-prefixb/拿到标准化输出然后再用一个增量式的解析器逐行处理维护一个状态机来记录当前处于哪个文件、哪个hunk、哪个区块。这里有个实操建议给到大家如果你们也要写类似的工具尽量不要自己去解析git diff的原始文本完全可以依赖git diff提供的机器可读格式。比如--numstat可以拿到变更统计--name-only可以快速拿到文件列表。真正需要精细到行级的场景再用我上面说的状态机解析。解析之后的数据结构大概是这样的{ file: src/calculator.py, changes: [ { hunk_id: 1, start_line_new: 15, lines_added: 3, lines_removed: 1, context_before: [def divide(a, b):, if b 0:], context_after: [ return result], added_lines: [ raise ValueError], removed_lines: [ return None] } ] }只有拿到这样的结构化数据后面的规则引擎才能稳定工作。直接拿文本流去匹配的后果就是边界情况永远处理不完。2.2 规则引擎设计可配置、有分级、支持白名单规则引擎是这个项目里最核心的部分。它的运行方式不复杂读取规则配置文件把每个规则转成一个“匹配器”再喂给代码块集合去做匹配。规则配置我设计成了YAML格式因为YAML可读性比JSON高且支持注释适合让团队成员直接review规则配置。核心规则有几个字段rules: - id: NO_LOGIN_HARDCODE message: 检测到硬编码的登录凭据 severity: critical pattern: | (password|passwd|pwd|secret|api_key|apikey)\s*[:]\s*[][^]{6,}[] file_pattern: *.py scope: all - id: AVOID_EVAL message: 检测到 eval/exec 动态执行代码请确认是否有注入风险 severity: high pattern: | \b(eval|exec|system|popen|os\.system)\s*\( file_pattern: *.py scope: codeseverity字段分为critical、high、medium、low四档。这个分级很重要因为在实际使用中不可能让所有规则都以同样权重出现。critical级的问题要阻断CIlow级的只作为提示不能因为鸡毛蒜皮的事把整个交付流程卡死。规则里还需要支持file_pattern来限定适用的文件类型不然Python的规则套在Java文件上全是误报。scope字段用于限定匹配范围是匹配整个代码块还是只匹配新增行、删除行、字符串字面量、注释等。我强烈建议在初始版本里把规则的匹配范围尽量限定到added_lines上。因为Code Review场景下我们关心的主要是“这次变更引入了什么”而不是“老代码里一直有什么”。老代码的问题应该单独提技术债不在本次评审里混为一谈。2.3 AI建议模块一种可选的辅助而非替代这是项目的可选模块默认不开启。它的作用是在规则引擎跑完之后把筛选出的可疑点而非全部diff交给大模型接口生成更自然的建议描述、给出修复示例甚至判断一下是否为误报。启用AI模块后分析工具输出的就不是干巴巴的“检测到密码硬编码”而是一段带有解释、示例代码和潜在风险说明的建议。我用的是OpenAI兼容的接口协议这样无论是官方的服务还是本地部署的推理服务只要兼容这个协议都能接入。关键是不能把整个diff一股脑塞给模型太大的上下文既慢又贵还会稀释注意力。我采用的方法是把“规则命中的位置前后文各5行命中规则的描述”拼成上下文再交给模型判断。调用时我用的是结构化输出的方式要求模型返回一段JSON包含reasoning、suggestion、is_real_issue这三个字段。is_real_issue那个字段尤其有用相当于加了一道AI过滤器能在一定程度上降低规则引擎的误报率。需要特别说明的是AI建议这块只是一个辅助手段千万不要把它当成判决书。它提供的是“一个可能的视角”最终是否修改还是人来定。想让它干活需要在配置文件里配上接口地址和密钥并且设置好模型名称、温度、单次预算等参数。2.4 报告与评论给人看的输出才有价值如果工具跑完之后只输出一坨JSON到终端那它是失败的。人不可能去读几千行的JSON来判断自己代码哪里有问题。报告生成器做的事情是把检查结果转成两种形式一份完整的Markdown报告和一段针对关键问题的简短摘要。Markdown报告按严重程度分目录展示结构大致是这样的问题列表、按文件分组、按严重程度分组、变更文件统计、AI建议摘录。CI里跑的时候会生成两个文件review-report.md和review-report.json。前者给人看后者给机器分析。PR评论这块我封了一层非常薄的适配器。目前主要支持GitHub的REST API和GitLab的Merge Request API逻辑很直接通过API把评论提交到对应的PR/MR会话下每条评论里带上文件路径、行号、消息级别。要接入其他平台只需要照着适配器接口再写一个类改改鉴权和URL拼接方式就行。3. 实操过程与核心环节实现3.1 环境准备和安装方式实际操作时我推荐用Docker跑这样本地、CI、同事的电脑上行为完全一致不会出现“我本地上没这个问题怎么CI上报错了”的情况。如果你是本地安装体验环境要求是Python 3.10以上Git 2.30以上Node 18以上这部分是给报告Markdown格式化用的也可以换掉。我用的是Python环境因为它做文本处理、正则表达式这块实在方便第三方依赖也少核心逻辑只需要PyYAML和requests这两个库。安装步骤非常简单三步git clone https://github.com/your-name/open-code-review.git cd open-code-review pip install -r requirements.txt如果你是Docker用户可以直接用项目里自带的Dockerfile构建镜像然后通过挂载目录的方式运行docker build -t open-code-review . docker run --rm -v $(pwd):/workspace open-code-review review --config /workspace/review_config.yaml我实测下来Docker方式最大的好处是自动解决了所有环境依赖问题。推荐大家在自己机器上第一次试用时就直接走Docker可以省掉不少安装依赖的麻烦。3.2 配置文件逐项拆解配置文件是整个工具的“开关面板”。第一次用的时候建议直接复制项目里的模板然后再根据自己团队的情况改。project: name: my-service languages: [python, javascript] target: base_ref: main head_ref: HEAD rules: enabled: - NO_LOGIN_HARDCODE - AVOID_EVAL - CHECK_EMPTY_EXCEPTION - BAN_MIXED_TABS_SPACES disabled: [] extra: - id: CUSTOM_RULE_001 message: 禁止直接调用未封装的日志器 pattern: | \blogger\s*\s*logging\.getLogger severity: medium report: output_dir: ./review_output format: [markdown, json] include_ai_summary: false ai: enabled: false api_base: https://api.openai.com/v1 api_key_env: OPENAI_API_KEY model: gpt-4o-mini temperature: 0.2 max_tokens: 800 request_timeout: 30 thresholds: block_on_severity: high max_files_to_report: 50 max_lines_per_file: 800 max_total_diff_lines: 5000这里重点说一下thresholds聚的值。block_on_severity的意思是当存在等于或高于这个级别的规则命中时进程返回非0退出码CI就会判定失败。这个机制很实用能很自然地跟现有流水线衔接。把block_on_severity设为high意味着critical和high都会被阻断而medium和low只是提示一下不影响合入。max_total_diff_lines这个参数也很关键。如果一次提交的diff超过5000行工具会自动跳过超限的部分只分析前5000行。这是防止有人一次提交几万行代码、把检查时间拖到无法接受的地步。实际遇到这种情况正确的做法是提醒团队把大PR拆小而不是无限放宽工具的扫描量。3.3 本地运行从diff到报告的全流程本地跑一次完整分析的命令是这样的open-code-review review \ --config review_config.yaml \ --repo . \ --base main \ --head HEAD如果你还没切换到另一个分支--base main --head HEAD的意思是分析当前分支相对于main分支的变更内容。如果你就站在main分支上只想看最近几次提交的变更可以这样open-code-review review --config review_config.yaml --repo . --base HEAD~3 --head HEAD跑完之后终端的输出大概长这样[open-code-review] 扫描完成 [open-code-review] 分析文件数: 12 [open-code-review] 新增行数: 342 [open-code-review] 规则命中: 5 (critical:0, high:1, medium:3, low:1) [open-code-review] 报告已生成: ./review_output/review-report.md [open-code-review] 报告已生成: ./review_output/review-report.json我习惯随手打开那个Markdown报告看一遍。报告按严重程度排序核心问题在最上面可以直接点进文件。整个流程跑下来体感上就是一个快速的静态检查速度非常快一般几万行的仓库也就在一两秒内出结果这也是我把规则引擎完全基于本地文本分析、不调任何外部服务的原因。3.4 接入GitHub Actions实现PR时自动评审要让工具在PR提交时自动跑起来最直接的方式是写一个GitHub Actions的workflow。下面这个是我在项目里一直在用的配置name: open-code-review on: pull_request: types: [opened, synchronize] permissions: contents: read pull-requests: write issues: write jobs: review: runs-on: ubuntu-latest steps: - name: 检出代码 uses: actions/checkoutv4 with: fetch-depth: 0 - name: 运行代码审查 uses: docker://your-dockerhub-user/open-code-review:latest env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} with: args: review --config /workspace/review_config.yaml --repo /workspace --base ${{ github.event.pull_request.base.sha }} --head ${{ github.event.pull_request.head.sha }} - name: 上传报告 uses: actions/upload-artifactv4 with: name: code-review-report path: review_output/这里有几个容易踩的坑提醒一下。第一fetch-depth: 0必须加否则actions/checkout默认只拉取浅克隆拿不到完整的提交历史diff对比时就直接罢工了。第二permissions里pull-requests: write是必须的如果只给了read机器人能跑但没法把评论贴到PR下面。第三github.event.pull_request.base.sha和head.sha这两个值要用对前者是目标分支最新提交后者是本次PR分支最新提交。如果你用的是GitLab CI思路一模一样只需要把GitHub特有的环境变量换成GitLab对应的CI_MERGE_REQUEST_TARGET_BRANCH_SHA和CI_MERGE_REQUEST_SOURCE_BRANCH_SHA评论API换成GitLab的接口。3.5 接入Git钩子让评审前置到本地CI接入是服务端的事本地钩子则是开发者的第一道防线。项目中我提供了一个安装脚本一条命令就能把pre-commit钩子装到当前仓库里open-code-review install-hook --config review_config.yaml这条命令做的事其实不复杂在git config core.hooksPath指定的钩子目录下写入一个pre-commit脚本然后在脚本中调用open-code-review review --base origin/main --head HEAD检查失败就exit 1从而阻止这次提交。这里有个使用心得要分享本地钩子的--base不要指定成main因为本地分支的main经常不是最新状态对比出来的diff会多出一大堆无关内容。更好的做法是设置成origin/main这样拿远程仓库的最新状态做对比基线本地改动跟远程主干之间的差异才是真正要审查的内容。钩子有个细节问题就是“卡得太死会让人想关掉它”。所以我在钩子的默认配置里把block_on_severity设为了critical只有出现致命级别问题才直接阻断提交其余级别只打印警告。这个权衡很重要工具是给人省事的不能变成阻碍否则几周之后团队里就会流传一句“这个检查没屁用我都不跑了”。4. 常见问题与排查技巧实录4.1 误报率太高团队开始不信任报告误报是这类工具面临的最大信任危机。我自己的测试项目里刚开始跑的时候误报率大概在20%左右也就是说五条提示里有一条是不该报的。这个比例放到团队里很快就会消耗掉大家的耐心。我的排查思路是不要试图通过增加“排除规则”来压误报而是先定位误报集中在哪些规则上再做针对性优化。具体操作是先把报告里的所有命中按rule_id分组统计每条规则的命中数量、人工确认的误报数量算出一个精确率。精确率低于70%的规则要么改匹配逻辑要么直接降级为low级别。另外一个常用手段是使用ignore_patterns配置在规则层面加白名单或例外区rules: - id: AVOID_EVAL message: 检测到动态执行代码 severity: high pattern: | \b(eval|exec)\( ignore_patterns: - # noqa: eval - .*sandbox.*比如有些测试代码就是专门为了测试eval的或者框架本身封装了沙箱环境这种情况下允许显式地在代码中标注“这里的东西我懂不需要检查”比一刀切关闭规则要合理得多。4.2 大PR扫描超时或内存溢出处理很大的PR时性能问题会暴露出来。几十个文件、上万个变更行在规则比较多的情况下单次运行可能要几十秒甚至更长。要应对这个场景我建议从两个方向处理。方向上先做增量分析。只扫描新增行或修改行用到的规则只匹配这部分行。因为Code Review本身关心的就是变化不是存量。这个策略能把大部分中等规模的PR从几十秒压到几秒。方向上引入文件级并发。Python的GIL在多线程场景下帮不上太多忙所以这里优先用多进程。我实测下来4核机器上把文件分到4个子进程里分析耗时能降到原来的四分之一到三分之一。如果你不想改代码只想通过配置解决问题那就把max_total_diff_lines调低同时把超限时从“报警”改成“警告但不阻断”。这可能意味着一次超大PR里远端文件没有被扫到但这说白了也是团队流程上应该解决的问题不该指望一个静态检查工具去扛住几千行的超长PR。4.3 GitHub Actions里评论发不出去遇到过很多次的问题是工具跑完了报告也生成了但PR下面就是看不到评论。大多数情况下是权限配置的问题。GitHub Actions的默认GITHUB_TOKEN权限是很受限的如果你不在workflow文件里显式配置permissions: pull-requests: write那你就只有只读权限自然就发不了评论。解决办法就是在workflow里加上这部分配置然后把token以环境变量的形式传给评论模块。另一种情况是fork分支的PR。外部贡献者提交的PR默认是触发不了需要写权限的comment操作的这是GitHub的安全机制。如果你想让外部PR也能被自动评审建议把评论改成workflow_run风格先让基础workflow跑出报告并上传artifact再在workflow_run里用另一个有更高权限的workflow读取artifact、代发评论。4.4 本地钩子没有生效有同事反馈说装完hook之后提交代码没有任何反应我排查后发现最典型的原因是安装脚本往系统目录写hook而当前仓库使用的hooksPath指向了别的地方。解决办法是先检查git config core.hooksPath如果你的输出是空说明使用的是默认的.git/hooks目录。如果指向了自定义目录那安装脚本需要改写到那个目录里。另外旧版本的Git对hook文件的执行权限有要求脚本必须具备x权限才能被触发。排错时可以用bash -x直接跑一下hook脚本看看是本身报错还是没被调用。4.5 报告里的行号和PR里的行号对不上这个问题在刚接GitHub评论时特别头疼。原因其实很基础diff里的行号有两种体系一种是被修改文件里的“新文件行号”另一种是原文件的“旧文件行号”。在PR评论里GitHub要求的是新文件的行号但很多规则分析的是diff中hunk的信息如果不注意就可能传成了旧行号或者把hunk的相对行号当成了文件里的绝对行号。我的解决方案是在diff解析阶段就把每个变更行对应的“新文件绝对行号”算好并缓存起来后续所有规则输出都统一使用这个行号体系。在评论适配层收到行号后还要再调用一次定位API做校验如果目标文件还在就拿着精确行号去评论如果发现行号对应的上下文跟预期不符就退回成文件级评论。这样至少不会出现评论挂在错误代码上的尴尬情况。5. 经验总结与实际应用建议这个项目在我自己的日常工作中已经稳定跑了几个月最大的收益不在于“抓出了多少bug”而在于把人的注意力从机械劳动里解放了出来。我自己明显能感觉到人工审查时看到一行硬编码密钥的提示注意力会立刻被拉走现在这些问题在打开PR之前就被规则引擎过滤掉了人工评审时讨论的更多是“这段设计的合理性”和“后续扩展性”这才是Code Review真正的价值所在。如果大家也想在自己的仓库里跑起来我建议先从小处入手。不要一上来就配几十条规则先选五六条和团队代码习惯最相关的比如硬编码凭据、危险函数调用、空的异常捕获、混用空格和Tab、日志里打印敏感字段跑上两周看团队反馈。规则是需要迭代的没有哪条规则一开始就是完美适配某个团队风格的。跑一段时间后再把大家觉得“真有用”的规则逐步加进去把产生误报的规则调低或删掉。最后分享一个小技巧除了PR评论之外这份报告还可以作为双向的“知识传递工具”。每次评审结束后把历史报告里出现次数最多的问题整理成团队文档作为新人培训材料。一个新同学来了与其让他背半天的规范文档不如把他的第一个PR跑一遍工具然后让他对照着真实的报告来理解团队约定。这种“在具体代码上学习规范”的方式效果比任何文档都好得多。后续这个项目我还在规划一些扩展比如支持更多语言的AST级分析以及把规则命中统计做成一个小面板方便团队月度复盘时查看质量趋势。代码本身是开源的希望有兴趣的读者能给它提想法和PR一起把它变得更顺手。