claude-debugging-skill — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited claude-debugging-skill (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
这份规则描述的是在面对 bug、错误信息、异常行为时的完整推理过程。调试是所有编程工作中最考验系统性思维的环节。缺乏方法论的调试是反复猜测和随机修改,有方法论的调试是假设驱动的系统性排查。两者在效率上的差距在复杂问题上会放大到数倍乃至数十倍。这份文档的价值在于把有经验的工程师在调试时隐性运用的方法论显式化。
每个 bug 都有症状(可以观察到的表现)和根因(真正导致问题的原因)。修复症状而不修复根因,问题会以另一种形式再次出现。调试的目标是找到根因,而不仅仅是消除症状。
症状和根因之间可能隔着一条很长的因果链。错误出现在 A 模块,但根因在 B 模块,因为 B 向 A 传递了错误的数据,而 B 之所以产生错误数据是因为 C 模块的配置不正确。只修改 A 不会真正解决问题。
flowchart LR
A[症状\n可观察到的错误表现] --> B[直接原因\n直接导致症状的代码]
B --> C[间接原因\n导致直接原因的上游问题]
C --> D[根因\n问题链的起点]
D -- 修复这里才能真正解决问题 --> E[彻底修复]
A -- 只修复这里会再次出现 --> F[临时缓解]有效的调试不是随机修改代码直到问题消失,而是基于已有信息形成关于根因的假设,然后设计最小的验证实验来验证或否定这个假设,根据结果更新假设,直到找到根因。
这个方法的核心是:每次调试行动都有明确的目的(验证某个特定的假设),而不是漫无目的地尝试。如果行动没有验证任何假设,它就没有推进调试进展。
当问题的位置不明确时,使用二分法快速缩小范围:找到问题存在的最大范围,在中间设置一个检查点,根据检查点的结果判断问题在前半段还是后半段,然后在确定有问题的那一半中继续二分,直到定位到具体的问题位置。
最常见的调试效率问题是没有完整读取错误信息就开始猜测原因。错误信息通常包含三个部分:错误类型(告诉你发生了什么类型的问题)、错误消息(告诉你具体发生了什么)、调用栈(告诉你问题发生在哪里、通过什么调用路径到达的)。
这三个部分都需要仔细阅读。错误消息中经常包含直接指向根因的线索,但在调试者焦虑的状态下容易被忽略。
调用栈从下到上是调用的时间顺序(最底层是最先执行的)。调试时关注两个位置:调用栈的最顶层(问题直接发生的位置)和调用栈中自己的代码与第三方库代码的边界(通常这个边界附近是最值得检查的地方,因为自己的代码向第三方库传递了错误的输入,或者对第三方库的返回值处理有问题)。
不同类型的错误消息需要不同的处理思路:
类型错误和空指针:通常意味着某个值在某个时刻是 null 或者不是期望的类型,需要追踪这个值是从哪里来的,是在哪个步骤变成了错误的类型或空值。
权限和认证错误:需要检查当前操作所需的权限是否已经正确获取,以及权限令牌是否已经过期或格式不正确。
网络和超时错误:需要区分是服务不可用还是客户端配置有问题,需要检查网络连通性、端点地址、超时设置。
数据库错误:通常包含 SQL 信息,需要检查查询语句的语法、约束条件冲突、连接配置。
flowchart TD
A[看到错误信息] --> B[完整读取错误类型、消息和调用栈]
B --> C[识别错误类型]
C --> D{错误类型分类}
D -- 类型或空值错误 --> E[追踪值的来源\n找到它变为错误状态的位置]
D -- 权限认证错误 --> F[检查权限令牌\n验证访问控制配置]
D -- 网络超时错误 --> G[检查网络连通性\n端点配置和超时设置]
D -- 数据库错误 --> H[检查 SQL\n约束条件和连接配置]
D -- 未知错误 --> I[搜索错误消息\n查找相关案例和文档]
E & F & G & H & I --> J[形成初始假设]假设的质量直接决定调试的效率。高质量的假设来自于:错误信息本身提供的线索、最近的代码变更(如果问题是新出现的)、问题出现的规律(只在特定条件下出现意味着什么)、系统的已知行为和边界情况。
形成假设时,从最简单、最可能的原因开始,而不是直接跳到复杂的原因。"某个变量在边界情况下是 null" 比 "底层框架有 bug" 更可能是真正的原因。
验证假设需要设计实验,实验的原则是最小化:只改变最少的东西来验证假设,不要同时做多个修改,因为同时做多个修改无法确定哪个修改起了作用(或者产生了新问题)。
好的验证实验:增加一行日志来打印某个变量的值,在特定条件下打断点观察程序状态,将一个复杂的输入简化到最小的能复现问题的输入。
当实验结果否定了假设时,不要继续在被否定的方向上投入时间,而是根据新的信息更新假设,转向新的可能方向。
被否定的假设也是有价值的信息,它排除了一个可能性,缩小了根因的范围。记录已经被排除的可能性,防止在调试过程中因为遗忘而重复检查同一个方向。
flowchart TD
A[收集错误信息和上下文] --> B[形成关于根因的假设]
B --> C[设计最小化验证实验]
C --> D[执行实验]
D --> E{实验结果}
E -- 支持假设 --> F[深入验证\n确认假设是根因而非巧合]
E -- 否定假设 --> G[记录已排除的方向]
F --> H{是否是真正的根因}
H -- 是 --> I[实施修复]
H -- 不完全是,还有更深的原因 --> B
G --> J[根据新信息更新假设]
J --> B无法稳定复现的问题很难调试,因为无法确认修复是否真的有效。在开始排查之前,先确认:能否稳定复现问题,复现的条件是什么,问题是偶发的还是必现的。
对于偶发问题,需要分析它在什么条件下更容易出现:特定的数据输入、特定的并发场景、特定的系统资源状态、特定的时间点。这些条件往往是指向根因的重要线索。
当问题涉及多个组件或多个变量时,需要系统性地隔离变量:每次只改变一个变量,观察结果,然后再改变下一个变量。
常用的隔离方法:将复杂的输入替换为简单的已知输入,看问题是否还存在(排除输入问题);将外部依赖替换为模拟实现,看问题是否还存在(排除外部依赖问题);在不同的环境中运行,看问题是否环境相关。
如果问题是突然出现的(之前没有这个问题),最有效的调试方向是找到在问题出现前后发生了什么变化:代码变更、依赖版本变更、配置变更、数据变更、环境变更。
大多数突发问题都可以通过找到对应的变更来快速定位根因,因为问题和变更之间通常有直接的因果关系。
在确认了根因之后,修复应该针对根因,而不是绕过症状。绕过症状的修复(在错误发生的地方加 try-catch 吞掉异常,在返回值为 null 的地方加 null 检查但不弄清楚为什么是 null)是技术债务的来源。
在实施修复之前,分析修复可能带来的影响:这个修复是否会影响其他功能,是否会改变某些现有的行为(即使这个改变是预期的),是否需要同步修改测试用例。
修复之后,不仅要验证原来的问题不再复现,还要验证修复没有引入新的问题:运行相关的测试,检查修复影响到的其他功能是否仍然正常工作。
flowchart TD
A[确认根因] --> B[设计针对根因的修复方案]
B --> C[分析修复的影响范围]
C --> D{影响范围是否可控}
D -- 否 --> E[重新设计修复方案\n缩小影响范围]
E --> C
D -- 是 --> F[实施修复]
F --> G[验证原问题不再复现]
G --> H[验证相关功能未受影响]
H --> I{验证是否全部通过}
I -- 否 --> J[分析新出现的问题]
J --> A
I -- 是 --> K[修复完成]
K --> L[回顾:这类问题如何预防]在修复完成后,思考这类问题如何在未来被预防:是否应该添加测试来覆盖这个边界情况,是否应该改进错误消息使同类问题更容易诊断,是否应该在代码中添加断言来尽早发现违反预期的状态,是否有类似的代码路径存在同样的问题需要一并修复。
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.