和 AI 一起调试时,先停止猜测
AI 越修越蠢:一个有数据支撑的现象
2025 年发表在 Nature Scientific Reports 上的研究测出了一个让人不太舒服的数字:LLM 在连续调试 2-3 轮后,调试能力下降 60-80%(Zhang et al., 2025)。研究者拟合出一条指数衰减曲线:
E(t) = E₀ × e^(-λt)
t 是调试轮次,λ 是衰减率。AI 越修越蠢——这是统计规律,不是我的错觉。
连续试补丁本质上是「猜测-验证」的随机搜索,每试一次引入一个新变量,搜索空间指数膨胀,命中根因的概率反而越来越低。
这篇文章讲的是一套我实际用下来更稳定的调试方法:不猜,先查。
猜测式调试为什么越修越乱
认知心理学的解释
认知心理学家 Daniel Kahneman 在《思考,快与慢》中提出了双系统理论:
- 系统 1(快思考):自动化的模式匹配,依赖直觉,速度快但容易出错
- 系统 2(慢思考):刻意的分析推理,依赖证据和逻辑,速度慢但更可靠
猜测式调试是典型的系统 1 行为。看到报错信息,AI 立刻调用训练数据中最常见的修复模式——try-catch、dynamic import、externals——就像一个见过大量 bug 的老程序员凭直觉开药方。直觉有时管用,但在复杂场景下更容易制造「确认偏误」(confirmation bias):一旦形成第一个假设,后续每一步修复都在寻找支持它的证据,而非证伪它。
2026 年 arXiv 上的研究(Chen et al., 2026)揭示了 AI 辅助调试中的 90 种认知偏差,其中三种最致命:
| 认知偏差 | 在调试中的表现 | 后果 |
|---|---|---|
| 锚定效应 | 被 AI 的第一个修复建议锁定 | 忽略其他更可能的根因 |
| 确认偏误 | 只关注支持当前假设的日志和错误信息 | 对反面证据视而不见 |
| 自动化偏差 | 盲目信任 AI 生成的代码,不做逐行审查 | 引入新 bug 而不自知 |
这三种偏差形成恶性循环:AI 给出猜测 → 我锚定在这个猜测上 → 只验证支持它的证据 → 修复失败 → AI 换措辞给出几乎相同的猜测 → 我再试 → 代码更混乱。
变量爆炸:猜测式调试的数学问题
每次「试一个修复」都引入一个新变量。假设初始问题有 1 个根因,试了 4 次修复就引入 4 个新变量,认知负荷呈指数增长。
更糟的是,每次失败的修复都会在代码中留下「疤痕」——多余的 try-catch、临时的 console.log、注释掉的旧代码。这些疤痕干扰我对原始问题的判断,让下一次猜测更不准。
Nature 的研究发现的 LLM 调试能力指数衰减,根因在于上下文被污染,而非模型能力下降。
系统化调试:从猜测到证据
科学方法在调试中的应用
1980 年代软件工程领域开始将科学方法引入调试。核心思想朴素:调试是做实验,不是改代码。
2026 年 arXiv 上的扎根理论研究通过观察专业开发者的调试行为,提炼出四个阶段:
- 复现:让问题稳定出现
- 心智模型构建:理解程序的预期行为与实际行为
- 假设生成与验证:提出根因假设并设计实验验证
- 修复与确认:实施最小修复并确认问题消失
关键洞察:修复是最后一步。修复之前需要完成复现、理解和验证三个阶段的积累。
AutoSD 研究(Kang et al., 2023/2025)将科学方法形式化:让 LLM 先提假设,再用调试器做实验,收集证据验证或反驳。「假设-实验-证据」的循环比直接让 LLM 生成修复代码的成功率高出显著幅度。
系统化调试的完整流程
两条核心约束:一次只改一个变量、每次验证有明确的通过/失败标准。这两条规则直接对抗变量爆炸和模糊判断。
三个真实案例
案例一:node:fs 进入了客户端 bundle
问题:pnpm build 报错 Module not found: Can't resolve 'node:fs',错误指向客户端组件。
猜测式修复:
// ❌ 猜测 1:加 try-catch → 构建依然失败
// ❌ 猜测 2:改成 dynamic import → 构建依然失败
// ❌ 猜测 3:加 webpack externals → 构建通过但运行时崩溃三轮尝试,引入三种修改,代码更混乱,还多了运行时崩溃的新问题。
系统化调试:
// ✅ 复现:pnpm build,确认错误稳定出现
// ✅ 读堆栈:
// file-utils.ts:3 → 顶部 import 了 node:fs
// export-button.tsx:8 → 'use client' 组件,import 了 file-utils.ts
// ✅ 根因:客户端组件 import 了 server-only 模块的导出
// 即使只用了不涉及 fs 的函数,webpack 也打包整个模块
// ✅ 最小修复:把不涉及 fs 的工具函数拆到 client-safe 模块改动范围:1 个新文件,1 行 import 修改。没有多余的 try-catch,没有临时日志,没有配置修改。
案例二:诡异的 hydration mismatch
问题:开发环境正常,生产环境首屏报 Hydration failed because the server rendered HTML didn't match the client。
猜测式修复:
// ❌ 猜测 1:Date 格式化不一致 → 时区不同,问题依旧
// ❌ 猜测 2:加 useEffect + useState 延迟渲染 → 警告消失但页面闪烁
// ❌ 猜测 3:检查 styled-components 配置 → 完全无关,浪费两小时系统化调试:
// ✅ 复现:incognito 访问生产环境,DevTools 确认 hydration 警告
// ✅ 读错误详情:
// Server: "<time>2026-06-24 08:00:00 UTC</time>"
// Client: "<time>2026-06-25 00:00:00 CST</time>"
// ✅ 根因:组件用了 new Date().toLocaleString()
// 服务端在东八区渲染,客户端在用户本地时区渲染
// ✅ 最小修复:
function ClientTime() {
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
return <time>{mounted ? new Date().toLocaleString() : ''}</time>
}系统化调试从错误信息本身出发,沿着 React 给出的精确线索直接定位到时区差异。修复范围局限在一个组件内。
案例三:AI Agent 工具调用链断裂
问题:AI Agent 执行多步任务,前两步正常,第三步调用 search_database 报错「invalid parameter type」。
猜测式修复:
// ❌ 猜测 1:重写工具描述,强调参数类型 → 偶尔有效,复杂场景仍失败
// ❌ 猜测 2:换更大的模型 → 成本翻倍,30% 情况下仍出现
// ❌ 猜测 3:修改 JSON Schema 增加 enum 约束 → 引入新验证错误系统化调试:
// ✅ 收集执行 trace:
// Step 1 plan_generation:
// Input: "查询用户张三最近 7 天的订单"
// Output: { steps: ["search_database", "format_result"] }
// Step 2 search_database:
// Input: { days: "7" } ← string
// Schema: { days: integer } ← 期望 integer
// Status: ❌ invalid parameter type
// ✅ 根因:plan_generation 输出 days: "7"(字符串)
// 未经类型转换直接传给 search_database
// ✅ 最小修复:在工具调用层增加参数验证和自动类型转换
// 不修改 prompt,不更换模型,不改 Schema2026 年实践研究表明,这种基于证据的 Agent 调试方法在故障定位准确率上比基线方法提高了约 24 个百分点。
猜测式 vs 系统化调试对比
| 维度 | 猜测式调试 | 系统化调试 |
|---|---|---|
| 起点 | 看到报错就猜原因 | 先复现,再读证据 |
| 每次修复 | 可能改多个地方 | 一次只改一个变量 |
| 失败后 | 立刻尝试下一个猜测 | 记录证伪原因,排除假设 |
| 对 AI 的使用 | 「帮我修这个 bug」 | 「帮我分析这个堆栈」 |
| 代码变化 | 越改越乱,留下疤痕 | 改动最小,方向明确 |
| 调试时间 | 初期快,后期指数增长 | 初期慢,后期收敛 |
| 对 LLM 的消耗 | 随轮次增加效果衰减 | 每轮保持独立,不衰减 |
AI 的正确用法:从代码老虎机到工程搭档
Addy Osmani 总结 2026 年 LLM 编程工作流时强调:一次只让 AI 做一件事——实现一个函数、修一个 bug、加一个特性。
不要这样用
// ❌ 把报错直接扔给 AI,让它「帮我修」
> 这里报了个错:Module not found: Can't resolve 'node:fs'
> 帮我修一下。
// AI 给一个猜测性修复,试了不行,再把新错误扔回去……循环开始应该这样用
// ✅ 第一步:让 AI 分析堆栈
> 这是完整构建错误堆栈:[粘贴]
> 请分析 import trace,指出 node:fs 从哪个文件开始被引入到客户端 bundle。
// ✅ 第二步:让 AI 列出假设
> 基于这个 import trace,列出 3-5 个可能根因并按可能性排序。
> 对每个假设说明验证方式。
// ✅ 第三步:逐个验证
> 我验证了假设 1(xxx),结果发现 yyy。这支持还是排除了这个假设?
// ✅ 第四步:确认修复
> 根因是 xxx。请给出最小修复方案:
> 只改必要代码,不加 try-catch,不改配置。当我要求 AI 先证明根因、再给修复方案时,它更像工程搭档,而非代码老虎机。
常见根因分类表
调试时根因通常可以归入以下几类。我在列出假设时会参考这个分类:
| 根因类别 | 典型表现 | 排查方向 |
|---|---|---|
| 模块边界错误 | node:fs 进入客户端 bundle | 检查 import trace,确认模块归属 |
| 序列化/类型不匹配 | API 参数类型错误、JSON 解析失败 | 对比 Schema 定义和实际传入值 |
| 状态不一致 | Hydration mismatch、缓存命中旧数据 | 对比服务端/客户端输入差异 |
| 竞态条件 | 偶发空指针、数据丢失 | 检查异步操作执行顺序 |
| 环境差异 | 开发正常但生产报错 | 对比 NODE_ENV、配置、依赖版本 |
| 边界条件 | 空数组、特殊字符、超大数据量 | 检查输入数据边界情况 |
| 依赖版本冲突 | 构建通过但运行时报错 | 检查 lock 文件和 peer dependencies |
| 权限/鉴权 | 403、CORS 错误、Token 过期 | 检查请求头、CORS 配置、Token 有效期 |
调试检查清单
下次调试前我把这个清单过一遍。有任何一项没打勾,调试就还没结束:
复现阶段
- 能用一条命令稳定复现问题
- 记录了完整错误信息(不只是第一行)
- 确认了问题环境(开发/生产、本地/远程)
- 排除了数据相关性(空数据、特殊字符、大数据量)
证据分析阶段
- 完整阅读了错误堆栈(从报错点到入口)
- 检查了构建日志中的 warning(warning 往往先于 error 出现)
- 查看了浏览器控制台的 hydration、网络、权限错误
- 查看了最近的 git diff 中相关文件的改动
假设验证阶段
- 列出了 3-5 个可能假设并按可能性排序
- 每个假设有明确的验证方式
- 每次验证只改了一个变量
- 被证伪的假设记录了原因,避免重复猜测
修复确认阶段
- 修复针对根因,而非绕过症状
- 重跑了原始失败命令,确认问题消失
- 跑了更高层验证(测试、构建、浏览器验收),确认无新回归
- 检查了相邻模块,确认修复没有破坏其他功能
- 代码中无遗留调试临时修改(console.log、注释代码、临时配置)
当 AI 陷入死循环时:战略性重启
Nature Scientific Reports 的研究还发现:某些模型(如 Claude 3.7 Sonnet)在 2 轮内就能达到 100% 调试成功率,而另一些模型则陷入「低有效性尾部」——反复在同一条死路上做微小调整。
我发现 AI 连续 2-3 轮修复失败时,最有效的策略是战略性重启:
- 清空当前对话上下文:之前的修复尝试已经污染了上下文
- 只带原始问题重新开始:把原始错误信息和未修改的代码作为新对话输入
- 明确要求系统化方法:告诉 AI「先分析根因,不要直接给修复代码」
这种「新鲜开始」策略让调试成功率提升 10-15 个百分点,本质上重置了衰减曲线。
结论
调试时最重要的动作,往往是先慢下来。
猜测式调试利用了 AI 的速度优势——快速生成修复代码、快速尝试不同方案——但忽略了它的认知局限:上下文污染、确认偏误、调试有效性衰减。让 AI 连续试了 3 个补丁还没解决问题时,继续试第 4 个的成功率已经很低了。
系统化调试把 AI 的速度优势用在正确的地方:快速整理日志、快速分析堆栈、快速列出假设和验证步骤。我让 AI 做分析,自己做决策;让 AI 处理信息,自己验证假设。这种分工比「把报错扔给 AI,让它猜」高效得多。
AutoSD 研究的结论值得记住:最有效的 AI 调试是让 AI 按科学方法辅助人类——提出假设、设计实验、收集证据、验证结论。
下次看到报错信息,在把整段错误粘贴给 AI 之前,我会先问自己一个问题:我是在寻找根因,还是在碰运气?
参考资料:
- Zhang et al., "Measuring and mitigating debugging effectiveness decay in code", Nature Scientific Reports, 2025
- Chen et al., "Cognitive Biases in LLM-Assisted Software Development", arXiv:2601.08045, 2026
- Kang et al., "Explainable automated debugging via large language model-driven scientific debugging", Empirical Software Engineering, 2025
- "A Grounded Theory of Debugging in Professional Software Development", arXiv:2602.11435, 2026
- "Rethinking Debugging Strategies for Code LLMs", arXiv:2506.18403, 2025
- "Dual-Process Scaffold Reasoning for Enhancing LLM Code Debugging", arXiv:2511.08052, 2025
- Osmani, A., "My LLM coding workflow going into 2026", Medium, 2025