关于 debugging-patterns
面向开发者的系统化调试技能,要求先调查根因再提出修复。它将工作分为调查、模式分析、假设测试和实施,并指导追踪调用链与核验证据。
主要功能
- 01修复前先调查根因
- 02复现并隔离故障
- 03使用 LSP 工具追踪调用链
- 04记录可验证假设与证据
- 05实施并验证最小修复
- 06通过手册处理常见故障
适用场景
- 01调查测试或构建失败
- 02追踪运行时崩溃的根源
- 03诊断浏览器与集成错误
- 04分析间歇性故障和回归问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ romiluz13/ cc10x --skill debugging-patterns安装后在 Claude Code 中输入/debugging-patterns确认已加载。
附带文件references/investigation-hygiene.mdreferences/root-cause-playbooks.md
详细介绍
系统化调试
概述
随机修复会浪费时间,还会引入新错误。快速打补丁会掩盖根本问题。
核心原则: 尝试修复前,务必先找出根本原因。只修复症状必然失败。
此技能在 v10 中仅提供指导,可深入调查过程。它不授权仅在本地打补丁、凭空猜测,或抱着“修复崩溃的那一行”的想法行事。
参考文件
只阅读当前调查所需的参考资料:
references/root-cause-playbooks.md:适用于构建/类型错误、不稳定测试、运行时崩溃、浏览器错误、git bisect 和边界追踪references/investigation-hygiene.md:适用于上下文管理、证据记录、假设追踪、重启流程和架构升级处理
铁律
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
如果尚未完成阶段 1,就不能提出修复方案。
五步快速流程(参考模式)
快速调试时,使用以下简洁流程:
1. Capture error message and stack trace
2. Identify reproduction steps
3. Isolate the failure location
4. Implement minimal general fix
5. Verify solution works
调试技巧:
- 分析错误消息和日志
- 检查近期代码变更
- 提出并验证假设
- 添加有针对性的调试日志
- 检查变量状态
根本原因追踪技巧:
1. Observe symptom - Where does error manifest?
2. Find immediate cause - Which code produces the error?
3. Ask "What called this?" - Map call chain upward
4. Keep tracing up - Follow invalid data backward
5. Find original trigger - Where did problem actually start?
切勿只在错误出现的位置修复——要追溯到最初的触发因素。 找出根本原因后,检查附近是否存在相同特征的问题,再宣布修复成功。
使用 LSP 追踪根本原因
使用 LSP 系统化追踪执行流程:
| 调试需求 | LSP 工具 | 用法 |
|---|---|---|
| “此函数定义在哪里?” | lspGotoDefinition | 跳转到源代码 |
| “哪些地方调用了此函数?” | lspCallHierarchy(incoming) | 向上追踪调用方 |
| “此函数调用了什么?” | lspCallHierarchy(outgoing) | 向下追踪被调用方 |
| “此变量的所有用法在哪里?” | lspFindReferences | 查找所有访问位置 |
系统化调用链追踪:
1. localSearchCode("errorFunction") → get file + lineHint
2. lspGotoDefinition(lineHint=N) → see implementation
3. lspCallHierarchy(incoming, lineHint=N) → who calls this?
4. For each caller: lspCallHierarchy(incoming) → trace up
5. Continue until you find the root cause
重要: 务必先从 localSearchCode 获取 lineHint。切勿猜测行号。
针对每个问题提供:
- 根本原因说明
- 支持诊断结论的证据
- 具体代码修复
- 测试方法
- 预防建议
场景操作指南
当故障符合以下情形之一时,请阅读 references/root-cause-playbooks.md:
- 构建或类型错误
- 测试失败
- 运行时崩溃
- 浏览器或控制台错误
- 间歇性异步错误
- “以前能用”的回归问题
- 多组件交接失败
请让此 SKILL.md 聚焦于四阶段调查工作流。具体命令、边界追踪模式和 git-bisect 操作指南请参阅参考文件。
适用场景
任何技术问题都应使用:
- 测试失败
- 生产环境中的错误
- 意外行为
- 性能问题
- 构建失败
- 集成问题
尤其适用于以下情况:
- 时间紧迫时(紧急情况容易让人想要猜测)
- “只要快速修一下”看起来显而易见时
- 已经尝试过多种修复方法时
- 之前的修复无效时
- 尚未完全理解问题时
不要在以下情况下跳过:
- 问题看起来很简单(简单错误也有根本原因)
- 你很赶时间(匆忙行事必然导致返工)
- 经理要求立即修复(系统化处理比反复折腾更快)
四个阶段
必须完成每个阶段后,才能进入下一个阶段。
阶段 1:根因调查
尝试任何修复之前:
-
仔细阅读错误消息
- 不要跳过错误或警告
- 它们通常会给出确切的解决方案
- 完整阅读堆栈跟踪
- 记下行号、文件路径和错误代码
-
稳定复现
- 能否可靠地触发问题?
- 具体步骤是什么?
- 每次都会发生吗?
- 如果无法复现 → 收集更多数据,不要猜测
-
检查近期更改
- 哪些更改可能导致此问题?
- Git 差异、近期提交
- 新依赖项、配置更改
- 环境差异
-
在多组件系统中收集证据
当系统包含多个组件时(CI → 构建 → 签名、API → 服务 → 数据库):
提出修复方案之前,先添加诊断检测:
For EACH component boundary: - Log what data enters component - Log what data exits component - Verify environment/config propagation - Check state at each layer Run once to gather evidence showing WHERE it breaks THEN analyze evidence to identify failing component THEN investigate that specific component有关具体的边界追踪方法,请阅读
references/root-cause-playbooks.md。 -
追踪数据流
当错误发生在调用堆栈深处时:
- 错误值从何处产生?
- 哪段代码以错误值调用了这里?
- 继续向上追踪,直到找到源头
- 从源头修复,而不是处理表象
-
追踪配置到其使用方(第三方 SDK、环境变量、导入时机)
提出替换方案、迁移方案或大型重构之前:
- 选项零: 能否仅通过更改配置或环境来修复?提出代码改写方案之前,先陈述并测试这一假设。许多集成问题是连接配置问题,而非架构问题。
- 如果应用配置中验证了某个环境变量,却从未将其传递给库的构造函数,请问:谁在读取它? 第三方 SDK 通常会在模块导入/加载时读取
process.env(或对应语言中的等效项),而不是通过应用的 DI 或配置对象读取。解决方法可能是设置或更正环境变量,而非更改应用代码。 - 在建议替换第三方 SDK 之前,先花些时间了解其配置方式:查看软件包 README、官方文档,或
node_modules(或供应商目录)中的已安装源码。问题可能出在端点或标志设置错误,而非需要更换库。 - 如果代码注释描述了导入时行为或环境变量依赖,请将其视为调查线索,而不是装饰——沿着线索找到真正的使用方。
阶段 2:模式分析
先找出模式,再修复:
-
寻找可用示例
- 在同一代码库中查找类似的可用代码
- 与出问题的代码类似的部分中,哪些能正常工作?
-
对照参考实现
- 如果要实现某种模式,请完整阅读参考实现
- 不要略读——逐行阅读
- 应用之前充分理解该模式
-
找出差异
- 可用代码与出问题的代码之间有哪些不同?
- 列出所有差异,无论多么细微
- 不要想当然地认为“这不可能有影响”
-
理解依赖项
- 此功能需要哪些其他组件?
- 需要哪些设置、配置和环境?
- 它依赖哪些假设?
阶段 3:提出假设并进行测试
遵循科学方法:
-
提出单一假设
- 清楚地陈述:“我认为 X 是根因,因为 Y”
- 把它写下来
- 具体明确,不要含糊
-
进行最小化测试
- 做出尽可能小的更改来测试假设
- 一次只更改一个变量
- 不要一次修复多个问题
-
验证后再继续
- 是否有效?是 → 阶段 4
- 无效?提出新的假设
- 不要在此基础上继续叠加修复
-
遇到不确定时
- 直说“我不明白 X”
- 不要假装明白
- 寻求帮助
- 进一步调查
假设质量标准
**可证伪性要求:**好的假设可以被证伪。如果你无法设计实验来推翻它,它就没有用。
不好的(不可证伪):
- “状态出了问题”
- “时机不对”
- “某处存在竞争条件”
好的(可证伪):
- “路由变化时组件重新挂载,导致用户状态重置”
- “API 调用在卸载后完成,导致更新已卸载组件的状态”
- “两个异步操作在未加锁的情况下修改同一个数组,导致数据丢失”
**区别:**具体程度。好的假设会提出具体、可测试的主张。
假设置信度评分
跟踪多个假设及其置信度:
H1: [hypothesis] — Confidence: [0-100]
Evidence for: [what supports this]
Evidence against: [what contradicts this]
Next test: [what would raise or lower confidence]
H2: [hypothesis] — Confidence: [0-100]
Evidence for: [...]
Evidence against: [...]
Next test: [...]
H3: [hypothesis] — Confidence: [0-100]
Evidence for: [...]
Evidence against: [...]
Next test: [...]
评分指南:
| 范围 | 含义 | 行动 |
|---|---|---|
| 80-100 | 证据充分,确定性高 | 继续修复 |
| 50-79 | 间接证据,需要更多数据 | 运行“下一项测试” |
| 0-49 | 推测,证据薄弱 | 降低优先级或舍弃 |
规则:
- 始终保留 2-3 个假设,直到其中一个达到 80+
- 每有一条新证据,都要更新置信度
- 最高置信度的假设低于 50 时,切勿开始修复
调试中的认知偏差
| 偏差 | 陷阱 | 解法 |
|---|---|---|
| 确认偏误 | 只寻找支持自己假设的证据 | “什么证据能证明我是错的?” |
| 锚定偏误 | 第一个解释成了你的锚点 | 开始调查前先提出 3+ 个假设 |
| 可得性偏差 | 最近遇到过类似 Bug → 假定原因相同 | 除非证据表明并非如此,否则将每个 Bug 都视为新问题 |
| 沉没成本 | 在一条思路上花了 2 小时,即使有相反证据仍继续 | 每 30 分钟问自己:“如果我现在才开始,还会走这条路吗?” |
元调试:调试自己的代码
调试自己编写的代码时,你是在与自己的心智模型较量。
这为何更难:
- 设计决策是你做的——你会觉得它们显然正确
- 你记得的是意图,而不是实际实现
- 熟悉会让你对 Bug 视而不见
应遵循的原则:
- 把自己的代码当作陌生代码——像阅读别人写的代码一样阅读它
- 质疑自己的设计决策——你的实现选择是假设,不是事实
- 承认自己的心智模型可能有误——代码的行为才是事实;你的模型只是猜测
- 优先检查自己动过的代码——如果你修改了 100 行代码后出现问题,这些代码就是重点嫌疑对象
最难承认的话:“这是我实现错了。”而不是“需求不明确”——犯错的是你。
何时重新开始调查
遇到以下情况时,考虑从头开始:
- 2+ 小时毫无进展——你很可能陷入了思维定式
- 3+ 次“修复”都不起作用——你的心智模型有误
- 无法解释当前行为——不要在困惑之上继续添加改动
- 你在调试调试器——某些根本环节出了问题
- 修复奏效了,但你不知道原因——这不叫修复,只是碰巧
重新开始的步骤:
- 关闭所有文件和终端
- 写下你确定知道的事情
- 写下你已排除的可能性
- 列出新的假设(与之前不同)
- 从阶段 1 重新开始
阶段 4:实施
修复根因,而非表象:
-
创建失败测试用例
- 尽可能简单地复现问题
- 如果可能,编写自动化测试
- 如果没有测试框架,则编写一次性测试脚本
- 修复前必须有测试用例
-
实施单一修复
- 解决已确认的根因
- 一次只做一项更改
- 不要顺手改进其他内容
- 不要捆绑重构
-
验证修复
- 测试现在通过了吗?
- 是否有其他测试失败?
- 问题是否确实已解决?
-
如果修复无效
- 停止
- 统计:你尝试过多少次修复?
- 如果少于 3 次:返回阶段 1,结合新信息重新分析
- 如果达到或超过 3 次:停止并质疑架构(见下方第 5 步)
- 未进行架构讨论前,不要尝试第 4 次修复
-
如果 3 次或更多修复均失败:质疑架构
表明存在架构问题的模式:
- 每次修复都会在不同位置暴露新的共享状态、耦合或问题
- 修复需要“大规模重构”才能实施
- 每次修复都会在其他位置引发新的问题
停止并质疑根本设计:
- 这种模式从根本上是否合理?
- 我们是否只是在“纯粹惯性的驱使下坚持下去”?
- 应该重构架构,还是继续修复表象?
尝试更多修复前,先与人类协作者讨论
这不是假设失败,而是架构错误。
危险信号——停止并遵循流程
如果你发现自己在想:
- “先快速修一下,之后再调查”
- “只要试着改一下 X,看看是否有效”
- “一次做多项更改,然后运行测试”
- “跳过测试,我手动验证一下”
- “问题大概出在 X,我来修一下”
- “我还没完全理解,但这或许有用”
- “模式建议这么做,但我会换种方式”
- “主要问题如下:[未调查就列出修复方案]”
- 尚未追踪数据流就提出解决方案
- 尚未排除仅需配置调整的修复方案(选项零),也未追踪导入时哪些对象读取环境变量或配置,就提议替换 SDK 或进行大规模迁移
- “再尝试修复一次”(此前已尝试 2 次或更多)
- 每次修复都会在不同位置暴露新问题
以上任何一种情况都意味着:停止。返回阶段 1。
**如果 3 次或更多修复均失败:**质疑架构(见阶段 4.5)
用户指出你做错了时的信号
留意以下纠正:
- “这不是正在发生吗?”——你未经验证就下了结论
- “这样能让我们看到……吗?”——你本该收集证据
- “别猜了”——你还没弄清楚就提出修复方案
- “深入思考”——应质疑根本设计,而不只是表象
- “我们卡住了吗?”(语气沮丧)——你的方法不起作用
**看到这些信号时:**停止。返回阶段 1。
防止自我辩解
| 借口 | 事实 |
|---|---|
| “问题很简单,不需要遵循流程” | 简单问题也有根因。对简单 Bug 来说,这套流程很快。 |
| “情况紧急,没时间遵循流程” | 系统化调试比反复猜测和试错更快。 |
| “先试试这个,再调查” | 第一次修复会定下处理模式。一开始就应该做好。 |
| “等确认修复有效后再写测试” | 未经测试的修复难以持久。先写测试才能验证修复。 |
| “一次做多项修复能节省时间” | 无法确定哪项修复有效,还会引入新 Bug。 |
| “参考内容太长,我会自行调整模式” | 理解不完整必然导致 Bug。请完整阅读。 |
| “我看出问题了,让我修一下” | 看到表象 ≠ 理解根因。 |
| “再尝试修复一次”(失败 2 次或更多后) | 失败 3 次或更多说明存在架构问题。应质疑处理模式,而非再次修复。 |
快速参考
| 阶段 | 关键活动 | 成功标准 |
|---|---|---|
| 1. 根本原因 | 阅读错误信息,重现问题,检查变更,收集证据 | 了解问题是什么以及原因 |
| 2. 模式 | 查找可用示例,进行比较 | 找出差异 |
| 3. 假设 | 提出理论,进行最小化测试 | 得到确认或提出新假设 |
| 4. 实施 | 创建测试,修复问题,验证结果 | 问题已解决,测试通过 |
当流程揭示“没有根本原因”时
如果系统调查发现,问题确实与环境、时间有关或由外部因素导致:
- 你已完成调查流程
- 记录你调查的内容
- 实施适当的处理方式(重试、超时、错误消息)
- 添加监控和日志记录,以便后续调查
但是: 95% 的“没有根本原因”案例都是调查不充分。
输出格式
## Bug Investigation
### Phase 1: Evidence Gathered
- **Error**: [exact error message]
- **Stack trace**: [relevant lines]
- **Reproduction**: [steps to reproduce]
- **Recent changes**: [commits/changes]
### Phase 2: Pattern Analysis
- **Working example**: [similar working code]
- **Key differences**: [what's different]
### Phase 3: Hypothesis
- **Theory**: [I think X because Y]
- **Test**: [minimal change made]
- **Result**: [confirmed/refuted]
### Phase 4: Fix
- **Root cause**: [actual cause with evidence]
- **Change**: [summary of fix]
- **File**: [path:line]
- **Regression test**: [test added]
### Verification
- Test command: [command] → exit 0
- All tests: PASS
- Functionality: Restored