2917 字
约 9 分钟
14
S24-X14 · AI 生成代码的审查与验收

AI 生成的代码有个共同特征:读起来比写起来顺。命名规范、注释齐全、结构工整,评审时一眼扫过去挑不出毛病。问题在于毛病不在字面上,在你没去跑的那条路径上。

下面这套流程解决一件事:怎么在不把每行代码都重读一遍的前提下,判断这段生成代码能不能进主干。


1. 缺陷分布:它们通常错在哪

缺陷类型 外观 典型表现 发现方式
边界未处理 主路径完美 空集合、null、0 条记录、超长字符串时崩 构造边界输入跑一次
异常被吞 try-catch 齐全 catch (Exception e) { log.warn(...) } 然后返回默认值 看 catch 块是否有重抛或状态变更
依赖不存在的方法 调用链看着合理 list.stream().first()、Optional.getOrElse() 这类不存在的 API 编译
配置项名字编造 配置写得像真的 spring.datasource.hikari.max-pool-size(真实键名不同) 启动 + 核对官方键名
日志与错误信息空泛 有日志 log.error("error occurred"),无 id、无入参 搜日志语句里的占位符数量
版本不匹配 语法正确 用了 JDK 21 的 API 而项目是 17 编译或核对 pom.xml

最值得注意是第四类:配置项名字编造。它编译通过、测试通过(测试里没加载那段配置),只在真实启动时暴露,而且报错信息往往指向别处。

一个经验判断:生成代码里"能编译但从未被执行"的行,是缺陷密度最高的地方。所以检查的重点不是读全量代码,是把没跑过的分支逼出来跑一次。


2. 五个检查点

按性价比排序,前两个能拦掉大部分问题。

检查点 具体动作 不做会怎样
编译与测试是否真跑过 要命令原文 + 输出尾部,自己再跑一遍 它说"已测试通过",实际没跑
外部调用的错误路径 逐个看超时、重试、降级、返回码分支 上线后第一个 500 就是它
并发与资源释放 看锁粒度、线程池来源、try-with-resources/finally 泄漏或死锁,压测才显形
与既有约定一致性 命名、包结构、返回体、异常体系 能跑,但破坏仓库一致性
有没有引入新依赖 git diff 看构建文件 供应链与体积成本无人评估

2.1 外部调用是重灾区

生成代码对"外部调用可能失败"的建模普遍偏乐观。逐条核对:

// 生成版本:超时、重试、降级全缺
public Price getPrice(String sku) {
    return restTemplate.getForObject(url + sku, Price.class);
}

// 验收要求:显式超时 + 失败语义 + 可观测
public Price getPrice(String sku) {
    try {
        return restTemplate.exchange(url + sku, GET, entity, Price.class,
                Map.of("connectTimeout", 300, "readTimeout", 800)).getBody();
    } catch (RestClientException e) {
        log.warn("price_fetch_failed sku={} err={}", sku, e.getClass().getSimpleName());
        return Price.fallback(sku);   // 降级策略必须写清楚来源
    }
}

RestTemplate 默认没有读超时,这条在多数项目里等于"永远等下去"。这类"默认值陷阱"是生成代码的固定弱点。人工核对时可以直接搜:

# 列出所有外部调用点,再逐个确认超时是否显式设置
$ rg -n "RestTemplate|WebClient|HttpClient" --type java -l
$ rg -n "setConnectTimeout|connectTimeout|Duration.ofMillis" --type java

两个结果集的差集,就是没设超时的地方。


3. 为什么"看起来合理"是最危险的通过条件

人评审代码时依赖两个信号:语法熟悉度和风格一致性。生成代码在这两项上得分极高,于是评审者的警觉被提前消耗掉了。

信号 生成代码表现 与真实质量的关联
命名规范 通常很好 弱
注释密度 偏高,甚至高于人手代码 弱,可能反向
结构对称 很好(模板化产物) 弱
错误分支完整 经常缺失 强
是否被实际执行过 常常没有 最强

结论很直接:风格类信号不能作为通过依据,执行证据才能。

危险的不是"它写错了",是"它写得让你不想查了"。一段 200 行、格式完美、注释齐全的代码,比一段 30 行粗糙代码更容易被放行。


4. 验证策略的四个层次

从便宜到贵,能上一层就不必上下一层:

  1. 跑测试:最快,但只覆盖已有断言的路径。生成代码常顺手改测试来迁就实现,所以先看测试文件有没有被改动。
  2. 跑真实入口:用真实请求/真实 CLI 参数走一遍,验证装配、序列化、配置加载。这一步最容易被跳过,也最能抓到问题。
  3. 看日志:确认日志里出现了预期的关键字段(id、耗时、结果码),而不是只有"进入方法"。
  4. 读代码:只在前三步有疑点时做,重点读错误分支和资源释放。
# 1. 测试:先确认测试文件本身没被改
$ git diff --stat HEAD -- '*Test.java'
# 2. 真实入口
$ curl -s -X POST localhost:8080/api/v1/orders/1/cancel -d '{}' | jq
# 3. 日志
$ grep -E "order_cancel|40901" logs/app.log | tail -20

5. 三种不可信信号

看到以下任一情况,把这段产出降级为"未验证草案":

信号 具体样子 正确处理
声称跑过但没给命令与输出 "已测试通过"、"本地验证无误" 要命令原文与输出尾部,自己复跑
引用不存在的 API 方法名合理但库里没有 编译;编译过了也要搜官方文档确认语义
注释比代码更详细地描述"应该" 注释写"这里应做幂等校验",代码没做 注释即承诺,逐条对照实现

第三类最隐蔽。它不是谎言,是计划与实现的错位:模型在注释里写下了正确设计,然后实现了简化版。


6. 让它自证

把"我相信你"变成"你给出证据"。要求固定成模板,每次都用:

完成后请给出:
1. 你执行过的命令原文,以及每条命令输出的最后 5 行
2. 你没有验证的假设清单(例如"假设 status 字段只有 4 个取值")
3. 你改动的文件列表,以及每个文件改了什么(一句话)
4. 你认为最可能出错的一处,以及怎么复现

不要写"应该可以"、"理论上没问题"。没跑过就写没跑过。

第 2 项价值最高。模型列出的"未验证假设"往往正好是你需要人工确认的业务前提,比它写的代码更有信息量。

6.1 小步提交与回滚

# 每个可验证单元一次提交,提交信息写清验证方式
$ git add -p
$ git commit -m "feat(order): 新增取消订单状态校验

验证:./mvnw -q test -Dtest=OrderCancelTest(3 passed)
未验证:并发取消同一订单的行为"

# 出错时回退到上一个已验证点,而不是继续叠补丁
$ git reset --hard HEAD~1

一次生成 800 行再评审,出问题时无法定位是哪一段引入的;一次 80 行、每步可跑,回退成本接近零。


7. 对生成代码的差异化要求

同样的功能,对 AI 产出的部分可以提高门槛,因为它补测试和补日志的边际成本几乎为零。

维度 人手代码常见标准 对生成代码的要求
测试 覆盖主要路径 覆盖关键路径 + 至少一条错误路径 + 一条边界
日志 关键节点有日志 每条错误日志必须带可定位的标识(id、参数、结果码)
注释 解释为什么 允许少写,但凡写"应该"的必须与实现一致
依赖 按需引入 新增依赖需说明为何不能用现有能力
配置 允许隐式默认 超时、池大小、重试次数必须显式写出

8. 排错:验收阶段卡住的情况

现象 原因 处理
编译报符号找不到 用了不存在的 API 或版本不符 核对构建文件里的依赖版本,让它按真实签名重写
测试通过但启动失败 测试未加载真实配置,键名编造 用生产 profile 启动一次;核对官方配置键名
本地通、容器里挂 写死 localhost、端口或路径 改成配置注入,检查 docker-compose.yml 的网络别名
行为与旧逻辑不一致 它顺手"优化"了原有分支 对比改动前的行为契约,把无关重构拆出去
日志里没有预期字段 只打了"进入方法",没打结果 要求在结果分支补标识字段
压测时线程数暴涨 自建线程池,未复用容器管理的池 改为注入既有 ExecutorService

最后一条的根因不在代码质量,在上下文缺失:它不知道项目里已有共享线程池。这类问题回到约定文件里补一条,比逐次纠正更省事。


9. 边界:验收能覆盖到哪里

场景 验收能做到 做不到
纯函数式逻辑 单测 + 边界用例基本可判 业务语义是否符合预期
CRUD 接口 真实请求可验证 长事务下的并发行为
性能相关改动 只能确认"没明显变慢" 需要压测与 profiler 才有结论
并发/分布式 代码可读、单测可过 竞态与顺序问题需专门构造
数据迁移 可验证样本 全量数据的例外形态

一句话:验收能证明"这段代码在验证过的输入下是对的",证明不了"它在所有输入下是对的"。这个差距只能靠测试覆盖率和灰度观察来缩小,不能靠评审。


10. 自测

  1. 生成代码里缺陷密度最高的,通常是哪一类行?
  2. 五个检查点里,哪两个能拦掉大部分问题?为什么排在前面?
  3. 为什么"命名规范、注释齐全"不能作为通过依据?
  4. 验证四层次中,哪一层最容易被跳过、又最能抓到装配类问题?
  5. 模型说"已测试通过"时,你要它补哪两样东西才可信?
  6. 一次生成 800 行与分四次各 200 行,回退成本的差别在哪里?
S24-X14 · AI 生成代码的审查与验收
https://www.clxhxhhr.top/posts/5968/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。