如何提升 AI 编码准确性:从"生成代码"到"验证结果"
使用 AI 编程助手时,经常会遇到一种情况:代码能编译,解释也很完整,但放进真实项目后,依然需要反复修改。
有时是误解了业务规则,有时是忽略了已有实现,也有时是为了"健壮性"增加了错误的默认值和异常处理。
提升 AI 编码准确性,不能只依赖一句"请认真检查"。更有效的方法,是让任务具备明确需求、相关上下文、合理边界和可执行的验收标准。
本文以 Java 开发为例,介绍一套可以直接用于日常工作的实践方法。
一、先明确:什么叫编码准确?
"代码没有语法错误"只是最基础的要求。
一个修改真正正确,至少需要满足以下几个方面:
| 维度 | 需要回答的问题 |
|---|---|
| 需求正确 | 实现的是用户真正需要的行为吗? |
| 业务正确 | 是否符合金额、权限、状态流转等规则? |
| 项目兼容 | 是否遵循现有接口、依赖和代码约定? |
| 修改完整 | 是否覆盖了受影响的调用方和必要配置? |
| 验证可信 | 是否实际执行了相关检查,而不只是声称正确? |
例如,查询用户不存在时返回一个空对象,可能不会报错,却违反了接口原本"用户不存在应返回业务错误"的约定。
因此,首先要明确:程序继续运行,不等于程序正确运行。
二、把模糊需求变成可验收的行为
下面这种需求很常见:
帮我优化用户查询接口,增加异常处理。
但"优化什么""哪些异常需要处理""失败时返回什么",都没有明确答案。AI 必须自行补齐这些空白,而补齐的内容可能并不符合业务预期。
可以改成:
diff
请修复用户查询接口在用户不存在时发生空指针异常的问题。
预期行为:
- 用户存在时,保持当前返回结构。
- 用户不存在时,使用项目已有的用户不存在业务异常。
- 数据库访问失败时,继续交给现有统一异常处理机制。
- 不返回空对象或默认用户。
约束:
- 不修改接口路径。
- 不新增依赖。
- 不重构无关模块。
这样的需求将"你觉得怎么处理"变成了"按明确规则实现"。
如果需求复杂,可以先列输入输出:
| 场景 | 预期行为 |
|---|---|
| 用户存在 | 返回已有格式的用户数据 |
| 用户不存在 | 返回项目约定的业务错误 |
| 参数不合法 | 按现有参数校验规则处理 |
| 数据库不可用 | 保留故障语义,不伪装成用户不存在 |
这张表可以同时指导实现和测试。
三、提供相关上下文,而不是堆积全部文件
上下文不足,容易导致猜测;无关信息过多,又会让关键约束难以识别。
以修复一个 Java 接口为例,通常优先需要:
- 接口入口及请求、响应类型。
- 对应 Service 和数据访问逻辑。
- 相关异常定义与统一异常处理方式。
- 相近功能的现有实现。
- 相关测试与构建配置。
不必一开始就把整个仓库全部读取。
可以这样要求 AI:
先定位目标接口及直接调用链,再读取相关实现和测试。
优先参考项目中相似功能的处理方式。
为结论标注文件路径和方法名。
信息不足时指出具体缺口,不要猜测方法或配置项。
找到完成本次修改所需的证据后,停止扩大搜索范围。
这里尤其要区分两种信息:
已经确认的事实 ,例如某个查询确实可能返回 null。
尚未验证的假设,例如某个分支"可能只会被定时任务调用"。
如果两者混在一起,假设就容易变成修改依据。
四、先定位原因,再要求最小必要修改
修复 Bug 时,直接要求 AI "把报错消除",容易得到表面修补。
例如:
kotlin
try {
return convert(userRepository.findById(userId));
} catch (Exception e) {
return new UserDTO();
}
异常消失了,但用户不存在、数据库故障、转换逻辑错误,都被统一变成空对象。
更合理的处理过程是:
- 找到具体失败位置。
- 确认触发条件。
- 查明预期行为。
- 修改导致错误的分支。
- 验证相关行为。
可以要求:
请先说明问题发生的具体位置、触发条件和依据。
随后实施最小必要修改。
不要通过扩大异常捕获范围、返回默认值或删除校验来掩盖问题。
如果发现根因与当前需求描述不同,先解释差异。
"最小必要修改"不等于只改一行。需要同步修改调用方或测试时,应当完成;它限制的是与目标无关的顺手重构。
五、把业务不变量写清楚
有些代码规则不能交给 AI 自行选择,尤其是金额、权限和事务相关逻辑。
例如:
diff
必须保持以下规则:
- 金额继续使用 BigDecimal。
- 精度与舍入方式沿用项目现有约定。
- 不改变订单状态迁移条件。
- 不扩大当前用户的数据访问范围。
- 不增加可能重复执行写操作的自动重试。
这些规则称为业务不变量:无论实现怎么调整,都必须保持成立。
它们比"注意安全""考虑边界情况"更有效,因为可以逐条检查。
同时,不要为所有项目机械套用同一份规则。应从当前代码和真实业务中提取约束,避免用一套泛化规范覆盖项目已有设计。
六、让测试验证需求,而不是重复实现
AI 写出了测试,并不代表修改已经受到有效保护。
一个常见问题是:实现猜错了规则,测试又按照同一个错误猜测编写,最终代码和测试一起通过。
避免这种情况,应先确定验收条件,再检查测试是否覆盖这些条件。
例如,手机号脱敏的规则已经明确:
| 输入 | 预期输出 |
|---|---|
null |
空字符串 |
"12345" |
保持原样 |
"13812345678" |
"138****5678" |
那么测试应验证这些业务结果,而不是只检查"方法执行没有抛异常"。
对于 Bug 修复,如果条件允许,还应确认:新增回归测试能够在修复前暴露问题,在修复后通过。
但不必为每一个低风险文本修改都增加测试。验证方式应与风险匹配:
| 改动类型 | 合适的验证方式 |
|---|---|
| 文案、注释 | 查看差异,检查格式 |
| 业务方法 | 针对性单元测试 |
| 数据访问逻辑 | 在测试数据库中进行集成验证 |
| API 行为 | 检查响应结构、状态与兼容性 |
| 权限、事务、金额逻辑 | 增加针对关键规则的验证 |
七、要求可核对的验证结果
"已经检查,没有问题"很难作为交付依据。
更有价值的说明是:
diff
修改文件:
- UserService.java
- UserServiceTest.java
实际执行:
- 指定业务测试,结果通过。
未验证:
- 依赖数据库的集成测试,当前环境缺少测试数据库。
剩余风险:
- 需要在集成环境确认统一异常处理后的接口响应。
可以直接要求 AI:
markdown
交付时分别说明:
1. 改了什么,为什么这样改。
2. 实际执行了哪些验证,以及结果。
3. 哪些检查未执行,原因是什么。
4. 是否还有影响验收的未解决问题。
不要把"已生成测试"描述成"测试已通过"。
验证透明不能消除所有错误,但能避免把未验证的部分误当作已经完成。
八、长任务中,保存决定和状态
任务持续时间越长,需求修正和代码变更越多,越需要明确记录当前状态。
一份简短检查点就可以包含:
当前目标:
修复用户不存在时的异常。
已确认:
查询结果可能为空。
项目已有可复用的用户不存在异常。
已决定:
沿用该异常,不新增返回结构。
已完成:
业务逻辑修改、针对性测试。
未完成:
接口层响应验证。
下一步:
运行相关接口测试。
重点是保存已确认的事实和已作出的决定,而不是复制全部对话。
相关文件改变后,要更新受影响的结论;恢复任务时,先读取检查点,再按需核对代码。这样可以减少重复探索,也能避免继续使用过时信息。
九、一份可以直接使用的任务模板
diff
请完成以下编码任务:
【目标】
说明具体问题与预期行为。
【项目上下文】
技术栈、相关模块、错误信息及已知调用链。
【业务约束】
列出必须保持的接口、权限、金额和状态规则。
【执行要求】
- 先定位原因,并用代码位置说明依据。
- 优先复用项目已有实现。
- 信息不足时指出缺口,不猜测 API 或业务规则。
- 进行最小必要修改,避免无关重构。
- 不用默认值或宽泛异常捕获掩盖失败。
【验收标准】
列出正常、异常和关键边界场景。
按改动风险执行相关验证。
【交付说明】
列出改动文件、实际验证结果、未验证项和剩余问题。
对于简单任务,可以压缩为一句:
请按项目已有约定完成这个修改,保持接口兼容,只改必要代码,并说明实际验证结果及未验证项。
结语
提升 AI 编码准确性,需要把"正确"变成可以执行和检查的标准。
需求明确,AI 才不必猜测业务;上下文相关,修改才有依据;约束清楚,执行才不会随意扩展;验证真实,交付才值得信任。
高质量的 AI 编码协作,从定义正确结果开始,以证据证明结果结束。