1. 为什么我把 Code Review 交给了 AI 来打辅助
做开发十来年,Code Review 这件事我经历过好几个阶段。最早是团队里几个人互相看,后来是强制 PR 必须至少一个人 Approve 才能合并,再后来上了 CI,跑 lint、跑单测、跑静态扫描。工具越来越多,流程越来越长,但有个问题一直没解决:真正有价值的评审意见,往往取决于评审人当天的心情、状态和手头忙不忙。
我见过太多 PR 挂了两三天没人理,也见过有人为了赶进度直接给自己点 Approve。更常见的情况是,评审人只看了 diff 的前两百行,后面几百行扫一眼就过了。这不是态度问题,是人的注意力本来就有限。一个 PR 改动十几个文件、上千行代码,指望人逐行看完还能给出高质量意见,不现实。
所以当我开始把 AI 引入 Code Review 流程时,出发点很朴素:让机器先过一遍,把人从重复劳动里解放出来,专注在真正需要判断力的地方。这篇内容就是我这段时间折腾下来的完整总结,包括整体思路、工具选型、实操步骤、踩过的坑,以及我目前稳定在用的方案。适合正在搭 CI 流程的团队、想提升 PR 质量的个人开发者,以及任何对 AI 辅助研发流程感兴趣的人。
需要先说明一点:AI 做 Code Review 不是要替代人,而是做第一层过滤。它能干掉大量低级问题,让人只看值得看的部分。这个定位想清楚了,后面的方案设计才不会跑偏。
2. 整体设计与思路拆解
2.1 先想清楚:AI 评审到底该管什么
很多人一上来就想让 AI 把整个 PR 从头到尾评一遍,结果要么是意见太多没人看,要么是废话一堆没重点。我的做法是先给 AI 划定职责边界,把评审内容分成三类:
- 机器该管的:代码风格、命名规范、明显的空指针风险、未使用的变量、重复代码、日志打印残留、硬编码密钥、SQL 拼接风险。这些有明确规则,AI 判断准确率高,误报率低。
- 机器辅助人管的:业务逻辑是否合理、边界条件是否覆盖、异常处理是否完整、接口设计是否一致。AI 可以给出提示,但最终判断交给人。
- 机器不该管的:架构决策、技术选型、团队约定背后的历史原因。这些需要上下文,AI 看不到全貌,强行让它评只会添乱。
这个分类直接决定了后面 prompt 怎么写、CI 怎么配。我见过有人把 AI 评审配成"每个 PR 必须解决所有 AI 意见才能合并",结果团队怨声载道,因为 AI 连"这个变量名不够好"都要卡一次。评审意见要分级,阻断性问题和建议性问题必须分开。
2.2 方案选型:为什么我最终选了"CI 触发 + 分级评论"
市面上做 AI Code Review 的路子大概有这么几种,我挨个试过:
| 方案 | 优点 | 缺点 | 我的评价 |
|---|---|---|---|
| IDE 插件实时提示 | 反馈快,写代码时就能看到 | 只覆盖本地改动,看不到 PR 全貌,团队无法统一 | 适合个人,不适合团队流程 |
| 本地脚本手动跑 | 灵活,想跑就跑 | 依赖个人习惯,容易漏,无法强制 | 过渡方案 |
| CI 自动触发 + PR 评论 | 强制、统一、可追溯 | 配置成本高,需要调 prompt | 最终选择 |
| 独立评审平台 | 功能全,报表好看 | 数据出域,成本高,定制难 | 大团队可以考虑 |
我最终选 CI 触发,核心原因是它把评审变成了流程的一部分,而不是靠自觉。PR 一提交,CI 自动跑 AI 评审,结果以评论形式贴回 PR,谁都能看到。这样既保证了覆盖率,又留下了记录,后面复盘也有据可查。
至于为什么用分级评论而不是一次性贴一大段,是因为我踩过坑。最早我把 AI 输出直接整段贴上去,结果评论又长又密,评审人根本不想看。后来改成按严重程度分三档:阻断(必须改)、警告(建议改)、提示(仅供参考),每档用不同标记,评审人一眼就能抓住重点。
2.3 数据流设计:从 PR 到评论的完整链路
整个链路我画过好几版,最后稳定下来的流程是这样的:
- 开发者提交 PR,触发 CI。
- CI 拉取本次 PR 的 diff(只取变更部分,不取全量代码)。
- 对 diff 做预处理:过滤掉二进制文件、锁文件、自动生成的代码。
- 按文件或按 hunk 切分,分批送给 AI 模型。
- AI 返回结构化结果(JSON 格式,包含文件、行号、严重级别、意见内容)。
- CI 脚本解析结果,去重、排序,按级别生成评论。
- 通过平台 API 把评论贴回 PR。
- 如果存在阻断级问题,CI 标记为失败,阻止合并。
这个链路里有两个关键设计点值得展开说。
第一,只送 diff 不送全量。原因很简单:全量代码太大,token 成本高,而且 AI 容易被无关代码干扰。但只送 diff 有个问题——AI 看不到上下文,可能误判。我的解决办法是在 prompt 里附上变更文件的完整内容作为参考,但明确告诉 AI"只评审 diff 部分"。这样既控制了成本,又给了足够上下文。
第二,结构化输出。早期我让 AI 直接输出自然语言,结果解析起来很痛苦,格式每次都不一样。后来强制要求 JSON 输出,并在 prompt 里给出 schema 示例,稳定性大幅提升。下面是我用的 schema:
{ "file": "src/main/java/com/example/UserService.java", "line": 42, "severity": "blocker", "category": "null-safety", "message": "user.getAddress() 可能返回 null,后续调用 getCity() 会抛 NPE", "suggestion": "建议先判空或使用 Optional" }这个结构后面解析、去重、排序都方便,强烈建议一开始就定好。
3. 核心细节解析与实操要点
3.1 Prompt 设计:决定评审质量的关键
AI 评审效果好不好,八成看 prompt。我前后改了十几版,总结出几个必须包含的要素:
角色设定要具体。不要写"你是一个代码评审员",太泛。我现在的写法是:"你是一名有十年经验的 Java 后端工程师,熟悉 Spring 生态,注重代码健壮性和可维护性,评审风格直接、务实,只指出真正重要的问题。"角色越具体,输出越贴合预期。
评审范围要明确。我会在 prompt 里列出本次评审关注的维度,比如:空指针风险、资源泄漏、并发问题、异常处理、日志规范、SQL 注入、硬编码。不关注的维度也列出来,比如:代码风格(交给 lint)、命名(交给团队规范)。这样 AI 不会越界。
输出格式要强制。前面说的 JSON schema 必须写进 prompt,并且给出正例和反例。我还会加一句:"如果某个文件没有问题,不要输出该文件的任何内容。"避免 AI 为了凑数硬编意见。
严重级别定义要清晰。我在 prompt 里明确定义三档:
- blocker:会导致运行时错误、数据丢失、安全问题,必须修复。
- warning:可能导致问题、违反最佳实践,建议修复。
- info:可选优化,不影响功能。
定义清楚后,AI 分级准确率明显提升。
3.2 分批策略:大 PR 怎么处理
一个 PR 改动几百行甚至上千行是常事,直接整段送进去,模型要么超 token 限制,要么注意力分散导致漏检。我的分批策略是这样的:
- 按文件分组,单个文件超过 500 行 diff 的,按 hunk 再切。
- 每批控制在 2000 token 以内(diff 部分),加上上下文不超过 4000 token。
- 批与批之间独立评审,最后合并结果。
- 合并时按文件和行号去重,同一位置多条意见只保留最高级别的。
这里有个细节:跨文件的关联问题容易被漏掉。比如 A 文件改了接口签名,B 文件还在用旧签名。分批评审时,AI 看不到这种跨文件问题。我的补充办法是在最后加一轮"全局检查",只送变更文件的接口定义和调用点,专门查这类问题。虽然多花一点成本,但能抓到不少真实 bug。
3.3 误报控制:怎么让团队不反感
AI 评审最大的敌人不是漏报,是误报。误报多了,团队就会无视所有 AI 意见,整个流程就废了。我用了几个手段控制误报:
第一,白名单机制。某些文件或目录不参与 AI 评审,比如自动生成的代码、第三方库拷贝、测试 fixture。这些地方 AI 容易误判,直接跳过。
第二,历史反馈学习。我在 CI 脚本里加了一个简单的反馈收集:如果评审人把某条 AI 意见标记为"无效",就记录下来。积累到一定量后,分析这些无效意见的模式,反过来优化 prompt。比如发现 AI 老是把某种写法误判为空指针风险,就在 prompt 里明确排除这种情况。
第三,阈值控制。info 级别的意见默认不贴到 PR,只在 CI 日志里输出。warning 级别贴出来但不阻断。只有 blocker 级别才阻断合并。这样即使有误报,也不会影响开发节奏。
实测下来,经过两三轮 prompt 调优,误报率能压到 10% 以下,团队接受度就上来了。
3.4 成本控制:别让 AI 评审变成烧钱机器
token 成本是绕不开的话题。我算过一笔账:一个中等规模的 PR,diff 大概 500 行,加上上下文,单次评审消耗约 8000 token。如果团队每天 20 个 PR,一个月就是 480 万 token。用主流模型的价格算,一个月几十到几百块不等,看模型选择。
控制成本的手段有这么几个:
- 只评审变更部分,不送全量代码。
- 跳过小 PR。改动少于 10 行的 PR 直接跳过 AI 评审,人工看一眼就行。
- 缓存重复内容。同一个文件多次提交,如果 diff 没变,复用上次结果。
- 分级调用。blocker 级别的检查用强模型,info 级别的用便宜模型。
我目前的配置是:默认用中等价位模型,遇到大 PR 或关键模块才切到强模型。这样成本可控,效果也够用。
4. 实操过程与核心环节实现
4.1 环境准备与依赖安装
我以最常见的 Git 平台 + CI 组合来演示,具体平台名称就不提了,思路是通用的。你需要准备:
- 一个能跑 CI 的环境(自建 runner 或托管服务都行)。
- 一个能调用 AI 模型的 API key。
- 一个能读写 PR 评论的 token。
依赖方面,我用 Python 写评审脚本,主要用到这几个库:
pip install requests pygithubrequests用来调 AI API,pygithub用来操作 PR。如果你用别的语言,找对应的 SDK 就行,逻辑一样。
环境变量配置:
export AI_API_KEY="your_api_key" export AI_API_BASE="https://your-api-endpoint" export PR_TOKEN="your_platform_token" export PR_REPO="owner/repo"注意:API key 和 token 一定要用 CI 的密钥管理功能注入,不要硬编码在脚本里,更不要提交到仓库。
4.2 拉取 PR diff 并预处理
第一步是把 PR 的 diff 拉下来。用pygithub大概是这样:
from github import Github import os g = Github(os.environ["PR_TOKEN"]) repo = g.get_repo(os.environ["PR_REPO"]) pr = repo.get_pull(int(os.environ["PR_NUMBER"])) diff_files = [] for f in pr.get_files(): if f.filename.endswith((".lock", ".min.js", ".generated.java")): continue if f.filename.startswith("vendor/"): continue diff_files.append({ "filename": f.filename, "patch": f.patch, "additions": f.additions, "deletions": f.deletions })这段代码做了两件事:拉取 PR 的所有变更文件,过滤掉锁文件、压缩文件、自动生成代码和第三方库。过滤规则要根据你项目实际情况调整,原则是只评审人写的代码。
预处理还有个重要步骤:过滤掉纯删除的 hunk。如果某个 hunk 只有删除没有新增,AI 评审意义不大,直接跳过。
4.3 调用 AI 模型并解析结果
核心的评审函数大概长这样:
import requests import json def review_diff(diff_content, filename): prompt = f"""你是一名资深后端工程师,请评审以下代码变更。 评审维度:空指针风险、资源泄漏、并发问题、异常处理、SQL注入、硬编码密钥。 不评审:代码风格、命名规范。 输出要求:JSON 数组,每个元素包含 file、line、severity、category、message、suggestion。 severity 取值:blocker、warning、info。 如果没有问题,返回空数组 []。 文件:{filename} 变更内容: {diff_content} """ resp = requests.post( f"{os.environ['AI_API_BASE']}/v1/chat/completions", headers={"Authorization": f"Bearer {os.environ['AI_API_KEY']}"}, json={ "model": "your-model", "messages": [{"role": "user", "content": prompt}], "temperature": 0.2 }, timeout=60 ) content = resp.json()["choices"][0]["message"]["content"] try: return json.loads(content) except json.JSONDecodeError: return []几个关键点:temperature设成 0.2,让输出稳定;超时设 60 秒,避免卡死;解析失败返回空数组,不要让整个流程崩掉。
4.4 结果去重、排序与贴评论
拿到所有文件的评审结果后,需要合并处理:
def merge_results(all_results): seen = set() merged = [] severity_order = {"blocker": 0, "warning": 1, "info": 2} for r in all_results: key = (r["file"], r["line"], r["category"]) if key in seen: continue seen.add(key) merged.append(r) merged.sort(key=lambda x: (severity_order[x["severity"]], x["file"], x["line"])) return merged去重的 key 用文件+行号+类别,避免同一位置重复评论。排序按严重级别优先,让评审人先看到重要问题。
贴评论时,我按级别分组,blocker 和 warning 贴到 PR,info 只写日志:
def post_comments(pr, results): blocker_and_warning = [r for r in results if r["severity"] in ("blocker", "warning")] if not blocker_and_warning: return body = "## AI 评审结果\n\n" for r in blocker_and_warning: icon = "[阻断]" if r["severity"] == "blocker" else "[警告]" body += f"{icon} `{r['file']}:{r['line']}` {r['message']}\n" if r.get("suggestion"): body += f" 建议:{r['suggestion']}\n" pr.create_issue_comment(body)如果存在 blocker,CI 脚本最后exit 1,阻止合并。
4.5 CI 配置示例
把上面的脚本串起来,CI 配置大概是这样:
name: ai-code-review on: pull_request: types: [opened, synchronize] jobs: review: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Python uses: actions/setup-python@v4 with: python-version: "3.10" - name: Install deps run: pip install requests pygithub - name: Run AI review env: AI_API_KEY: ${{ secrets.AI_API_KEY }} AI_API_BASE: ${{ secrets.AI_API_BASE }} PR_TOKEN: ${{ secrets.PR_TOKEN }} PR_REPO: ${{ github.repository }} PR_NUMBER: ${{ github.event.pull_request.number }} run: python review.py这个配置在 PR 打开和更新时触发,跑完评审脚本。如果脚本返回非零退出码,CI 失败,PR 无法合并。
5. 常见问题与排查技巧实录
5.1 AI 返回格式不对怎么办
这是最常见的问题。表现是脚本解析 JSON 失败,评审结果为空。原因通常是模型输出里带了 markdown 代码块标记,比如json ...。
解决办法有两个:一是在 prompt 里明确要求"直接输出 JSON,不要用代码块包裹";二是在解析前先做清洗:
def clean_json(content): content = content.strip() if content.startswith("```"): content = content.split("\n", 1)[1] content = content.rsplit("```", 1)[0] return content.strip()两个手段一起用,基本能解决。
5.2 评审意见太多太碎怎么办
早期我遇到这个问题,一个 PR 贴出三四十条意见,评审人直接崩溃。后来做了几件事:
- 提高 info 级别门槛,默认不贴。
- 同一文件同一类别的意见合并成一条。
- 限制单次贴出的意见数量,超过 15 条只贴前 15 条,其余写日志。
实测下来,一个 PR 贴 5 到 10 条意见是比较舒服的区间。
5.3 大 PR 超时或超 token 怎么办
大 PR 是绕不开的。我的处理策略:
- 单文件 diff 超过 500 行的,按 hunk 切分。
- 总 diff 超过 5000 行的,只评审改动最大的前 10 个文件,其余跳过并在评论里说明。
- 设置单次调用超时 60 秒,超时重试一次,再失败就跳过该文件。
提示:不要为了评审大 PR 无限加大 token 限制,成本和稳定性都会出问题。宁可少评一点,也要保证流程稳定。
5.4 团队不认可 AI 意见怎么办
这是流程问题,不是技术问题。我的经验是:
- 先在小范围试点,收集反馈,调优 prompt。
- 把 AI 定位成"辅助"而不是"裁判",明确它不阻断合并(除非是 blocker)。
- 定期复盘 AI 意见的准确率,把数据摆出来,用事实说服团队。
- 允许评审人一键忽略某条意见,并记录原因,用于后续优化。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决办法 |
|---|---|---|---|
| 评审结果为空 | JSON 解析失败 | 看原始返回内容 | 加清洗逻辑,优化 prompt |
| 意见太多 | 阈值太低 | 统计各级别数量 | 提高 info 门槛,合并同类 |
| 超时 | diff 太大 | 看单文件行数 | 分批处理,设置超时重试 |
| 误报多 | prompt 不具体 | 分析无效意见模式 | 补充排除规则,加白名单 |
| 成本高 | 全量评审 | 统计 token 消耗 | 跳过小 PR,分级调用模型 |
| 评论贴不上 | token 权限不足 | 看 API 返回错误 | 检查 token scope |
5.6 几个我踩过的坑
坑一:忘了过滤删除行。早期 AI 对纯删除的代码也评头论足,说"这里删掉了重要的判空逻辑",其实人家是重构。后来在预处理阶段直接跳过纯删除 hunk。
坑二:prompt 里没写语言。有次评审一个 Go 项目,AI 按 Java 的习惯给建议,驴唇不对马嘴。后来在 prompt 里动态注入语言类型,问题解决。
坑三:CI 失败但没提示。有次脚本报错退出,但 PR 上没有任何提示,开发者一脸懵。后来加了异常捕获,任何错误都贴一条评论说明"AI 评审执行失败,请人工评审"。
坑四:token 泄露。早期图省事把 key 写在脚本里,差点提交上去。现在全部走 CI 密钥管理,脚本里只读环境变量。
6. 我目前稳定在用的配置与后续扩展
跑到现在,我稳定下来的配置是这样的:CI 触发,Python 脚本,中等价位模型,temperature 0.2,只评审 diff,blocker 和 warning 贴评论,info 写日志,blocker 阻断合并。误报率控制在 10% 以内,团队接受度不错。
后续我打算扩展几个方向。一是多模型交叉验证,blocker 级别的意见用两个模型分别评,都认为是 blocker 才阻断,进一步降低误报。二是结合静态分析工具,把 lint、安全扫描的结果一起喂给 AI,让它综合判断,减少重复。三是评审历史沉淀,把每次评审结果存下来,定期分析高频问题,反哺到编码规范和培训里。
最后分享一个小技巧:prompt 里加一句"如果代码写得很好,请明确说'未发现明显问题'"。这看起来是废话,但能让 AI 在没问题时给出明确反馈,而不是硬编几条意见凑数。这个改动让我这边的无效意见少了一大截。
另外,AI 评审的评论里我习惯带上"本意见由 AI 生成,仅供参考"的声明。既是合规要求,也是给评审人一个心理预期——AI 说的不一定对,最终判断还是靠人。这个定位摆正了,整个流程才能长久跑下去。