和 AI 一起调试时,先停止猜测

0阅读9分钟

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-catchdynamic importexternals——就像一个见过大量 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 上的扎根理论研究通过观察专业开发者的调试行为,提炼出四个阶段:

  1. 复现:让问题稳定出现
  2. 心智模型构建:理解程序的预期行为与实际行为
  3. 假设生成与验证:提出根因假设并设计实验验证
  4. 修复与确认:实施最小修复并确认问题消失

关键洞察:修复是最后一步。修复之前需要完成复现、理解和验证三个阶段的积累。

AutoSD 研究(Kang et al., 2023/2025)将科学方法形式化:让 LLM 先提假设,再用调试器做实验,收集证据验证或反驳。「假设-实验-证据」的循环比直接让 LLM 生成修复代码的成功率高出显著幅度。

系统化调试的完整流程

流程图画布 · 115%
Mermaid 流程图加载中...

两条核心约束:一次只改一个变量每次验证有明确的通过/失败标准。这两条规则直接对抗变量爆炸和模糊判断。

三个真实案例

案例一: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,不更换模型,不改 Schema

2026 年实践研究表明,这种基于证据的 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 轮修复失败时,最有效的策略是战略性重启

  1. 清空当前对话上下文:之前的修复尝试已经污染了上下文
  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

评论 0

0 / 1000