我拿真实 Java 项目测了下 AI 修 BUG:真正难的不是写代码,是做工程判断
最近我拿一个真实前后端项目测了下 XunOPC。
不是刷题,也不是让它写一个独立函数,而是直接放进真实工程里,让它自己找问题、判断风险、修改,再复核。
后端一共有 583 个 Java 文件。
这次测完以后,我最大的感受不是“AI 现在写代码真快”,而是另一件事:
Coding Agent 真正难的,可能已经不是代码生成,而是工程判断。
现在让模型写 CRUD、补接口、改 SQL,甚至生成测试,都不算特别新鲜。
真正进到一个老项目以后,问题通常不是:
这一行代码怎么写?
而是:
为什么这里会出问题?
以及:
还有哪些地方可能有同样的问题?
这次有两个细节,我觉得特别典型。
一个 BUG,不能只修报告里点名的那几个文件
其中一个问题出在审批流程里。
最开始报告已经定位到 3 个服务存在同类风险,核心都和 handleRejectTarget 的调用有关。
如果只是按任务修 BUG,正常逻辑应该是:
找到 3 个文件
↓
修改
↓
验证
↓
结束
任务也算完成了。
但 XunOPC 没停。
它继续全局搜索 handleRejectTarget 的所有调用,一共找到了 6 处,然后逐个检查调用上下文。
最后又发现两个原报告没有点名的服务:
IpChanges
LineApply
实际上也存在同一种风险。
也就是说,报告最开始说 3 个,最后实际覆盖到了 5 个服务。
这个地方让我印象挺深。
因为它做的不是简单的“多改两个文件”。
而是一件我们平时修线上 BUG 很常见的事情:
从一个 BUG,反推一个 BUG 类型。
比如线上报一个 NPE。
最省事的修法当然是:
if (obj != null) {
...
}
然后提交。
但做过一段时间项目的人一般都会继续搜:
这个方法还有谁在调用?
这个错误写法是不是在别的 Service 里也复制过?
是不是同一个设计问题已经散在其他模块里,只是还没炸?
真实工程里,很多 BUG 都不是一个点。
只是某一个点先暴露出来而已。
所以我现在看 Coding Agent,已经不太在意最后有没有一句:
Task completed.
我更想看的是:
它是在修“这一条 BUG”,还是已经理解了“这一类 BUG”。
这两个能力差很多。
@Transactional 这个坑,更能看出有没有工程脑子
第二个问题是定时任务重复生成续签记录。
问题链路大概是:
插入续签记录
↓
更新“已复制”状态
如果第一步成功,第二步失败,而且两个动作不在同一个事务里,那么数据库就会留下这种状态:
续签记录:已经生成
原记录:仍然显示未复制
下一次定时任务再跑:
发现未复制
↓
再次生成续签记录
重复数据就出来了。
看到这里,Java 开发第一反应应该都差不多:
@Transactional
protected void doExecute() {
...
}
看起来一行就解决。
但这次偏偏不能这么干。
XunOPC 往上继续看了基类和调用关系,发现 doExecute 是 protected,而且存在同类内部调用。
这时候 Spring 事务那个经典坑就出来了。
声明式事务依赖代理。
正常情况是:
调用方
↓
Spring Proxy
↓
目标 Bean
↓
@Transactional 方法
代理在中间帮你开启、提交或者回滚事务。
但如果类内部这么调:
public void execute() {
doExecute();
}
实际相当于:
this.doExecute();
这次调用并不会重新经过 Spring Proxy。
于是就可能出现一个很坑的情况:
注解写了,代码也完全正常,但事务压根没有按你以为的方式生效。
这种代码其实比直接报错更危险。
因为它“看起来对”。
最后 XunOPC 没有硬套 @Transactional,而是换成了 TransactionTemplate。
大概就是把两步操作显式包进同一个事务:
transactionTemplate.execute(status -> {
createRenewRecord();
updateCopiedStatus();
return null;
});
这样结果就清楚了:
两步都成功
=> commit
中间任何一步失败
=> rollback
我觉得这里真正有意思的,不是 AI 会不会用 TransactionTemplate。
这个 API 模型当然见过。
真正难的是:
它知道什么时候“最标准的答案”不能用。
这就是工程判断。
真实项目里,最花时间的往往不是写,而是读
现在很多 AI 编程 Demo 都很爽。
一句话输入进去,几百行代码直接出来。
但真实项目里,最浪费时间的部分经常不是写代码。
是读代码。
读调用链。
读基类。
读历史实现。
读事务边界。
读状态变化。
读为什么五年前有人要这么设计。
有时候一个 BUG 只看当前文件,五分钟就能给出修法。
但顺着调用关系再看两层,会发现刚才那个修法根本不成立。
所以我现在反而比较关注 Coding Agent 有没有这些动作:
- 会不会主动扩大搜索范围;
- 会不会顺着调用关系继续看;
- 会不会翻基类和已有实现;
- 会不会理解框架机制,而不是只匹配代码模式;
- 找到一个明显答案以后,会不会再确认这个答案在当前工程里真的生效。
这些能力其实已经不太像“代码生成”。
更像在理解一个系统。
我现在最怕 AI 给我“看起来完全正确”的代码
AI 如果写了一段根本编译不过的代码,其实不可怕。
IDE 红了。
CI 挂了。
测试失败了。
马上就能发现。
真正危险的是这种:
@Transactional
public void process() {
...
}
看起来很合理。
或者:
if (result != null) {
...
}
也很正常。
但它可能完全没有解决根因。
因为真正的问题藏在代理机制、调用链、并发、数据库状态或者业务流程里。
这种 Patch 最容易混过去。
Reviewer 第一眼甚至会觉得:
“嗯,应该就是这么改。”
上线以后继续炸。
所以我现在越来越觉得,AI Coding 真正进入生产环境以后,评价标准不能只是:
能不能写出代码?
而应该变成:
它知不知道自己为什么这么改?
这次测试也不是“AI 已经能替程序员”
这次就是一次真实项目测试,样本量也只有 1。
不能因为一次表现就直接得出“某个 Agent 一定比另一个强”。
而且这次也有明显不足。
一共修改了 7 个文件,最后做了静态复查,但当时测试环境没有 Maven,所以没有完成完整编译验证。
这个问题我觉得该写就得写。
真实工程测试如果最后只剩:
“全自动。”
“完美修复。”
“全面领先。”
基本就没什么参考价值了。
我真正觉得有意思的是,这次开始看到一些过去更像人类工程师才会做的动作:
发现一个问题以后,继续往外扩。
看到标准修法以后,没有立刻套模板。
因为 Spring 的真实调用机制,放弃了最直觉的 @Transactional。
最后换成更符合当前工程结构的实现。
这些东西,其实已经不是“AI 会不会写代码”的问题了。
更像是:
AI 有没有开始理解工程。
代码生成会越来越便宜。
真正难的,可能一直都是判断。
尤其是那种:
知道一段代码看起来没问题,但其实不能这么写。