用 Agent 调试
系列入口:如何使用 Agent。动手改代码前请先建立 Git 快照。
一、Agent 在调试里到底强在哪
调试包含两类工作:一类是机械而繁重的——读大量代码、追调用链、搜日志、跑复现;另一类是需要判断的——从证据中推断根因。Agent 在前一类上几乎是完美助手,速度快、不知疲倦,能帮你把范围迅速收窄;在后一类上它能给出有价值的候选假设,但最终的因果判断和改不改、怎么改,仍需要你把关。
用得好,Agent 让你从「在海量代码里大海捞针」中解放,把精力集中在推理上。用得不好,它会变成「猜测式乱改机器」——这里改改、那里试试,碰巧不报错了就声称修好,埋下更深的雷。差别在于你是否带它走一条严谨的调试闭环。
二、第一步:先稳定复现
在谈论修复之前,必须能让问题可靠地出现。一个无法稳定复现的 bug,你既无法确认它的触发条件,也无法判断改动是否真的生效。
让 Agent 协助你把复现步骤固化下来:明确输入、环境、操作顺序,以及实际结果与预期结果的差别。最好能形成一条可重复执行的命令或脚本,这样后面每次验证都能一键确认。很多调试之所以反复,就是因为复现本身不稳定——这次出现、下次不出现,于是你根本分不清是改动起了作用,还是问题时有时无。
这一阶段还要警惕「环境相关」的假象:问题可能只在特定数据、特定配置、特定顺序下出现。和 Agent 一起把这些边界条件尽量摸清楚,复现越精确,定位越有的放矢。
三、第二步:收集证据、缩小范围
有了稳定复现,不要急着猜原因,先收集证据。让 Agent 沿调用链阅读相关代码、查看日志和报错栈、追踪数据从输入到出错点的流转。
关键的纪律是让结论建立在证据上。要求 Agent 说清「它看到了什么、据此怀疑哪里」,而不是直接抛出一个无根无据的结论。你可以引导它从报错点向前后逐段核对:这一步收到的输入对不对?哪一环开始出现异常?像多 Agent 排错那样,常常能锁定到「输入是对的、输出却错了」的那一段,嫌疑范围随之大幅收窄。
这一步要特别区分症状与根因。报错信息指向的位置往往只是症状爆发处,真正的原因可能在更上游——一个被污染的数据、一个错误的前提假设。只盯着症状修,很容易按下葫芦浮起瓢。
四、第三步:提出可检验的假设
当范围收窄,提出对根因的假设。好的假设不是「可能是这里有问题」这种模糊说法,而是一个可以被实验证实或证伪的具体命题:「当入参 X 为空时,函数在第 N 行未做空值判断,导致返回了错误结果」。
可以让 Agent 多给几个候选假设,并对每个假设说明它依赖什么证据、如何区分它们。但不要被它的自信口吻带偏——假设再多,没经验证都只是猜想。你要做的是和它一起挑出最可能、最容易先验证的那一个,而不是同时动手改好几处。
五、第四步:用最小实验验证,而不是「改到好」
这是整个闭环里最容易崩、也最关键的一步。
反例是「改到好为止」: Agent 或人在这里改一行、那里加个判断,跑一下发现报错没了,就宣布修好。问题在于你根本不知道是哪一处改动起了作用、也不知道是否只是掩盖了症状;同时改多处还可能引入新问题。这种「碰巧不报错」极不可靠,bug 常常换个形式复发。
正解是一次只验证一个假设。 针对当前假设做最小改动或最小观察(比如先加日志确认数据状态,再决定怎么改),确认问题因此出现或消失,从而证实根因。如果实验否定了假设,就回到证据、修正假设,而不是靠堆改动碰运气。当根因被证实,再实施真正的修复,你会清楚知道「为什么这样修、它如何解决根因」,而不是模糊地「现在好像能用了」。
六、第五步:补回归测试,闭环才算结束
修复让当前问题消失,但如果没有一道防线,同样的 bug 很可能在以后重构、或他人改动时悄悄回来。
让这个稳定复现的场景变成一个测试: 它在修复前应当失败(能抓住 bug),在修复后通过。这样它从一次性的复现脚本,升级成长期守护的回归用例。这一步把「这次修好」转化为「以后不会再坏」,是调试闭环真正的收尾。理想情况下,你甚至可以在修复前先写好这个失败测试,再改代码看它转绿——测试先行让「修没修对」变得毫不含糊。
七、把 Agent 用好的几条边界
- 要依据,不要断言:让它对每个结论给出对应证据(代码位置、日志、数据)。
- 一次一假设:拒绝同时改多处的「碰运气式」修复。
- 不可逆/大范围改动先确认:配合 Git 检查点,确保随时可退回。
- 人做最终因果判断:Agent 擅长收集和给候选,但根因确认和修复方案由你拍板。
七点五、「改到好为止」为什么是调试大忌
这条值得用更完整的篇幅讲透,因为它是日常最常见、后果最隐蔽的反模式。
设想一个典型场景:页面上某个数字偶尔算错,你让 Agent 修。它快速扫了几眼,说「可能是浮点精度问题」,加了个四舍五入;跑一下,这次对了,但没几天另一个入口又错。于是它再加一层判断、再补一个特判……表面上「现在能用了」,可代码里堆叠了一堆补丁,谁也说不清最初的根因是什么。真正的根因——也许是上游某个数据在特定情况下重复累加——从来没被触及,只是被一层层「看起来有效」的改动暂时盖住。
这种做法的根本问题是放弃了因果,转而追求相关性:「我改了这里,现象消失了,所以是这里的问题」。但现象可能因测试数据、执行顺序而暂时消失,并不代表根因被解决。同时修改多处时,你更无法判断究竟是哪处起了作用、哪些是多余甚至有害的。科学调试的态度恰好相反:先有一个明确假设,再用一次只动一处的实验去证实或证伪它,根据结果决定下一步,哪怕这条路看起来更慢。
一个可操作的克制方法是:每当想「再顺手改一处试试」,先要求把当前假设写下来、说明预期结果;改完严格对照。如果改完现象没变,说明假设被证伪,应回退这处改动、回到证据,而不是把它留下继续叠。让「每处改动都对应一个被检验的假设」成为纪律,你就不会滑向补丁堆积的泥潭。
七点六、根因往往不在报错的那一行
新手调试的注意力天然被报错信息和栈顶吸引,但那里多数时候只是「症状爆发点」,真正的根因藏在更上游。
举例:一个函数报空指针,栈顶明确指向第 100 行。最直接的反应是在第 100 行加一个非空判断——报错确实消失了。但如果继续追问「为什么这里会收到空值」,你可能发现是更早的某个调用方违反了约定、或某段数据解析在出错时静默返回了空。只在第 100 行兜底,等于把一个「数据契约被破坏」的问题,降级成「在这里别崩就行」,同样的空值还可能在别处引发别的故障。
顺着「为什么」往上游多追几层,是从「修症状」走向「修根因」的关键。你可以让 Agent 不要停在报错点,而沿数据和调用反向追踪,直到找到那个「最早开始偏离正确状态」的位置。判断是否到底了,有个实用标准:当你能解释清楚「这个错误前提是如何一步步产生、最终在此爆发」,并且修复那个源头能让整条链恢复正常,才算触及根因。这个追问的习惯,正是 Agent 帮你扩大搜索广度时,你最该主导的推理方向。
七点七、偶发性 bug:把「时有时无」逼成「必然出现」
最棘手的一类是偶发问题:它不稳定复现,于是任何「修好了」都无法确认,可能只是这段时间没触发。
应对的思路不是急着改,而是先收窄变量、增加观测,把偶发逼成可复现。让 Agent 协助梳理:问题是否依赖特定顺序(并发/竞态)、特定时间、特定数据状态、特定缓存?在可疑环节加日志或断言,记录每次的关键输入和状态,让下一次出现时留下证据。对于并发问题,尤其要关注「两个操作的先后」这一类时序假设——它们最容易表现为偶发,也最难靠多跑几次确认。
一个心态上的提醒:偶发问题在没抓到规律前,不要轻易提交修复并宣称解决。更稳妥的是先让观测手段运行一段时间、积累足够样本,待触发条件清晰后再按标准的假设—验证流程处理。把「复现」这第一步做扎实,对偶发 bug 来说往往就解决了一半的困难。
七点八、日志该怎么加:太多和太少都坏事
调试离不开观测,但加日志本身有讲究,加得不对反而干扰判断。
太少的问题明显:信息不足,问题出现时无据可查,只能再复现一次。太多则是更隐蔽的麻烦:关键事件被淹没在海量噪声里,你想找的那一行被刷过去;高频日志还可能拖慢系统、甚至本身改变时序,让偶发问题更难抓。健康的做法是在关键节点记录关键变量与决策:输入、重要分支的选择、出错前的状态、以及能标识一次请求的关联信息,让你能把一次完整链路串起来。
加日志时还要想清楚「我要回答什么问题」——是想确认数据在哪一步变错,还是确认两个操作的先后顺序。围绕问题去埋点,而不是无差别打印一切。调试结束后,把临时噪声日志清理掉,保留真正有长期价值的。好的日志让证据主动浮现,你和 Agent 就不必靠猜测填补信息空白。
七点九、和 Agent 一起调试时,你最该亲自做什么
Agent 承担了大量搜集工作,但有几件事你不应外包,这决定了调试的质量。
第一是确认问题本身被准确界定:实际与预期的差异到底是什么,别在问题都没说清时就让它四处改。第二是判断哪个假设值得追、以及何时算证实了根因——Agent 能给一堆候选和很自信的结论,但因果确认是你的责任,尤其要防止它把症状当根因。第三是拍板修复方案,确保修复针对根因、不会引入新风险,而不是只要「现在不报错」。
把分工想得很清楚:Agent 扩大你能看到的代码和证据广度、加速机械验证;你主导推理方向、价值判断和最终扳机。这样的协作既榨干了 Agent 的效率优势,又守住了只有人能承担的判断质量,调试才既快又稳。
七点十、一份可以照着走的调试对话模板
最后给你一套可直接套用的对话结构,让你每次带 Agent 调试都走在严谨闭环上,而不是临场想到哪说到哪。
开场先界定问题:告诉它预期行为、实际行为、复现步骤和已掌握的线索,并要求它「先不要改代码,先阅读并报告」。接着要证据:让它沿调用链和数据流梳理,指出哪一环开始异常,并对每个判断附上代码位置或日志依据。然后收敛假设:请它给出两三个可检验的根因假设、各自如何区分,你和它一起选一个先验证。再做最小实验:一次只动一处,确认问题随假设出现或消失;被证伪就回退、重来。最后固化:实施修复并把复现场景写成回归测试。
这套模板的价值在于把「要依据、一次一假设、根因优先、回归收尾」这些纪律,固化成固定的说话顺序。你也可以把它做成一个斜杠命令,遇到 bug 一键进入这套流程。好的调试协作,往往始于一段结构正确的开场。
八、常见问题
Q:Agent 一口咬定根因,我要信吗? 把它当作待验证假设,而不是结论。要求它指出证据位置,并用最小实验验证;越自信、越要核实,因为错误结论也会被说得很肯定。
Q:找不到可复现路径怎么办? 先加日志、缩小输入和环境差异,把「偶发」逐步逼成「可复现」;实在偶发的,要更关注其依赖的时序、状态或并发条件。
Q:改完不报错了,算不算修好? 不一定。要确认是针对已证实根因的修复、且回归测试通过;「不报错」可能只是症状被掩盖。
Q:该让 Agent 直接改,还是先讨论? 复杂或高风险问题,先让它呈现证据与假设、和你对齐,再动手;简单明确的小问题可直接修,但同样要验证和回归。
九、调试检查清单
- 已能用固定步骤稳定复现,并记录实际与预期差异。
- 基于日志、调用链和数据收集了证据,范围已收窄。
- 区分了症状与根因,提出了可检验的具体假设。
- 一次只验证一个假设,拒绝「改到好为止」。
- 修复针对已证实的根因,且能解释为什么有效。
- 已补一个修复前失败、修复后通过的回归测试。
总结
用 Agent 调试要走严谨闭环:先稳定复现、再靠证据定位并区分症状与根因、提出可检验的假设、用一次只动一处的最小实验验证根因,最后把复现场景固化成回归测试;始终要求 Agent 给依据而非断言、拒绝「改到不报错为止」的碰运气,人掌握最终因果判断,才能让 Agent 的高速真正转化为可靠的修复。