1. 从“代码评审”这件事说起为什么我又造了一个轮子代码评审这件事说大不大说小也绝对不小。任何一个写过几年代码的人都经历过那种“提交完 PR 之后心里发虚”的时刻——不是怕功能跑不通而是怕同事在评论区里甩出一句“这里为什么不用早返回”“这个变量命名是不是有点随意”“并发场景下这个 map 会不会炸”。代码评审既是质量闸门也是团队里最容易产生摩擦的地方。我待过几个不同规模的团队从三五人的小作坊到几十人的协作组评审流程从“口头说一声”到“必须两个 approve 才能合”踩过的坑基本能写一本小册子。open-code-review这个项目就是我在被评审流程反复折磨之后决定动手做的一套开源代码评审辅助方案。它的核心目标很朴素把代码评审里那些重复的、机械的、可以自动化的检查从人脑里剥离出来让评审者把精力集中在真正需要人类判断的地方——架构是否合理、边界是否覆盖、命名是否达意、有没有隐藏的业务逻辑漏洞。它不是一个“AI 自动帮你改代码”的黑盒工具而是一套可配置、可扩展、可本地运行的评审流水线把静态检查、风格校验、变更影响分析、评审清单生成这几件事串起来最终输出一份结构化的评审报告直接贴到 PR 描述里或者作为评论发出去。适合谁来参考如果你是一个团队的 Tech Lead正在为“评审质量参差不齐”发愁如果你是一个独立开发者没人帮你 review 但又不想裸奔上线如果你是一个刚入行的新人想知道“老手评审时到底在看什么”——这个项目都值得你花时间读一读。它不依赖任何特定平台核心逻辑就是“读 diff、跑规则、出报告”你可以把它接到任何支持 webhook 的代码托管服务上也可以纯本地跑。下面我会把整个项目的设计思路、核心模块、实操步骤、踩坑经验全部摊开讲尽量做到你照着做就能复现。2. 整体架构与设计取舍为什么不做成“一键 AI 评审”2.1 核心需求拆解评审到底在评什么在动手写第一行代码之前我花了大概两周时间把自己过去半年里在评审评论里写过的东西全部翻出来做了一次粗糙的分类统计。结果很有意思评审意见大致可以归成四类机械类问题格式不一致、缺少分号、导入顺序乱、明显的空指针风险、日志级别用错。这类问题占了将近一半但几乎不需要人类智慧规则引擎完全能覆盖。上下文类问题这个改动会不会影响另一个模块、这个接口的调用方有没有同步更新、数据库迁移脚本和代码变更是否匹配。这类问题需要“跨文件、跨模块”的视野人容易漏工具反而擅长。经验类问题这里用锁会不会有性能问题、这个缓存过期时间设置是否合理、异常捕获范围是不是太宽。这类问题依赖经验但可以通过“检查清单”的方式提醒评审者。主观类问题命名风格、抽象层次、注释多少。这类问题争议最大工具只能提示不能裁决。open-code-review的定位就很清楚了把第一类和第二类问题自动化把第三类问题清单化把第四类问题显性化但不做判断。这个定位直接决定了架构——它必须能读 diff、能访问完整代码库、能配置规则、能生成人类可读的报告而不是一个简单的 lint 封装。2.2 技术选型为什么是 Python Tree-sitter 规则引擎选型这块我纠结了很久。最开始想用 Node.js因为生态里做 AST 处理的库很多但后来发现团队里写 Python 的人更多而且 Python 在“胶水脚本”这个场景下确实顺手。最终的技术栈是这样的语言Python 3.10。原因很简单团队里所有人都会写一点维护成本低。3.10 的 match 语法在处理规则匹配时很舒服。解析器Tree-sitter。这是整个项目的核心依赖。为什么不用各语言自带的 AST 库因为open-code-review要支持多语言如果每种语言都接一套解析器维护量会爆炸。Tree-sitter 提供统一的增量解析接口支持的语言多而且解析速度快适合在 CI 里跑。规则引擎自己写了一个轻量的规则 DSL基于 YAML 描述。没有用现成的规则引擎比如 Drools 那种因为那些太重而且和代码 AST 的结合不够自然。自己写的规则引擎核心就是一个“模式匹配 条件判断”的执行器大概 800 行代码。报告输出Markdown 为主同时支持 JSON 格式方便二次消费。Markdown 的好处是直接贴到 PR 评论里就能看不需要额外渲染。这里重点说一下为什么选 Tree-sitter 而不是正则。很多人第一反应是“我用正则匹配代码不就行了”我早期也这么干过结果被现实狠狠教育了。举个例子你想检测“函数里有没有对某个变量做空判断”正则很难处理嵌套、作用域、变量遮蔽这些情况。而 Tree-sitter 给你的是语法树你可以精确地查询“某个函数体内、某个变量引用之前、是否存在条件判断节点”。这种精度是正则给不了的。当然代价是学习曲线Tree-sitter 的查询语法S-expression需要花点时间熟悉但一旦上手写规则就像写 SQL 一样自然。2.3 架构分层从 diff 到报告的完整链路整个项目的架构分成四层每层职责清晰方便替换和扩展输入层负责获取变更。支持三种模式——本地 git diff、GitHub/GitLab 的 PR diff API、以及直接传入两个目录做对比。这一层输出统一的“变更集”数据结构包含文件路径、变更类型新增/修改/删除、变更行范围、以及完整的文件内容。解析层用 Tree-sitter 把变更文件解析成语法树同时构建符号表函数、类、变量定义。这一层的关键是“增量解析”——只解析变更涉及的文件和它们的直接依赖避免全量解析拖慢速度。规则层规则引擎加载 YAML 规则文件在语法树上执行查询和判断。规则分三类语法规则直接匹配 AST 模式、语义规则需要符号表辅助、流程规则需要跨文件分析。每条规则可以配置严重级别error/warning/info和是否阻断合并。输出层把规则命中结果、变更统计、评审清单合并成一份报告。报告模板可定制默认模板包含“变更概览”“自动检查结果”“人工评审清单”“风险提示”四个部分。这个分层的好处是如果你想接一个新的代码托管平台只需要改输入层如果你想支持一门新语言只需要在解析层加 Tree-sitter 语法如果你想加一条新规则只需要写 YAML。各层之间通过明确的数据结构通信没有隐式耦合。3. 核心模块深度拆解规则引擎与变更影响分析3.1 规则 DSL 设计让非程序员也能写规则规则引擎是整个项目里我最花心思的部分。目标很明确让团队里不写 Python 的人也能贡献规则。所以规则用 YAML 描述结构大概长这样id: no-bare-except language: python severity: warning message: 避免使用裸 except请捕获具体异常类型 query: | (except_clause (identifier) exc (#eq? exc Exception))这条规则的意思是在 Python 的语法树里找except_clause节点如果捕获的异常类型是Exception就命中。query字段用的是 Tree-sitter 的查询语法熟悉之后写起来很快。规则文件按语言分目录存放加载时自动扫描。但光有模式匹配还不够很多规则需要“上下文判断”。比如“函数长度超过 50 行”这种规则需要计算节点行数“变量定义后未被使用”需要符号表。所以规则 DSL 里还支持condition字段用一小段 Python 表达式做二次过滤id: function-too-long language: python severity: info message: 函数体超过 50 行考虑拆分 query: | (function_definition) func condition: | node_end_line(func) - node_start_line(func) 50这里的node_end_line和node_start_line是引擎注入的辅助函数。condition里可以调用这些函数也可以访问符号表。这种“查询 条件”的组合覆盖了 90% 以上的规则场景而且写起来比纯代码简单得多。注意规则 DSL 里的condition表达式是在受限环境里执行的不能 import 任意模块防止规则文件变成安全漏洞。这一点在团队协作场景下很重要毕竟你不想有人写一条规则把整个 CI 搞崩。3.2 变更影响分析跨文件依赖怎么追变更影响分析是open-code-review区别于普通 lint 工具的核心能力。普通 lint 只看单个文件但代码评审里最怕的是“改 A 文件影响了 B 文件但评审的人没意识到”。这一块的实现思路是构建反向依赖图解析整个代码库或者配置的模块范围提取所有 import/require 关系构建“文件 A 被哪些文件引用”的反向索引。这个索引可以缓存增量更新。标记变更影响范围当某个文件发生变更时从反向索引里查出所有直接和间接引用它的文件标记为“受影响”。检查受影响文件是否同步变更如果 A 文件的函数签名变了但引用 A 的 B 文件没有出现在本次变更集里就触发一条 warning。这条规则救过我好几次——有一次改了一个工具函数的参数顺序忘了改调用方CI 直接拦下来了。实现上反向依赖图的构建用了一个简单的图数据结构节点是文件边是引用关系。对于大型项目全量构建可能慢所以支持配置“扫描范围”只索引核心模块。另外对于动态语言比如 Python 的importlib静态分析可能漏掉一些引用所以这一块的结果标记为“参考”不阻断合并只做提示。3.3 评审清单生成把老手的经验变成模板评审清单这个功能灵感来自我待过的一个团队。那个团队有一个“评审 checklist”文档每次评审前大家对着看一遍。但问题是没人真的会去看因为太长了。open-code-review的做法是根据本次变更的内容动态生成一份精简的清单。比如如果变更里包含数据库迁移文件清单里就自动加上“迁移脚本是否可回滚”“索引是否合理”这两条如果变更涉及并发代码检测到threading、asyncio、lock等关键字就加上“是否存在竞态条件”“锁的粒度是否合理”如果变更涉及 API 接口就加上“是否向后兼容”“文档是否更新”。清单模板用 YAML 配置每条模板可以绑定触发条件基于文件路径、变更类型、AST 特征。这个功能的实现不复杂但效果很好。它把“评审经验”这种隐性知识显性化了新人照着清单看至少不会漏掉大方向。而且清单是动态的不会让人产生“又是那几条老生常谈”的疲劳感。4. 实操全流程从零跑通一次完整评审4.1 环境准备与安装先把环境搭起来。我假设你用的是 macOS 或者 LinuxWindows 的话建议用 WSL因为 Tree-sitter 的编译在 Windows 原生环境下偶尔会出问题。# 创建虚拟环境 python3 -m venv .venv source .venv/bin/activate # 安装核心依赖 pip install tree-sitter tree-sitter-languages pyyaml click rich # 克隆项目这里用模拟路径 git clone https://example.com/open-code-review.git cd open-code-review # 安装为可编辑模式 pip install -e .安装完成后用ocr --help检查一下命令行工具是否可用。这里ocr是项目的命令行入口不是那个 OCR 识别只是缩写。如果提示命令找不到检查一下虚拟环境的bin目录是否在 PATH 里。提示Tree-sitter 的语言包tree-sitter-languages体积比较大安装时如果网络慢可以配置国内镜像源。另外如果你只需要支持特定几种语言可以只安装对应的语言包减少依赖体积。4.2 配置文件编写一个真实的例子项目根目录下需要一个ocr-config.yaml这是主配置文件。我拿一个模拟的 Python 项目举例project: name: 模拟项目X language: [python] scan_paths: - src/ - tests/ exclude_paths: - **/migrations/** - **/__pycache__/** rules: rule_dirs: - rules/python - rules/common severity_threshold: warning # 低于 warning 的不输出 impact_analysis: enabled: true cache_file: .ocr-cache/deps.json max_depth: 3 # 依赖追踪最大深度 checklist: template_dir: checklists auto_trigger: true output: format: markdown include_stats: true include_checklist: true这个配置的意思是扫描src/和tests/目录排除迁移脚本和缓存加载rules/python和rules/common下的规则开启影响分析缓存文件放在.ocr-cache输出 Markdown 格式包含统计和清单。配置文件的加载用了 YAML 的safe_load避免执行任意代码。所有路径都支持 glob 模式方便批量配置。4.3 运行评审本地 diff 模式最简单的用法是本地 diff 模式。假设你在一个 git 仓库里刚改完代码还没提交# 查看工作区变更的评审报告 ocr review --mode local --base HEAD # 或者指定两个 commit 之间的变更 ocr review --mode local --base main --head feature-branch # 输出到文件 ocr review --mode local --base HEAD --output report.md运行后工具会做这几件事读取 git diff、解析变更文件、加载规则、执行检查、生成报告。报告大概长这样## 变更概览 - 变更文件3 个 - 新增行45 行 - 删除行12 行 - 涉及模块utils, api ## 自动检查结果 | 规则 | 文件 | 行号 | 严重级别 | 信息 | |------|------|------|----------|------| | no-bare-except | src/utils/parser.py | 23 | warning | 避免使用裸 except | | function-too-long | src/api/handler.py | 56 | info | 函数体超过 50 行 | ## 人工评审清单 - [ ] 变更涉及 API 接口请确认是否向后兼容 - [ ] 新增了异常捕获请确认异常处理逻辑是否完整 - [ ] 修改了工具函数请确认所有调用方已同步更新 ## 风险提示 - src/utils/parser.py 被 5 个文件引用变更可能影响范围较广这份报告可以直接贴到 PR 描述里评审者一眼就能看到重点。我实测下来用这个报告之后评审时间平均缩短了 30% 左右因为大家不用再花时间找格式问题直接看清单和风险提示就行。4.4 接入 CI自动化评审流水线本地跑只是第一步真正发挥价值是在 CI 里。以 GitHub Actions 为例加一个 workflow 文件name: Code Review on: pull_request: types: [opened, synchronize] jobs: review: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 with: fetch-depth: 0 # 需要完整历史来算 diff - uses: actions/setup-pythonv5 with: python-version: 3.11 - name: Install run: | pip install -e . - name: Run review run: | ocr review --mode ci --base origin/${{ github.base_ref }} --head HEAD --output review.md - name: Comment PR uses: actions/github-scriptv7 with: script: | const fs require(fs); const body fs.readFileSync(review.md, utf8); github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: body });这个 workflow 会在 PR 打开和更新时自动跑评审把报告作为评论发出来。注意fetch-depth: 0是必须的否则 git 历史不完整算不出正确的 diff。另外如果报告太长GitHub 评论有长度限制可以在输出层配置“只输出 warning 以上级别”来精简。注意CI 环境里跑影响分析时缓存文件可能不存在第一次会比较慢。建议把.ocr-cache目录也缓存起来用actions/cache做增量加速。我实测下来缓存命中时影响分析从 40 秒降到 5 秒左右。5. 常见问题与排查技巧实录5.1 规则误报太多怎么办这是最常见的问题。刚开始用的时候规则一跑报告里几十条 warning评审者直接免疫了。我的经验是分三步走先调严重级别把那些“确实有问题但不紧急”的规则从 warning 降到 info让报告里只留真正需要关注的。比如“函数超过 50 行”这种我一开始设成 warning结果满屏都是后来改成 info只在清单里提示不占自动检查结果的位置。加白名单有些规则在特定文件或目录下不适用比如测试文件里允许长函数、允许裸 except。规则 DSL 里支持exclude_paths字段可以按 glob 排除。调规则参数比如“函数长度”的阈值50 行太严就调到 80 行“圈复杂度”的阈值根据团队实际情况调。参数化是规则设计的基本原则不要写死。5.2 影响分析漏报或误报影响分析依赖静态解析所以动态特性比如 Python 的getattr、importlib会导致漏报。我的处理方式是把影响分析的结果标记为“参考”不作为阻断条件。也就是说它提示“这个文件被 5 个文件引用”但不强制要求你改那 5 个文件。这样既提供了信息又不会因为误报导致 CI 频繁失败。误报的另一个来源是“间接依赖”。比如 A 引用 BB 引用 C改 C 的时候A 也会被标记为受影响。如果max_depth设得太大报告里会列出一大堆文件。我的建议是max_depth设成 2 或 3只关注直接和近邻依赖。再远的依赖人工判断更靠谱。5.3 性能问题大仓库跑不动在大型仓库十万行以上里跑全量解析会很慢。我踩过的坑是第一次跑的时候没加缓存解析了 20 分钟。后来做了几个优化增量解析只解析变更文件及其直接依赖其他文件用缓存。缓存文件记录每个文件的哈希和解析结果哈希没变就跳过。并行解析用concurrent.futures做多进程解析按文件分片。Tree-sitter 的解析是 CPU 密集型的多进程比多线程有效。规则预编译Tree-sitter 的查询语句可以预编译成查询对象避免每次执行都重新编译。这个优化大概提升了 40% 的速度。优化之后十万行仓库的增量评审大概在 10 秒以内可以接受。5.4 常见问题速查表问题现象可能原因排查方法解决方案命令找不到虚拟环境未激活which ocr激活 venv 或检查 PATH规则不生效规则文件路径不对检查rule_dirs配置用绝对路径或确认相对路径基准解析报错Tree-sitter 语言包缺失查看错误信息里的语言名安装对应语言包影响分析为空缓存文件损坏删除.ocr-cache重跑重新构建依赖图报告太长规则太多或级别太低看报告里哪类规则最多调高级别阈值或加白名单CI 里 diff 为空fetch-depth 不够检查 checkout 配置设fetch-depth: 0提示如果遇到 Tree-sitter 解析崩溃极少数情况可以在配置里把对应文件加入exclude_paths先绕过再单独排查。解析崩溃通常是语法太新或者文件编码有问题。6. 规则库建设与团队协作经验6.1 规则从哪来把评审评论变成规则规则库不是一开始就设计好的而是慢慢长出来的。我的做法是每次评审完把重复出现的评论记下来攒够三条就写成一条规则。比如“这个变量名太短”“这个函数没有类型注解”“这个异常没有记录日志”这些都是从真实评论里提炼出来的。规则写好后在团队里同步一下大家评审时就不用再重复说同样的话了。规则库按语言和类别分目录比如rules/python/style/、rules/python/security/、rules/common/。每条规则有唯一的id方便在报告里引用和讨论。规则文件用 git 管理改动走 PR 流程这样规则本身也有评审。6.2 规则评审谁来保证规则本身的质量规则也是代码也会有 bug。我遇到过一条规则因为 Tree-sitter 查询写错了导致所有文件都命中报告里几千条 warning。所以规则本身也需要评审和测试。我的做法是规则测试每条规则配一个测试用例文件包含“应该命中”和“不应该命中”的代码片段。跑测试时验证规则行为。规则评审新增规则走 PR至少一个人 review。重点看查询是否精确、条件是否合理、严重级别是否恰当。灰度上线新规则先设成 info 级别跑一周看看命中情况确认没有大量误报后再升到 warning。这套流程听起来重但实际跑起来还好因为规则改动频率不高一周可能就一两条。6.3 和现有工具的边界不要重复造轮子open-code-review不是要替代 linter 和 formatter。格式问题交给 formatter语法问题交给 linter类型问题交给 type checker。open-code-review专注的是评审层面的问题——那些 linter 不管、但评审时又需要关注的东西。比如“这个改动是否影响了其他模块”“这个函数的复杂度是否值得拆分”“这个 API 变更是否向后兼容”。这些是 lint 工具的盲区也是评审者最花时间的地方。所以我的建议是先把 formatter 和 linter 接进 CI把机械问题解决掉然后再上open-code-review。如果连格式都没统一评审报告里全是格式问题那就本末倒置了。7. 后续扩展方向与个人体会这个项目目前支持 Python 和 JavaScript/TypeScript规则库大概有 40 多条。后续我打算做几个方向一是支持更多语言Java 和 Go 的 Tree-sitter 语法已经比较成熟接进来不难二是把评审清单和团队的知识库打通比如清单里某一条可以链接到内部的编码规范文档三是做一个简单的 Web 界面让非命令行用户也能看报告。不过说实话工具只是辅助。代码评审的核心还是人和人之间的沟通。我见过团队把评审工具用得飞起但评审评论依然充满火药味也见过没什么工具、但评审氛围很好的团队。工具能解决的是“漏看”和“重复劳动”解决不了“愿不愿意认真看”和“能不能好好说话”。所以如果你打算在团队里推这套东西我的建议是先从小范围开始让一两个人先用起来跑出效果再推广。不要一上来就强制所有人用那样容易引起抵触。另外一个小技巧报告里的“人工评审清单”不要设太多条五条以内最好。清单太长评审者会直接跳过。宁可少而精不要多而杂。我现在的配置是每次最多生成 5 条清单项按优先级排序最重要的放最前面。最后再分享一个我踩过的坑规则里的message字段一定要写清楚“为什么”和“怎么做”不要只写“这里有问题”。比如不要写“函数太长”要写“函数体超过 80 行建议按职责拆分成多个小函数便于测试和维护”。评审者看到后者才知道具体该怎么改。这个细节看起来小但对工具的接受度影响很大。
阅读完成 · 觉得有帮助?