我从去年开始就一直在被一个问题折腾:代码评审怎么就从“质量保障”变成了“走个流程”。
打开几个技术社区的帖子,清一色在聊 Code Review 的文化建设、评审清单、轻重级评审模型,道理我都懂。可真到了团队里,实际情况往往是——MR 一多,reviewer 只能抽空扫一眼;时间一紧,能点个 Approve 就算是给面子;偶尔想认真看几个文件,又发现自己已经忘了这块业务当初为什么这么写。最后 Code Review 彻底变成了一件“反人性”的差事。
这个背景下,我拉了开源项目open-code-review,目标是做一套“能自动跑起来、能说人话、又不是 AI 废话生成器”的代码审查增强工具。它能自动拉取变更、解析 diff、把团队规范编译成可执行的检查规则,再让 LLM 只做规则覆盖不到的语义判断,最后把意见聚合起来回写到 MR 评论里。这篇文章把这套系统的搭建思路、核心模块、踩坑调优过程全部拆开讲一遍,适合那些团队规模在 5 到 50 人、有自己的 Git 托管平台和基础 CI、想认真把评审这件事落地而不是继续表演的技术负责人和一线工程师。
1. 先聊清楚:为什么代码评审总在“走过场”
1.1 评审形式化的三类典型症状
我观察到的第一个症状叫“补签型评审”。代码早就合并了,评审记录却是几天后补的。出现这种症状的团队,十有八九是把评审当成发布流程里的一个勾选项,没有人觉得它有实际价值。
第二个症状是“只看不改型评审”。Reviewer 打开 MR,花十分钟扫完 diff,留下几条“建议优化命名”“方法有点长”之类的泛泛之谈,但不会深究设计思路和边界条件。这类 review 看起来热热闹闹,实际上对拦截缺陷没有帮助。
第三个症状最隐蔽,叫“沉默型评审”。点开 MR,一句话没有,直接 Approve。这种操作多了,大家就会默认“评审就是走个形式”。一旦这个默认形成,即使后面有认真负责的同事想提意见,也会被认为“你太难搞了”。
这三个症状有同一个根因:评审者没有足够的上下文和注意力预算去解决一个模糊问题——“这段代码好不好”。人脑直接面对这个开放问题时,本能反应是逃避。所以问题的解法不是喊口号“请大家认真评审”,而是要给评审者提供结构化的辅助信息,降低识别问题的心智成本。
1.2 “Open”的真正含义:不只开放源码,更开放流程
我决定做open-code-review的时候,名字里最关键的是 Open,但它包含三层含义,不是简单指开源。
第一层,源码开放。整个系统可以自托管,部署在自己的内网环境,不会把仓库内容送到外部服务(除了你主动配置的 LLM API)。
第二层,规则开放。团队规范不再散落在 PRD、wiki 和聊天记录里,而是一份份结构化的规则文件,任何成员都能添加、修改、review。规则本身也要被评审,这一点我觉得比代码评审更重要。
第三层,流程开放。传统评审里,人是最主要的审查主体;这个项目把它改成人机协作:机器负责可枚举、可自动化的检查项,人负责需要业务理解和架构判断的部分。整个过程从“黑盒的瞬间判断”变成“白盒的可追溯流程”。
1.3 这个项目适合谁、不适合谁
直接说结论。
适合:
- 使用 GitHub、GitLab、Gitea 的软件研发团队,并且已经建了分支保护;
- 团队有基本的 CI 基础,愿意把评审检查嵌进流水线;
- 能花半小时集体梳理一轮“哪些规范值得自动化”的团队;
- 对引入 LLM 审查有兴趣、但不想把整个 MR 直接丢给在线 AI 工具的安全性敏感团队。
不适合:
- 一个人写代码、没有固定协作者的场景,价值不大;
- 完全不上 CI、靠人肉沟通的团队,先补基础设施再考虑自动化;
- 希望工具能替代人工评审的团队。这个项目定位是辅助增强,不是替代。
2. 整体架构与工作流:OpenCodeReview 如何把审查拆成三个阶段
2.1 设计理念:先规则后 LLM,规则能解决的不花钱
真正设计open-code-review时,我第一个决定的不是用什么语言、什么框架,而是定下一条核心原则:检查链路是有优先级的,规则引擎永远在 LLM 之前。
原因很直白。规则引擎是可解释的,命中就是命中,开发者收到一条“你引入了 console.log,请确认是否调试残留”,不会有任何歧义;而 LLM 给出的意见即使是对的,也往往需要人二次判断。其次,规则引擎跑一遍几百个文件的成本几乎可以忽略,而 LLM 每过一遍 token 都要花钱,调用一次少则几秒慢则半分钟。如果连新增了一个 debugger 语句都要让 LLM 看一遍,既费钱又费时间,还很滑稽。
所以这条设计原则的具体含义就是:
- 能用正则和历史代码模式匹配的,用规则引擎;
- 能用 AST 静态分析发现的,用规则引擎;
- 规则引擎判断不了、需要了解业务语义的,才进 LLM 辅助审查层。
2.2 三个阶段:提交前检查、Diff 解析、意见聚合
整个系统的主流程是三条流水线串起来的。
第一阶段是触发与获取。监听 Git 平台的事件(GitLab 的 Merge Request 事件、GitHub 的 Pull Request 事件),拿到仓库地址、源分支、目标分支和最新的 commit SHA。这一步很基础,但有一个容易被忽略的细节:必须校验事件的签名,否则任何人伪造一个 webhook 请求就能让系统去克隆任意分支,属于严重的安全隐患。
第二阶段是 Diff 解析与规则扫描。把仓库 clone 到本地(用镜像方式减少传输量),用 Git 命令拿到目标分支和源分支之间的 diff,然后做结构化解析。解析结果一方面进入规则引擎,匹配配置的规范规则;另一方面被截断、分块,送给 LLM 辅助层。这里最关键的数据结构是“行号映射表”,后面我会专门讲。
第三阶段是意见聚合与回写。规则引擎的输出是机械化的结构化问题,LLM 的输出是不确定性的自然语言意见,两者格式完全不一样。系统里设置了一个聚合模块,把它们统一标准化成 ReviewComment 对象:包含文件路径、起止行号、严重级别、消息正文、来源。然后对同一文件、同一区域的评论做折叠,只把最有代表性的展示到 MR 上,避免刷屏。
触发事件 -> 获取变更信息 -> 拉取代码 -> 计算 Diff -> Diff结构化解析 -> 规则引擎扫描 -> LLM语义辅助审查 -> 意见标准化与聚类 -> 评论回写/IM推送这条链路看起来不复杂,但每个环节都有不少坑,比如 diff 里文件删改造成行号漂移、大文件截断导致上下文缺失、LLM 输出 JSON 格式不稳定等。后面章节我会逐个展开。
2.3 技术选型和目录结构:一版简单但耐用的落地形态
技术选型我用了 Python 3.11 + FastAPI 做事件接收和结果回写,Celery 做异步任务队列,PostgreSQL 存结果(当然本地调试可以直接上 SQLite),GitPython 处理 Git 操作。LLM 层通过一个适配器接口统一封装,标准实现里支持 OpenAI 接口风格的商业模型,也支持通过 Ollama 调用本地部署的开源模型。
open-code-review/ ├── app/ │ ├── api/ # Webhook 接收、评论上报 API │ ├── core/ # 配置加载、日志、数据库连接 │ ├── diff/ # Diff 解析器,行号映射表 │ ├── rules/ # 规则引擎(正则、AST、自定义脚本) │ ├── llm/ # 大模型适配层(OpenAI/Ollama/自部署) │ ├── scanner/ # 审查流水线编排 │ ├── result/ # 意见标准化、聚类、评论生成 │ └── providers/ # GitLab/GitHub/Gitea 平台适配 ├── rules/ │ ├── python_rules.yaml │ ├── javascript_rules.yaml │ └── general_rules.yaml ├── tests/ ├── docker-compose.yml └── pyproject.toml这个结构没有过度设计。实际运行的时候,核心变化发生在 scanner 模块里,它把 diff 对象、规则集、LLM 客户端三个组件协调起来,顺序执行。依赖注入用得比较克制,逻辑链路清晰,后来加新平台适配的时候基本不用动其他模块。
3. Diff 解析与规则引擎:最容易被低估的核心模块
3.1 Diff 解析:从 patch 到结构化变更的转化
很多第一次做代码审查工具的人最容易犯的错误,是只把 Git Diff 当成“要给 LLM 看的文本”,直接拼进 prompt 就完事。但规则引擎要可靠工作,必须拿到结构化的变更信息,否则所有检查都只能在整段文本上用正则碰运气。
一个 Diff 的标准结构是 hunk,每个 hunk 内包含若干行变更,每行有状态(新增/删除/上下文)、旧文件行号、新文件行号、以及行内容。我封装了一个解析函数,核心逻辑是把这些行级信息读出来,构建“旧行号 -> 新行号”、“新行号 -> 旧行号”两个映射表:
import re HUNK_HEADER = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@.*$") def parse_diff(diff_text: str, filename: str): changes = [] old_line = 0 new_line = 0 for raw_line in diff_text.splitlines(): header = HUNK_HEADER.match(raw_line) if header: old_line = int(header.group(1)) new_line = int(header.group(3)) continue if raw_line.startswith("\\"): continue if raw_line.startswith("+++") or raw_line.startswith("---"): continue line_type = raw_line[0] if raw_line else " " content = raw_line[1:] if line_type == "-": changes.append({ "filename": filename, "type": "del", "old_line": old_line, "new_line": None, "content": content.strip("\n"), }) old_line += 1 elif line_type == "+": changes.append({ "filename": filename, "type": "add", "old_line": None, "new_line": new_line, "content": content.strip("\n"), }) new_line += 1 else: changes.append({ "filename": filename, "type": "context", "old_line": old_line, "new_line": new_line, "content": content.strip("\n"), }) old_line += 1 new_line += 1 return changes这个结构的价值在注释回写时体现得最明显。规则引擎在“新增行”上发现问题后,必须知道这条新增行对应新文件里的哪个行号,否则评论会贴到错误的位置。如果你把旧行号当新行号用了,开发者收到一条指向 80 行、但实际新增在第 120 行的评论,体验会非常差。
解析之后,还要处理几个“脏场景”:文件重命名、二进制文件、换行符变化造成的全量 Diff。实际处理中我增加了启发式判断:如果一个大文件只有一两行改动,但换行符从 CRLF 变成 LF,diff 会显示出大量删除新增,这时候应该整文件过滤掉,否则一个无关改动就能把一个 MR 的报告刷成几百条。
3.2 规则引擎:把团队 25 条规范编译成可执行配置
我们团队花了半天时间,把散落在草稿文档和聊天记录里的规范翻出来,最后抽象出了 25 条值得自动化的检查项。这个环节切忌贪多,规则越抽象越没意义,越具体越好。按实现方式我把它们分成三类:
正则类规则,识别明确的坏味道。比如禁止console.log、禁止debugger、禁止新增TODO但又没带负责人标识。这类规则最简单,但要注意写在字符串里的内容会被误伤,需要对完整正则做上下文判断。
- id: no-console-log languages: [javascript, typescript] type: regex pattern: "console\\.(log|debug|info)" message: "检测到调试输出,请确认是否为调试残留" severity: warning valid_files: - "tests/" - "src/utils/debugger.ts"AST 类规则,识别代码结构层面的问题。比如不允许函数超过 80 行、不允许嵌套超过 4 层、不允许空 catch 块。这里我用的是 Tree-sitter 做的多语言 AST 提取,不依赖每个语言的专属工具链,也能有一个统一的规则描述格式。
- id: function-too-long languages: [python] type: ast check: function_length params: max_lines: 80 message: "函数长度超过 {max_lines} 行,建议拆分" severity: warning依赖类规则,识别依赖变化带来的风险。比如新增一个正则表达式的第三方库是不是合理、package.json 里是否直接引入了已知体积较大的包。这类规则从 diff 里提取 package.json 或 requirements.txt 的变化,再对照一个维护的依赖风险清单。
这三类规则对新手足够用了。我的经验是:把规则文件当作代码一样评审,规则变更也要走 MR。因为一条错误规则带来的误报,会快速消耗开发者对工具的信任,比不检查还糟糕。
3.3 LLM 辅助审查:让模型只在语义层面说话
规则引擎能覆盖掉 70% 的明确问题,但代码评审里最有价值的部分恰恰是剩下的 30%:逻辑是否正确、边界条件有没有遗漏、并发处理是否安全、设计是否合理。这些没有明确答案,但 LLM 在“预判风险”这件事上确实有用。
我设计 LLM 辅助审查的一个原则是:给模型足够但不过量的上下文,并强制结构化输出。一开始我把整个 MR 的 diff 全部塞给模型,结果它经常被长上下文干扰,输出一堆空泛的建议,比如“建议增强错误处理”这类毫无信息量的话。后来我改成按文件分组,每个文件单独作为一次调用,并限制每个文件最多取前 200 行变更,超出部分分片处理。
Prompt 层面的模板大致长这样:
你是一位资深代码审查者。以下是 {repo} 仓库中 {project} 项目 关于需求 "{ticket_title}" 的代码变更。 变更文件:{filename} 变更内容(diff):{diff_content}
请重点检查以下方面,并只输出 JSON: 1. 逻辑错误:包括空指针、越界、竞态条件、错误忽略 2. 边界条件:空集合、非法输入、极端值 3. 安全和性能问题 4. 与现有代码风格明显不一致的地方 输出格式: [ { "line": 新增行号, "severity": "error|warning|info", "message": "具体问题描述,请直接指出问题,不要建议性空话" } ] 如果没有发现问题,输出 []关键细节是line字段必须用 diff 片段中新增行的行号。为了让模型知道哪一行是新增的,我保留了 diff 中的+前缀,并在 prompt 中明确说明“带 + 的行是新增行”。这样模型指出的行号才能被回写到 MR 正确的位置。
对模型本身,我用的温度参数是 0.1,保证输出稳定性。模型选型上有两种路线:商业 API 效果好、速度快,但不能把源码发出去的团队不适合;本地部署模型隐私安全,但需要一个好显卡。我们内网环境用 Qwen2.5-Coder 14B 的量化版,效果虽然不如 GPT-4 级别,但匿名化、私有化这一条就值了。我在代码里做了一个适配器接口,切换到不同模型不需要改业务逻辑:
class LLMClient(Protocol): def review_diff(self, filename: str, diff_text: str, context: str) -> list[dict]: ... class OpenAICompatibleClient: def __init__(self, base_url: str, api_key: str, model: str, temperature: float = 0.1): ... def review_diff(self, filename, diff_text, context): messages = build_prompt(filename, diff_text, context) response = self.chat(messages, response_format="json") return sanitize_json(response) class OllamaClient: def __init__(self, endpoint: str, model_name: str, temperature: float = 0.1): ... def review_diff(self, filename, diff_text, context): ...4. 接入 GitLab CI 与 ChatOps:真正让 Review 不依赖人盯
4.1 CI 触发策略:什么时候全量检查,什么时候增量检查
工具本身跑得再好,如果没人去触发它,价值也会大打折扣。open-code-review的默认接入方式是 webhook 触发,我用的是 GitLab 的 Merge Request 事件。
这里有两个触发策略的核心决策,我说一下当时的思考过程。
第一个决策是“在 Webhook 里跑,还是在 CI 里跑”。我最终选择了 Webhook 作为主触发源,CI 里只保留一个可选的校验脚本。原因是 Webhook 可以拿到完整的事件上下文,Merge Request 刚创建、代码更新、评论新增这些动作都能自然感知,触发延迟低。而且 Webhook 服务独立于 CI Runner,不会因为 Runner 资源不够而排队,延迟也更稳定。
第二个决策是“什么时候跑全量,什么时候跑增量”。我们的分支策略是主干开发,feature 分支合入 main。所以在 Webhook 里我判断目标分支:如果目标分支是 main 或 release 开头的受保护分支,就走完整流水线(规则引擎 + LLM);如果目标分支是普通的 feature 分支互合,只跑规则引擎中最基础的正则类规则,不跑 LLM,因为这种 MR 通常还没稳定,结果噪声会很大。
下面是关键判断逻辑:
def should_run_full_check(target_branch: str, source_branch: str) -> bool: if target_branch in ("main", "master"): return True if target_branch.startswith("release/"): return True if target_branch.startswith("feature/") and source_branch.startswith("feature/"): return False return True4.2 意见上报:如何把结果恰到好处回写到 MR
拿到审查结果之后,最大的挑战不是生成意见,而是“不要让 MR 被评论淹没”。试想一个 MR 改动了 30 个文件,规则引擎跑了 40 条 warning,LLM 又提了 15 条意见。如果全部逐条贴到 MR 页面,开发者看到的是一个刷屏的评论区,第一条高价值警告反而被淹没了。
我们在聚合模块里做了三件事。
第一件,按严重程度截断。error 级别全部展示,warning 级别最多展示 8 条,info 级别只做摘要。别小看这个截断,很多 MR 噪音都来自 warning 和 info,把这两类收敛以后,报告的可读性直接提升一个档次。
第二件,做评论合并和去重。同一文件同一行附近的意见合并为一条,同一文件的多个问题按 severity 排序后在一个评论块里列出。如果 LLM 和规则引擎在同一个地方都发现了问题,只保留规则引擎的确定性结果,避免重复。
第三件,把“文件级别的评论”降级为“MR 级别的摘要”。当某个文件只是轻微不符合风格规范,但不影响正确性时,不再贴到具体行,而是汇总在 MR 整体评论里,这样开发者不会被零散评论干扰。
接入 IM 推送时我也沿用了同样的逻辑。在结果回写到 Mr. 评论之后,再推一条总结消息到企业微信/钉钉/Telegram 机器人,内容是“本次检查发现 3 个错误,6 个警告,详情请查看 MR 评论”。早期版本我试过把每条意见都推到群里,结果群消息一分钟刷了几十条,同事差点想把机器人禁言。
4.3 一个实际运行的最小化配置
如果你只是想在团队里先跑通一个 POC,最精简的路径是 Docker Compose 起服务,然后配置一个 GitLab Webhook。
docker compose up -d然后在.env里配置 GitLab 地址、访问 Token、Webhook 签名密钥:
GITLAB_URL=https://gitlab.example.com GITLAB_TOKEN=xxxxxxxxxxx GITLAB_WEBHOOK_SECRET=your_webhook_secret LLM_CLIENT=ollama OLLAMA_ENDPOINT=http://localhost:11434 OLLAMA_MODEL=qwen2.5-coder:14b之后到 GitLab 项目设置里的 Webhook 页面,添加一个 URLhttp://your-server:8000/api/webhooks/gitlab,勾选 Merge Request Events,填上密钥,点击测试。能收到测试事件,就说明接线完成。
如果你不想在自己的服务器上多部署一个常驻服务,也可以把它包装成一个 CI Job,在 CI 里调用一个命令行入口,拉取 diff 后直接输出审查结果。但这种方式每次只能基于当前流水线的上下文运行,没法感知“上次审查过了没有”,也就无法做增量评论去重。
5. 实测效果与误报治理:调优过程中的真实数据
5.1 第一批跑出来的结果:可用之前要先能忍
工具上线后,我们先在一个内部项目群里静默跑了两个星期,不对 MR 做强制门禁,只做观察。结果非常真实:总评论数大约 1.3 条/个 MR,其中约 30% 是规则引擎准确命中的实际问题;但另外 34% 是错误或无效意见,这种比例根本没法让开发者长期依赖。
第一个暴击来自正则规则的误报。我们有一条规则是“禁止在生产代码里出现console.log”,但项目里有个测试辅助文件把console.log封装成了日志工具,结果规则把工具本身也标记出来了。后来我给规则加了valid_files和ignore_patterns字段,允许排除特定目录和特定上下文。
第二个暴击来自LLM的输出失控。有一次它在一个非常普通的 DTO 拷贝类代码里持续挑刺,说“建议使用构建器模式”“减少重复代码”,这种没有业务上下文的建议,对一线开发者来说不仅是噪音,还会让人对工具的专业度产生怀疑。后来我在 prompt 里加了一段话:“如果问题属于风格偏好而非明确缺陷,请降低严重级别。”但效果有限,最后是通过 severity 截断规则才压制住的。
5.2 误报根因分类与过滤策略:我总结的一份调优表
我把误报根因归纳成四类,每类都有对应的处理策略:
| 误报类型 | 典型场景 | 处理策略 |
|---|---|---|
| 正则上下文缺失 | 字符串、注释里包含违规关键词 | 增加 context 预判,命中字符串/注释时跳过 |
| 跨文件语义缺失 | 单文件内看起来没用但实际被其他文件引用 | 只把规则引擎结果定位为 warning,不拦合并 |
| LLM 幻觉 | 模型认为某个逻辑有 bug,实际是正常的 | 对 LLM 意见设置置信度,并限定只提示不阻断 |
| 文件级噪音 | 自动生成代码/第三方代码被审查 | 支持 .gitattributes 和 ignore 规则跳过生成文件 |
其中跨文件语义缺失是目前最难的。一个函数在 A 文件里看起来没有意义,但它可能是 B 文件调用的公共接口,这种情况单文件分析天然不可能知道。我的选择是:规则引擎中跨文件相关的检查一律设为 info 级,不阻断合并,只提醒开发者和评审者“这里可能需要确认”。
另外关于 LLM 幻觉,我的核心手段是“降低期望、分级处理”。具体来说,LLM 的意见永远不能作为合并阻断条件,只能作为 review 提示。如果一个 LLM 意见没有对应到具体代码行,或者没有指出明确的逻辑边界,就直接丢弃。
5.3 性能开销:一次 MR 的检查时间预算
我们在内网环境实测,一个包含 20 个项目文件的普通 MR,完整流程的平均耗时在 30 到 50 秒之间。规则引擎几乎不占时间(我们统计一般是 2 到 4 秒,主要花在 AST 解析和 Git 命令上),真正的大头在 LLM 调用。按文件分组后,每调一次模型需要 3 到 8 秒,20 个文件就要一分钟出头。
为了不拖慢开发节奏,我做了两个熔断机制:
- Diff 行数超过 500 行的 MR,跳过 LLM 审查,只跑规则引擎;
- LLM 审查任务设置 5 分钟超时,超时后直接返回部分结果。
这两个机制上线后,最长任务耗时被压制在 3 分钟以内。我反而发现这促进了“小步提交”,因为改动越大,工具就越不帮忙,这反过来逼团队把 MR 拆小。这算是一个意外的副产物。
另外性能上还有一个容易忽略的点:每次事件触发都 clone 一次全量仓库,会消耗大量带宽和磁盘。我用git clone --filter=blob:none --no-checkout做了 blobless clone,只需要拉取提交历史和 diff 涉及的 blob,普通仓库的拉取时间能缩短 60% 以上。
6. 从工具到习惯:OpenCodeReview 落地团队文化的配套机制
6.1 评审清单的设计:能自动化的一律不放进清单
很多人以为推行 Code Review 就是定一张超大的检查清单,让评审者逐条对照。但我的体验正好相反:清单越短越有效。
我们把清单设计成三行原则:
- 自动化规则能覆盖的问题,不需要人再重复检查;
- 评审者只聚焦三件事——逻辑边界是否完备、变更是否符合当前架构、是否会影响现有调用方;
- 如果对某一点不确定,直接留言提问,而不是猜。
每周我们做一次半个小时的“评审复盘”,方式很简单:把这周自动化工具报告的 error 级别问题,和人工 review 时提出的意见全部拉出来,看有哪些是工具发现的、有哪些是工具漏掉的。凡是工具漏掉的,如果属于可枚举规则,就写一条新规则;如果属于语义判断,就在周五分享会上讲一遍。这个过程比单纯“加强评审意识”有用得多。
6.2 工具只是放大器,评审文化才是信号源
这一点我想放到最后,因为工具落地最容易栽跟头的不是技术,而是“预期错位”。
如果你给一个完全没有评审习惯的团队套上自动化工具,最可能出现的场面是:开发者根本没心思看 MR 评论里写了什么,工具只是多了一个需要被忽略的噪音源。反过来,如果团队已经有认真 review 的底子,自动化工具才会真正放大它的生产力,把人的注意力从那些机械检查中解放出来,聚焦到更值得花时间的问题上。
我内部推这个项目的时候,第一个月刻意做了一件事:只在每条自动评论后面加一行“本评论由 OpenCodeReview 自动生成,仅供参考,不代表必须修改”。我们把机器定义为“最较真的实习生”而不是“裁判”。这样开发者天然不会觉得被冒犯,看到有价值的意见随手改掉,看到没价值的直接忽略。这个心理定位非常重要,它决定了使用者是把工具当成伙伴还是当成麻烦。
6.3 我个人的实操体会:先统一“什么叫好代码”,再谈自动化
到了最后,我想分享一个可能和大部分技术方案讨论都不太一样的心得。open-code-review这个项目最核心的价值可能不在于它自动检查了多少行代码,而在于为了配置一套合理的规则,我们团队不得不好好坐下来,把“什么叫好代码”这件事彻底讨论了一遍。
以前大家以为这个问题有共识,真到列规则的时候才发现,不同人对“函数多长算长”“错误处理到什么程度算规范”“什么依赖可以直接引入”的看法差异巨大。但这个过程本身极其有价值。规则文件写完后,团队对代码风格的认知第一次变得有据可查,而不是靠感觉、靠记忆、靠老同事的口头传承。
所以如果你也想搭一套自己的自动评审系统,我的建议是:不要在工具选型和模型选择上纠结太久,先把一个需要大家集体确认的规则清单拉出来,让大家吵一架,吵完这架,这套系统已经成功了三分之一。工具本身我一直认为只是放大器,真正的信号源在团队内部。