关于 debugging-code
面向排查崩溃、错误输出和异常执行路径的交互式调试技能。它通过 dap 检查运行中的进程,无需重启即可查看状态。
主要功能
- 01查看实时变量与调用栈
- 02逐行执行与设置断点
- 03条件断点与异常断点
- 04在运行进程中求值表达式
- 05支持提供 DAP 后端的语言
适用场景
- 01追踪错误值的来源
- 02排查位置未知的异常
- 03检查运行中或远程进程
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ almogbaku/ debug-skill --skill debugging-code安装后在 Claude Code 中输入/debugging-code确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/advanced-techniques.mdinstalling-debuggers.mdscripts/install-dap.sh详细介绍
交互式调试器
当程序崩溃、输出错误,或者你需要确切了解执行过程是如何到达某个状态时使用——如果增加更多打印语句再运行一次,也无法足够快地找到答案。
你可以随时暂停正在运行的程序,在那个确切时刻查看实时变量值和调用栈,逐行向前执行或跳转到下一个断点,还可以针对实时进程计算任意表达式——所有这些都无需重启。
设置
此技能使用 dap,这是一个 CLI 工具,它会启动后台守护进程,通过 DAP Protocol 与调试器交互并维护调试器状态,因此你只需通过多次调用与其交互即可。
如果未安装 dap(检查方法:command -v dap),现在就安装。
继续安装前,请先询问用户并通知用户。
通过 Homebrew 安装(macOS)
brew install AlmogBaku/tap/dap
安装脚本:
bash scripts/install-dap.sh
从源代码安装:
go install github.com/AlmogBaku/debug-skill/cmd/dap@latest
此工具是开源的,可在 GitHub 上获取,由维护者按照最佳实践维护。
原生支持 Python、Go、Node.js/TypeScript、Rust、C/C++,以及任何其他支持 DAP 的语言。
如果缺少调试器后端或无法启动,请参阅 references/installing-debuggers.md
查看所有命令和标志:dap --help 或 dap <cmd> --help。
启动会话
dap debug <file> 会在调试器下启动程序。后端会根据文件扩展名自动检测。
根据你掌握的信息选择启动策略:
- 已有假设 — 在你认为存在问题的位置设置断点:
dap debug script.py --break script.py:42 - 条件断点 — 仅在满足条件时暂停:
dap debug script.py --break "script.py:42:x > 5"(带条件的断点说明务必加引号) - 多文件应用 — 在多个模块中设置断点:
--break src/api/routes.py:55 --break src/models/user.py:30 - 没有假设,程序较小 — 从入口开始执行:
dap debug script.py --stop-on-entry(大型项目中请避免这样做——启动代码会产生大量干扰;应改用断点逐步缩小范围) - 异常,位置未知 —
dap debug script.py --break-on-exception raised(Python)/all(Go/JS) - 远程进程 —
dap debug --attach host:port --backend <name> - 进程已在运行(服务器卡住、线上问题) — 无需重启即可附加:
dap debug --pid <PID> --backend <name>macOS + Go 注意事项:
dlv --pid需要禁用 SIP(csrutil disable)。 建议直接在调试器下启动程序,或附加到远程调试器!
会话隔离: --session <name> 可避免并发的 Agent 相互干扰。
提示:如果可以,建议使用你的会话 ID(${CLAUDE_SESSION_ID})。
运行 dap debug --help 查看所有标志、后端和示例。
调试思路
当单独阅读源代码无法验证根本原因时,就该使用调试器了。 调试器可以让你观察实际发生的情况:真实的值、实际的路径和实际的状态。 当它们偏离预期时,你就找到了问题所在。
两次碰壁,就重新思考。 如果两个假设都在同一位置失败,说明你的思维模型有误。 重新阅读代码,构建一个截然不同的理论,并设置不同的断点。
逐步升级。 先使用 dap eval 验证一个简单假设。使用条件断点过滤干扰。只有在需要交互式控制时,才改用完整断点并逐步执行。
模拟用户操作流程。 如果你在调试用户操作流程,就沿着预期的代码执行路径设置断点。
如果你原本以为会调用 compute(),但它始终没有被调用,那么问题就在调用方——而不在 compute(),而在本应调用它的代码。
设置断点,而不是打印。 当你想打印某些内容时,改为设置断点。
了解当前状态
每次执行 dap 命令时,都会自动返回完整上下文:当前位置、源代码、局部变量、调用栈和输出。每次暂停时,问问自己:
- 局部变量的值是否符合预期?
- 调用栈显示的代码路径是否符合预期?
- 到目前为止的输出是否揭示了任何意外情况?
沿调用栈向上追查因果关系。 如果第 0 帧中的值不对,请检查 dap eval "<expr>" --frame 1,看看调用方传入了什么。继续向上查看(--frame 2、--frame 3),直到找到该值首次出错的帧——那就是 Bug 的根源,而不是表象。
暂停时的输出示例:
Stopped at compute() · script.py:41
39: def compute(items):
40: result = None
> 41: return result
Locals: items=[] result=None
Stack: main [script.py:10] → compute [script.py:41]
Output: (none)
如果程序在命中断点前就退出了:
Program terminated · Exit code: 1
→ 将断点设得更早,或使用 --stop-on-entry 重新启动。
构建假设
设置断点前,先想一想:“我认为 Bug 出在 X,因为 Y。” 好的假设可以证伪——下一次观察就能证实或推翻它。还没有假设?设置两个断点进行二分,缩小搜索范围,或参见上面的起步策略。
策略性地设置断点
- 设置在问题开始的地方,而不是问题显现的地方
- 第 80 行出现异常?根因在更前面——从更早的位置开始
- 不确定?进行二分:
--break f:20 --break f:60——检查两段中间的状态是否出错,缩小搜索范围
断点设置位置:
- 边界——数据跨越格式、表示形式或模块边界的位置;此处的状态最干净
- 状态转换——赋值或修改损坏值的那一行
- 错误分支——输入导致错误路径的条件
- 反模式——不要在库代码内部设置断点;应在调用处设置。不要在紧密循环中使用无条件断点——请使用条件断点。
在会话中管理断点
随着了解加深,可在可疑代码中更深的位置添加断点,并移除已经完成任务的断点——逐步缩小范围,无需重新启动:
dap continue --break app.py:50 # add breakpoint deeper, then continue
dap continue --remove-break app.py:20 # drop a breakpoint you're done with
dap break add app.py:42 app.py:60 # add multiple breakpoints at once
dap break list # see what's set
dap break clear # start fresh
如果断点位于无效行,或适配器调整了断点位置,dap 会在输出中发出警告。
条件断点
仅在条件为真时暂停——这对循环、热点路径和特定输入值至关重要。
语法:"file:line:condition"(务必加引号)。
dap debug app.py --break "app.py:42:i == 100" # skip 99 iterations, stop on the one that matters
dap debug app.py --break "app.py:30:user_id == 123" # reproduce a user-specific bug
dap continue --break "app.py:50:len(items) == 0" # catch the empty-list case mid-session
不变量断点
将条件断点用作运行时断言——一旦出错就立即暂停:
dap debug app.py --break "bank.py:68:balance < 0" # catch the overdraft
dap debug app.py --break "pipe.py:30:type(val) != int" # type violation
跟踪执行过程
每次暂停时,根据你的怀疑选择如何继续:
如果你连续单步执行超过 3 次,就应该设置断点,而不是继续单步。
dap step # step over — trust this call, advance to next line
dap step in # step into — suspect what's inside this function
dap step out # step out — you're in the wrong place, return to caller
dap continue # jump to next breakpoint
dap continue --to file:line # run to line (temp breakpoint, auto-removed)
dap context # re-inspect current state without stepping
dap output # drain buffered stdout/stderr without full context
dap inspect <var> --depth N # expand nested/complex objects
dap pause # interrupt a running/hanging program
dap restart # restart with same args and breakpoints
dap threads # list all threads
dap thread <id> # switch thread context
每次暂停都会显示当前的 file:line,让你随时知道自己所处的位置。
使用 dap eval "<expr>" 检查实时状态,无需单步执行:
dap eval "len(items)"
dap eval "user.profile.settings"
dap eval "expected == actual" # test hypothesis on live state
dap eval "self.config" --frame 1 # frame 1 = caller (may be a different file)
避免使用会调用有副作用方法的 eval 表达式——它们会修改程序状态,可能破坏调试会话。除非你有意测试修复方案,否则只进行只读访问。
跳转到后续位置
如果你想快速查看特定行,又不想设置永久断点,请使用 dap continue --to file:line。这是一次性的断点——只暂停一次,随后便消失。适用于“我只想看看第 50 行的 x 是什么样”,无需管理断点的整个生命周期。
高级场景
有关高级场景——卡住、并发 Bug、深层嵌套状态、循环二分——
请参阅 ${CLAUDE_SKILL_DIR}/references/advanced-techniques.md。
演练
Bug:compute() 返回 None
Hypothesis: result not assigned before return
→ dap debug script.py --break script.py:41
Locals: result=None, items=[] ← wrong, and input is also empty
New hypothesis: caller passing empty list
→ dap eval "items" --frame 1 → [] ← confirmed
→ dap step out → caller at line 10, no guard for empty input
→ dap continue --break script.py:8 --remove-break script.py:41
← narrowing: add breakpoint at data source, drop the one we're done with
Stopped at main():8, items loaded from config as []
Root cause: missing guard. Fix → dap stop.
没有假设(出现异常,位置未知):
Exception: TypeError, location unknown
→ dap debug script.py --break-on-exception raised
Stopped at compute():41, items=None
Root cause: None passed where list expected.
验证修复
在 Bug 处暂停时,使用 eval 将你提出的修复表达式应用于实时状态进行测试。如果它在 eval 中有效,在代码中也会有效。然后编辑代码并执行 dap restart,端到端验证修复。
应用修复后,重新运行相同场景进行验证。dap restart 会使用相同的参数和断点重新运行——形成快速反馈循环。在你于发现 Bug 时的同一断点处观察到正确行为之前,不要相信修复已生效。
清理
dap 会话通常会在程序退出或空闲超时后自动终止。
如果应用未正常关闭(例如,你在调试时强制终止了它),可以手动终止会话:dap stop。