一个 PR 提了 40 条意见,一半是废话:12 条"建议添加注释"、9 条"可以考虑用 StringBuilder",真问题只有 3 条,混在中间没人看见。
这是 AI 评审落地最常见的结局:不是提不出意见,是意见的分布不可用。价值不在命中数,而在"人愿意读完"。所以选型要问的不是"模型能不能看出问题",而是"它出的错和我的注意力,哪个更贵"。
1. 能力边界:强在模式,弱在意图
| 能力 | 强弱 | 原因 |
|---|---|---|
| 模式识别(危险调用、空指针、资源未释放) | 强 | 训练语料里的高频缺陷形态 |
| 团队约定一致性(命名、分层、返回体) | 中到强 | 约定能写进提示,但写不全 |
| 局部语义(这段 if 分支是否覆盖) | 中 | 单文件内可推,跨文件开始失准 |
| 业务意图(这个状态该不该允许变更) | 几乎为零 | 需求与领域知识不在 diff 里 |
| 性能与并发(锁粒度、N+1、竞态) | 弱 | 需要执行模型与数据分布,非文本可判 |
一句话概括:它是"读代码的人",不是"懂系统的人"。用它替代人工评审的阅读环节可行,替代判断环节不可行。
2. 能可靠拦住的四类
| 类别 | 典型形态 | 误报倾向 |
|---|---|---|
| 明显缺陷 | 空指针解引用、未关闭流、catch 后返回默认值 |
低 |
| 危险 API 用法 | RestTemplate 无超时、SimpleDateFormat 当静态字段、明文拼 SQL |
低 |
| 与约定偏差 | 日志缺标识、异常被吞、Controller 里写业务 | 中,取决于约定写得是否清楚 |
| 可读性 | 超长方法、重复分支、命名与语义不符 | 高,最需要降噪 |
第一、二类的共同点是可以脱离业务判断对错:一个没有超时的 HTTP 调用在任何业务里都是隐患。这类意见值得全量上报,因为它们几乎不需要人来复核。
第四类问题最大。可读性标准因人而异,模型又倾向于"多提几条显得认真",结果它挤占的正是前三类的注意力预算。
3. 拦不住的四类
| 类别 | 为什么漏 | 后果 |
|---|---|---|
| 需求理解错 | diff 里没有需求,模型只能按代码自洽性判断 | 实现完整、逻辑自洽,但做的事不是要做的 |
| 跨模块影响 | 看不到调用方与被依赖的契约 | 改了一个签名,三个上游炸 |
| 性能退化 | 需要数据量与执行计划 | 循环里查库、全表加载,评审时像正常代码 |
| 并发与状态 | 需要时序与共享状态图 | 竞态、幂等缺失、双重提交 |
这四类恰好是线上事故的主要来源,也恰好是 AI 最弱的地方。所以流程上必须保留一道**人来回答"这段代码在系统里意味着什么"**的关卡,位置在 AI 评审之后、合并之前。
4. 误报的成本与降噪
误报不是"多说了几句":评审者对工具的信任下降(后面真问题也被跳过)、讨论区变长(决策被埋)、作者开始习惯性点"忽略",忽略规则越堆越多。
| 降噪手段 | 做法 | 代价 |
|---|---|---|
| 严重度分级 | 只对 blocker/major 阻断,其余折叠 |
分级标准要人工定,且模型分级不稳定 |
| 置信度门槛 | 只报模型自评高置信的意见 | 漏掉一部分真问题 |
| 范围限定 | 只评审变更行及邻近上下文 | 看不到跨文件问题 |
| 规则白名单 | 关掉风格类、注释类检查 | 需要持续维护 |
| 数量上限 | 单 PR 最多 N 条,按严重度取 | 超出的真问题被截断 |
# .ai-review.yaml —— 先关掉制造噪声的类别,再谈准确率
severity_gate: major # 只有 major 及以上阻断合并
max_comments_per_pr: 12 # 按严重度取前 12 条,其余折叠
categories:
security: { enabled: true, min_confidence: 0.7 }
correctness:{ enabled: true, min_confidence: 0.6 }
convention: { enabled: true, min_confidence: 0.8 }
style: { enabled: false }
docs: { enabled: false }
ignore_paths: ["**/generated/**", "**/*.lock", "**/migrations/**"]
ignore_paths 必须包含生成代码:那些文件模型看不懂也会提意见,人也不会改。
5. 提示怎么组织:四件必给的东西
评审质量几乎完全由"喂进去的上下文"决定。只给 diff,模型只能做局部语法检查;给全上下文,它又开始在无关代码上发挥。可用的组合是四段:
[1] 变更内容:完整 diff(含文件名与行号),不给摘要
[2] 相关上下文:被改函数的定义、直接调用方(≤ 3 处)、相关实体/接口定义
[3] 团队规范:不超过 15 条硬规则,写成可判定的句子
- 所有外部调用必须显式设置超时
- 异常不得吞掉后返回默认值,必须记录并上抛或明确降级
- Controller 不得直接依赖 Mapper
[4] 不要报什么:明确排除项
- 不要提命名风格、注释、格式化
- 不要建议重构未改动的代码
- 不确定时不要报;宁可漏,不要凑数
第 4 段最容易被省略,也最影响可用性。模型默认行为是"尽量多提",必须显式反向约束,并且把"不确定就不报"写成硬要求。
规范条目要可判定:"代码要清晰"只会变成噪声源,"外部调用必须显式设置超时"能对着 diff 逐行判。
能不能检索到相关定义与调用方,决定第三类的检出上限:工具只读 diff 时,跨模块漏检是必然的;能索引整个仓库时,问题才有机会被提出。
6. 与人工评审的分工,以及 diff 大小的影响
| 环节 | 谁做 | 判据 |
|---|---|---|
| 机器可判的(编译、测试、静态规则、危险 API) | AI + CI | 有明确对错,不需要业务知识 |
| 局部逻辑正确性 | AI 提候选,人确认 | 需要读代码路径 |
| 系统影响与业务意图 | 人 | 需要需求与架构上下文 |
| 是否合并 | 人 | 责任归属不能转移 |
分工的实质是:AI 把人的注意力从"读完 400 行"压缩到"判断 12 条",省下阅读时间,不转移判断责任。
diff 大小与效果强相关。几十行的 PR 上模型能看清上下文,意见密度合理;上千行后注意力被稀释,出现"前面反复提、后面无人看"。应对是按文件或模块拆分评审任务,而不是把整条 diff 塞进去。
7. 可追踪性与度量
没有追踪的 AI 评审无法优化:你不知道它提的意见被采纳了多少,也就不知道调参有没有用。
-- 每条意见落库,靠 comment_id 回采状态
CREATE TABLE ai_review_comment (
id BIGINT PRIMARY KEY,
pr_id BIGINT NOT NULL,
file_path VARCHAR(512),
line_no INT,
category VARCHAR(32), -- security / correctness / convention / style
severity VARCHAR(16),
confidence DECIMAL(3,2),
model VARCHAR(64),
prompt_ver VARCHAR(16), -- 提示版本,必须记
status VARCHAR(16), -- open / adopted / rejected / outdated
created_at TIMESTAMP
);
| 指标 | 定义 | 用途 |
|---|---|---|
| 有效意见率 | 被采纳或被讨论的意见 / 总意见 | 衡量噪声水平,低于三成说明降噪不够 |
| 采纳率 | adopted / 总意见 |
衡量价值,但会被"顺手改"污染 |
| 逃逸缺陷率 | 合并后发现的缺陷 / 合并 PR 数 | 唯一能证明"漏了什么"的指标 |
| 人工评审时长 | 从提交到首次有效评论 | 衡量省下的注意力 |
prompt_ver 必须记。否则提示改过之后,历史采纳率不可比,"这次调整到底有没有用"永远回答不了。
# 按类别统计采纳率,找出最该关掉的类别
jq -r '.category + "\t" + .status' comments.jsonl \
| awk -F'\t' '{n[$1]++; if($2=="adopted") a[$1]++} END{for(k in n) printf "%s %.2f\n", k, a[k]/n[k]}'
8. 引入路径:先非阻断,再谈阻断
# .github/workflows/ai-review.yml —— 第一阶段:只评论,不拦合并
- name: ai-review
if: github.event_name == 'pull_request'
run: |
ai-review review --diff "${{ github.event.pull_request.diff_url }}" \
--config .ai-review.yaml \
--out review.json
- name: post
run: ai-review post --input review.json --mode comment # 关键:comment 而非 request_changes
| 阶段 | 行为 | 退出条件 |
|---|---|---|
| 1. 影子模式 | 只发评论,不阻断 | 有效意见率稳定在可接受区间 |
| 2. 阻断 major | major 及以上阻断,minor 折叠 |
误阻断率低到团队能接受 |
| 3. 收窄范围 | 只对核心模块阻断 | 逃逸缺陷率有下降趋势 |
跳到第 2 阶段是常见的失败方式:误阻断几次之后,团队会要求整体关掉,之后很难再推第二次。
9. 排错
| 现象 | 原因 | 处理 |
|---|---|---|
| 意见集中在文件前几行 | diff 过长,注意力被前段消耗 | 按文件/模块拆分评审任务 |
| 全是"建议加注释" | 未关闭 docs/style 类,未写排除项 | 提示里显式"不要报",配置里关类别 |
| 反复提同一处 | 同一 PR 多次触发,未去重 | 按 file+line+category 去重,只留最高严重度 |
| 提了不存在的 API | 训练语料滞后于依赖版本 | 把依赖版本与关键接口签名放进上下文 |
| 阻断了一条正确代码 | 置信度门槛过低,或规范条目本身模糊 | 提高门槛;规范改成可判定句式 |
| 采纳率虚高 | 作者顺手改了格式,被算作采纳 | 采纳判定看代码语义变化,不看行数变化 |
10. 边界:这时候 AI 评审只会放大混乱
| 场景 | 为什么不适合 | 该怎么做 |
|---|---|---|
| 探索性重构 | 没有"正确"可对照,意见全是风格偏好 | 关闭评审,靠测试与人工走查 |
| 一次性脚本/迁移 | 不进主干、不留维护成本 | 跳过 |
| 团队规范本身不清晰 | 模型只能猜,猜错就是噪声 | 先把规范写成可判定条目 |
| 强业务领域代码 | 模型缺领域知识,会把正确实现报成错误 | 只开安全与正确性两类 |
| 早期原型 | 代码会被整体重写 | 等到接近合并再评 |
反过来,最适合的场景高度一致:有明确规范、有测试覆盖、PR 规模适中、缺陷形态偏模式化的后端服务代码。这类仓库里 AI 评审的性价比最高,因为它要做的正是"把已经写下来的规则逐条对照"。
11. 自测
- 为什么"意见分布不可用"比"提不出意见"更致命?
- 能可靠拦住的第一、二类,共同点是什么?
- 拦不住的四类里,哪一类最可能在评审时看起来完全正常?
- 提示里第 4 段(不要报什么)为什么不能省?
prompt_ver不记录,会让哪个问题永远无法回答?- 为什么第一阶段必须是非阻断的评论模式?