我拿一个真实 Java 项目测了下 AI 修 BUG,真正拉开差距的不是会不会写代码
我拿一个真实 Java 项目测了下 AI 修 BUG,真正拉开差距的不是会不会写代码
最近我拿一个真实前后端项目测了下 XunOPC。
不是刷 LeetCode,也不是让它写一个孤立函数,而是直接把一个实际工程扔进去,让它自己找问题、判断风险、修复,再做复核。
项目后端一共有 583 个 Java 文件。整个过程中我没有一步一步告诉它“这里怎么改”,只给了任务目标。
这次测完以后,我最大的感受反而不是“AI 现在代码写得真快”。
现在模型会写 CRUD、补接口、改 SQL、生成测试,其实已经没那么稀奇了。真正有区别的,是它发现一个问题以后,会不会继续往下追,以及它能不能判断一个“看起来正确”的修法,在当前工程里到底真的生不生效。
这次有两个地方,我觉得特别典型。
1. 报告只指出 3 个问题,它自己又往外扩了
其中一个问题出在审批流程。
一开始已经定位到 3 个服务里存在同类风险,核心都和 handleRejectTarget 的调用有关。
如果只是按报告修 BUG,正常流程其实很简单:
- 找到这 3 个文件;
- 修改对应逻辑;
- 验证;
- 提交。
任务也算完成了。
但它没有停在这里,而是继续全局搜索 handleRejectTarget 的所有调用。
最后一共找到了 6 处,再逐个看调用上下文,结果发现另外两个原报告里没有点名的服务——IpChanges 和 LineApply——其实也存在同类问题。
也就是说,最初报告只指出了 3 个风险点,最后实际覆盖到了 5 个服务。
这个地方我觉得挺有意思。
因为它做的不是“多改了两个文件”,而是做了一件真实开发里很常见的事:
从一个 BUG,反推一个 BUG 类型。
比如线上报了一个空指针,我一般不会只在报错那一行加个判空。
第一反应通常是继续搜:
这个方法还有谁在调用?
这个写法是不是在其他 Service 里也复制过?
是不是同一个设计问题已经散落在多个模块?
真实项目里很多 BUG 都是这样。你今天修掉一个,只能说明这个点先爆了,不代表其他地方没有同样的问题。
所以我现在看 Coding Agent,越来越不太在意它最后弹出来一句“任务完成”。
我更关心的是:
它是在修这一条 BUG,还是已经理解了这一类 BUG。
这两个能力差别挺大。
2. @Transactional 看起来能解决,实际上可能根本不生效
第二个问题更典型。
有一个定时任务会重复生成续签记录。
问题链路大概是这样:
插入续签记录
↓
更新“已复制”状态
如果第一步成功,第二步失败,而两个操作又没有处在同一个事务里,那么数据库最终会留下一个很尴尬的状态:
续签记录已经插入了,但原记录还是“未复制”。
下一次定时任务再执行:
检测到未复制
↓
再次生成续签记录
于是重复数据就出来了。
看到这里,Java 开发第一反应基本都会想到:
@Transactional
protected void doExecute() {
// insert
// update
}
看起来没问题。
但这里刚好踩到了 Spring 事务里一个很经典的坑。
XunOPC 没有直接加 @Transactional,而是继续往上看了基类和调用关系,发现目标方法 doExecute 是 protected,而且存在同类内部调用。
这时候问题就来了。
Spring 声明式事务本质上依赖代理。
正常情况下,调用链应该类似:
外部对象
↓
Spring Proxy
↓
目标 Bean
↓
@Transactional 方法
代理有机会在方法执行前开启事务,执行完成后提交或回滚。
但如果是类内部直接调用:
public void execute() {
doExecute();
}
本质上是:
this.doExecute();
这个调用并没有重新经过 Spring Proxy。
于是就可能出现一个很坑的情况:
注解写了,代码也能编译,IDE 也不报错,但事务压根没有按你想的方式工作。
这种 BUG 比直接报异常更麻烦。
因为它“看起来完全正确”。
最后它没有硬套 @Transactional,而是改成了 TransactionTemplate。
逻辑大概类似:
transactionTemplate.execute(status -> {
createRenewRecord();
updateCopiedStatus();
return null;
});
把两步操作显式放到同一个事务回调里。
结果就是:
插入成功 + 更新成功
=> 提交
插入成功 + 更新失败
=> 整体回滚
这才真正解决了重复生成的问题。
我觉得这个地方比“AI 会不会写 Spring”更有意思。
因为 @Transactional 怎么用,模型肯定见过无数遍。
真正难的不是:
这个注解怎么写?
而是:
当前这个调用链里,这个注解到底有没有用?
这已经不是代码补全问题了,而是工程判断。
3. Coding Agent 真正难的是读工程,不是生成代码
现在很多 AI 编程 Demo 看起来都很爽。
输入一句需求,几秒钟生成几百行代码。
但真实项目真正费时间的部分,经常根本不是“写”。
而是读。
读调用链。
读基类。
读已有实现。
读事务边界。
读数据库状态。
读为什么当年有人要这么设计。
同样一个 BUG,如果只看当前文件,可能五分钟就能给出一个修法。
但把上下游都看完以后,可能会发现这个修法根本不能用。
所以我现在会比较关注 Coding Agent 有没有几个能力:
- 会不会主动扩大搜索范围;
- 会不会顺着调用关系继续往上看;
- 会不会检查项目里已经存在的类似实现;
- 会不会判断框架机制,而不是只匹配语法;
- 会不会在发现“常规答案”后,再验证一下这个答案在当前工程里是否成立。
这几个能力,其实都不像传统意义上的“代码生成”。
更像一个程序员在理解系统。
4. 为什么我觉得“看起来正确”比直接写错更危险
AI 写错一个方法,其实没那么可怕。
编译失败了,一眼就能看到。
测试挂了,也很好定位。
真正危险的代码一般长这样:
@Transactional
public void xxx() {
...
}
看着没问题。
或者:
if (result != null) {
...
}
也很合理。
但是因为调用链、状态机、并发、事务传播或者框架机制的问题,它实际上没有解决根因。
这种代码最容易混进真实项目。
因为 Reviewer 看第一眼也会觉得:
“嗯,像是这么回事。”
然后上线以后继续炸。
所以我现在越来越觉得,AI Coding 真正要进入生产环境,评价标准不能只是:
能不能把代码写出来。
而应该是:
它能不能知道自己为什么这么改。
5. 这次测试也有明显不足
这次测试只是一次真实项目实测,样本量就是 1,不能用一次结果证明某个 Agent 永远更强。
另外,这次一共修改了 7 个文件,最后做了静态复查,但当时测试环境没有 Maven,所以没有完成完整编译验证。
这一点我觉得也应该明确写出来。
真实工程测试如果最后只剩“完美修复”“全面领先”,其实参考价值反而不高。
工具有做得好的地方,也一定有没覆盖到的地方。
我更关心的是它到底表现出了什么能力。
这次让我比较意外的,不是它修掉了几个 BUG。
而是它开始表现出一些我原本更习惯在人类工程师身上看到的动作:
发现一个问题以后继续扩大搜索范围;
看到一个标准解法以后,没有直接套模板;
因为 Spring 的实际调用机制,放弃了看起来最简单的 @Transactional;
最后换成了更符合当前工程结构的事务方案。
这些东西,我觉得才是 Coding Agent 接下来真正值得看的方向。
代码生成本身可能会越来越便宜。
工程判断,才是下一阶段真正难复制的部分。
更多推荐



所有评论(0)