3056 字
约 10 分钟
16
S24-A05 · AI 辅助代码评审:能拦什么、漏什么

一个 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. 自测

  1. 为什么"意见分布不可用"比"提不出意见"更致命?
  2. 能可靠拦住的第一、二类,共同点是什么?
  3. 拦不住的四类里,哪一类最可能在评审时看起来完全正常?
  4. 提示里第 4 段(不要报什么)为什么不能省?
  5. prompt_ver 不记录,会让哪个问题永远无法回答?
  6. 为什么第一阶段必须是非阻断的评论模式?
S24-A05 · AI 辅助代码评审:能拦什么、漏什么
https://www.clxhxhhr.top/posts/6134/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。