AI 反复改不好同一个 Bug?别急着抽卡,改用这套排查流程
同一个 bug 交给 AI 改了五六次还在报错,回复里动辄「找到根本原因」「加了兜底」,跑起来却依旧失败。这篇文章拆解 AI 反复修不好问题的常见原因,给出从复现、诊断、验证到止损重开的具体流程和检查清单,并说明「多抽几次」和「停下来重构上下文」各自适合什么场景。
一个 bug 交给 AI 助手改到第六次,运行结果还是同样的报错——这种体验很多人都经历过。更让人无奈的是过程:它先说「已定位根本原因」,改完一轮没解决,又补一段「兜底逻辑」,再跑还是失败。于是对话越拖越长,代码越改越乱,最后甚至说不清当前版本和最初那个版本差在哪。
这类问题的关键,通常不在模型不够聪明,而在整个调试回路没有被组织成一件可以验证的工程工作:没有稳定的复现步骤、没有失败的测试、没有明确的「改对了」的判据,AI 只能根据一段报错文字做局部猜测。下面把常见原因、可操作的流程和几个有分歧的做法拆开讲。
为什么会陷入「改一次、错一次」的循环
信息不完整,AI 只能猜
只贴一段报错栈,不说明如何触发、环境版本、最近改过什么、预期行为是什么,模型就只能根据它对常见框架的经验去猜。猜测在简单问题上命中率高,在耦合较深的业务逻辑里几乎必然偏离。
把「兜底」当成修复
「兜底」本身不是错,但它常被用来掩盖没找到的因果链:加个 try/catch 吞掉异常、给空值塞默认值、把超时时间调长、把重试次数加上去。表面上程序不崩了,实际错误还在,换个输入又会冒出来。看到这类改动时,要追问一句:这段代码在错误发生时,原本应该发生什么?
缺少可判定的失败信号
如果「修好了没有」只能靠肉眼跑一遍、看一眼日志,那每一次尝试都是一次模糊评估。没有失败的测试用例作为判据,AI 和人都容易把「这次没报错」误认为「问题解决了」。
上下文被污染
长对话里堆着好几轮失败尝试的代码片段和错误结论。模型容易沿用早前那条错误思路,甚至把已经被推翻的假设当事实。对话越长,这种惯性越明显。
动手之前:把问题整理成一份可直接消化的输入
花十分钟准备下面这些内容,往往比再多改三轮更有效。
- 最小复现:能触发问题的最短路径,命令行、请求参数或测试代码。
- 完整错误:完整堆栈或日志,不要只截最后一行;包括退出码。
- 环境信息:语言与运行时版本、依赖版本、操作系统、部署方式。
- 时间线:这个功能上次正常是什么时候,之后改动过哪些提交。
- 预期与实际:一句话写清「应该发生什么」和「实际发生了什么」。
- 已尝试过的改动:列出改过什么、结果如何,避免重复试错。
如果连最小复现都做不出来,先别急着让 AI 改代码,让它帮你设计复现步骤和日志埋点更划算。
一套可落地的排查流程
第一步:把 bug 固化成一条失败的测试
在让任何人(或任何模型)动手前,先写出一条当前会失败、修复后应当通过的测试或最小脚本。
# 先确认它确实失败
pytest tests/test_order.py::test_refund_partial -x -q
这条用例就是后续所有尝试的裁判。没有它,讨论「是否修好」只能靠感觉。
第二步:先诊断,不改代码
明确要求:本轮只输出分析,不要给补丁。让它给出多个并列假设,每个假设附上「如何验证」的具体方法,例如查哪段日志、打什么断点、看哪个字段。然后由你去验证,而不是让它一次性全部改掉。
可用的指令示例:「列出三个可能导致该报错的原因,按可能性排序。不要修改代码,对每个原因给出验证手段和我应该查看的文件与行号。」
第三步:一次只改一处,逐次验证
拿到验证结果后再让它改,并且限定范围:只改与已确认原因相关的最小代码块。每轮结束后先看 diff 再跑测试。
git diff --stat
git diff
pytest tests/test_order.py::test_refund_partial -x -q
如果这一轮没通过,回滚再试下一个假设,不要让失败改动层层叠加:
git stash push -m "attempt-3-failed"
第四步:要求它给出证据而不是结论
「已找到根本原因」这句话本身没有信息量。把它换成可核查的要求:指出具体文件、函数、行号,说明从哪条日志或哪个分支判断出来,并解释为什么这个原因能解释全部现象,而不只是其中一条。解释不完整,就说明还没定位。
第五步:及时止损,重开会话
同一问题试过两三轮仍无进展,就该怀疑是上下文或方向出了问题。做法是:新建一个干净会话,只带最小复现、完整报错、当前相关文件,把前面所有失败结论丢掉。用一张白纸重来,往往比在旧对话里继续追问更省时间。
第六步:换视角交叉验证
换一个模型、或者把问题描述改成「假设这段代码是别人写的,请审查它的错误处理路径」,常常能跳出原有的思维定式。也可以自己先用 git bisect 或日志缩小范围,再让 AI 在更小的范围内分析。
两种常见做法,适用场景不同
| 做法 | 适用场景 | 代价 |
|---|---|---|
| 同一会话里多试几次 | 单文件、边界清晰的小问题;配置错误;明显的拼写或类型问题 | 快,但容易累积错误改动,越改越乱 |
| 停下来重开、重构上下文 | 跨模块调用、并发与时序、状态不一致、涉及第三方服务 | 前期多花十几分钟,但能避免在错误方向上反复投入 |
「多抽几次」在简单问题上确实有效,但判断标准要明确:如果连续两次尝试改动的方向完全不同,说明它也在猜,这时继续抽卡只是在碰运气。
需要留意的几个坑
- 不要让它顺手改测试。修复过程中如果出现删除断言、放宽比较、加
skip的改动,要立刻停下来确认理由。 - 警惕防御式补丁。默认值、空 catch、无限重试、把类型断言改成宽类型,这些都在掩盖问题而不是解决它。
- 控制单轮改动规模。一次改十几个文件,即使碰巧跑通了,也无法判断是哪一处起了作用。
- 保留现场。失败版本的 diff 存成 stash 或临时分支,方便回头看。
- 涉及外部依赖时先隔离。用 mock 或本地桩把第三方服务切掉,确认问题在不在自己这一侧。
关于运行环境
如果排查过程需要长时间挂着跑测试、翻日志,或者让 CLI 类的 AI 工具持续执行多轮任务,本地机器休眠、网络抖动会让会话中断、日志断档。需要长期在线时,可以把这类工作放到一台机房里的整机远程 Mac 上按月租用,远程连接使用,网络和在线时长都更可控,属于可选方案之一,不是必须。
小结
AI 反复修不好同一个 bug,多数时候不是能力问题,而是输入太模糊、判据太软、方向错了还不停。把流程改成:先写失败用例 → 只诊断不改码 → 一次改一处 → 看 diff 再验证 → 两三轮无果就重开,能挡掉大部分无效循环。同时记住,「兜底」和「根本原因」只是话术,真正有意义的是它能否指出具体位置、给出可验证的证据,并解释全部现象。模型和工具的能力更新很快,具体用法请以官方最新文档为准。