🚀 快速安装
复制以下命令并运行,立即安装此 Skill:
npx skills add https://github.com/wshobson/agents --skill debugging-strategies
💡 提示:需要 Node.js 和 NPM
调试策略
运用经过验证的策略、强大的工具和有条不紊的方法,将调试从令人沮丧的猜测转变为系统性的问题解决过程。
何时使用此技能
- 追踪难以发现的错误
- 调查性能问题
- 理解不熟悉的代码库
- 调试生产环境问题
- 分析崩溃转储和堆栈跟踪
- 分析应用程序性能
- 调查内存泄漏
- 调试分布式系统
核心原则
1. 科学方法
1. 观察:实际行为是什么?
2. 假设:可能导致该行为的原因是什么?
3. 实验:验证你的假设
4. 分析:结果是否证明/否定了你的理论?
5. 重复:直到找到根本原因
2. 调试心态
不要假设:
- “不可能是 X” – 有可能就是它
- “我没改过 Y” – 检查一下总没错
- “在我机器上是好的” – 找出原因
应该做:
- 稳定复现问题
- 隔离问题
- 做好详细记录
- 质疑一切
- 卡住时休息一下
3. 橡皮鸭调试法
向别人(橡皮鸭、同事或自己)解释你的代码和问题,往往能揭示问题所在。
系统性调试流程
阶段 1:复现
## 复现清单
1. **你能复现它吗?**
- 总是发生?偶尔发生?随机发生?
- 需要特定条件吗?
- 其他人能复现吗?
2. **创建最小复现示例**
- 简化为最小的示例
- 移除无关代码
- 隔离问题
3. **记录步骤**
- 写下确切的复现步骤
- 记录环境详情
- 捕获错误信息
阶段 2:收集信息
## 信息收集
1. **错误信息**
- 完整的堆栈跟踪
- 错误代码
- 控制台/日志输出
2. **环境**
- 操作系统版本
- 语言/运行时版本
- 依赖项版本
- 环境变量
3. **近期更改**
- Git 历史
- 部署时间线
- 配置更改
4. **影响范围**
- 影响所有用户还是特定用户?
- 影响所有浏览器还是特定浏览器?
- 仅生产环境还是开发环境也有?
阶段 3:形成假设
## 形成假设
基于收集的信息,思考:
1. **发生了什么变化?**
- 最近的代码更改
- 依赖项更新
- 基础设施变更
2. **有什么不同?**
- 工作正常的环境 vs 出错的环境
- 工作正常的用户 vs 出错的用户
- 之前 vs 之后
3. **哪里可能出错?**
- 输入验证
- 业务逻辑
- 数据层
- 外部服务
阶段 4:测试与验证
## 测试策略
1. **二分搜索**
- 注释掉一半代码
- 逐步缩小问题范围
- 重复直到找到根源
2. **添加日志**
- 在关键位置添加打印语句
- 追踪变量值
- 跟踪执行流程
3. **隔离组件**
- 分别测试每个部分
- 模拟依赖项
- 移除复杂性
4. **对比工作与故障状态**
- 对比配置差异
- 对比环境差异
- 对比数据差异
调试工具
JavaScript/TypeScript 调试
// Chrome DevTools 调试器
function processOrder(order: Order) {
debugger; // 执行在此暂停
const total = calculateTotal(order);
console.log("总额:", total);
// 条件断点
if (order.items.length > 10) {
debugger; // 仅当条件为真时中断
}
return total;
}
// 控制台调试技巧
console.log("值:", value); // 基础
console.table(arrayOfObjects); // 表格格式
console.time("操作");
/* 代码 */ console.timeEnd("操作"); // 计时
console.trace(); // 堆栈跟踪
console.assert(value > 0, "值必须为正数"); // 断言
// 性能分析
performance.mark("开始-操作");
// ... 操作代码
performance.mark("结束-操作");
performance.measure("操作", "开始-操作", "结束-操作");
console.log(performance.getEntriesByType("measure"));
VS Code 调试器配置:
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "调试程序",
"program": "${workspaceFolder}/src/index.ts",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"skipFiles": ["<node_internals>/**"]
},
{
"type": "node",
"request": "launch",
"name": "调试测试",
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
"args": ["--runInBand", "--no-cache"],
"console": "integratedTerminal"
}
]
}
Python 调试
# 内置调试器 (pdb)
import pdb
def calculate_total(items):
total = 0
pdb.set_trace() # 调试器在此启动
for item in items:
total += item.price * item.quantity
return total
# 断点 (Python 3.7+)
def process_order(order):
breakpoint() # 比 pdb.set_trace() 更方便
# ... 代码
# 事后调试
try:
risky_operation()
except Exception:
import pdb
pdb.post_mortem() # 在异常点进行调试
# IPython 调试 (ipdb)
from ipdb import set_trace
set_trace() # 比 pdb 更好的界面
# 用于调试的日志
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
def fetch_user(user_id):
logger.debug(f'正在获取用户:{user_id}')
user = db.query(User).get(user_id)
logger.debug(f'找到用户:{user}')
return user
# 性能分析
import cProfile
import pstats
cProfile.run('slow_function()', 'profile_stats')
stats = pstats.Stats('profile_stats')
stats.sort_stats('cumulative')
stats.print_stats(10) # 最慢的 10 个
Go 调试
// Delve 调试器
// 安装:go install github.com/go-delve/delve/cmd/dlv@latest
// 运行:dlv debug main.go
import (
"fmt"
"runtime"
"runtime/debug"
)
// 打印堆栈跟踪
func debugStack() {
debug.PrintStack()
}
// 带调试的 panic 恢复
func processRequest() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Panic:", r)
debug.PrintStack()
}
}()
// ... 可能引发 panic 的代码
}
// 内存分析
import _ "net/http/pprof"
// 访问 http://localhost:6060/debug/pprof/
// CPU 分析
import (
"os"
"runtime/pprof"
)
f, _ := os.Create("cpu.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// ... 要分析的代码
高级调试技巧
技巧 1:二分搜索调试
# 使用 Git bisect 查找回归错误
git bisect start
git bisect bad # 当前提交是坏的
git bisect good v1.0.0 # v1.0.0 是好的
# Git 会切换到中间的提交
# 测试它,然后:
git bisect good # 如果它工作正常
git bisect bad # 如果它已损坏
# 继续直到找到错误
git bisect reset # 完成后重置
技巧 2:差异调试
对比工作与故障状态:
## 有什么不同?
| 方面 | 工作环境 | 故障环境 |
| ------------ | ----------- | -------------- |
| 环境 | 开发环境 | 生产环境 |
| Node 版本 | 18.16.0 | 18.15.0 |
| 数据 | 空数据库 | 100 万条记录 |
| 用户 | 管理员 | 普通用户 |
| 浏览器 | Chrome | Safari |
| 时间 | 白天 | 午夜之后 |
假设:时间相关的问题?检查时区处理。
技巧 3:跟踪调试
// 函数调用跟踪
function trace(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor,
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`调用 ${propertyKey},参数:`, args);
const result = originalMethod.apply(this, args);
console.log(`${propertyKey} 返回:`, result);
return result;
};
return descriptor;
}
class OrderService {
@trace
calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
}
技巧 4:内存泄漏检测
// Chrome DevTools 内存分析器
// 1. 拍摄堆快照
// 2. 执行操作
// 3. 拍摄另一个快照
// 4. 对比快照
// Node.js 内存调试
if (process.memoryUsage().heapUsed > 500 * 1024 * 1024) {
console.warn("内存使用过高:", process.memoryUsage());
// 生成堆转储
require("v8").writeHeapSnapshot();
}
// 在测试中查找内存泄漏
let beforeMemory: number;
beforeEach(() => {
beforeMemory = process.memoryUsage().heapUsed;
});
afterEach(() => {
const afterMemory = process.memoryUsage().heapUsed;
const diff = afterMemory - beforeMemory;
if (diff > 10 * 1024 * 1024) {
// 10MB 阈值
console.warn(`可能存在内存泄漏:${diff / 1024 / 1024}MB`);
}
});
按问题类型分类的调试模式
模式 1:间歇性错误
## 应对不稳定的错误
1. **添加详细日志**
- 记录时间信息
- 记录所有状态转换
- 记录外部交互
2. **查找竞态条件**
- 并发访问共享状态
- 异步操作乱序完成
- 缺少同步机制
3. **检查时间依赖**
- setTimeout/setInterval
- Promise 解析顺序
- 动画帧时序
4. **压力测试**
- 多次运行
- 改变时序
- 模拟负载
模式 2:性能问题
## 性能调试
1. **先做性能分析**
- 不要盲目优化
- 测量优化前后
- 找出瓶颈
2. **常见元凶**
- N+1 查询
- 不必要的重新渲染
- 大数据处理
- 同步 I/O
3. **工具**
- 浏览器 DevTools 性能选项卡
- Lighthouse
- Python:cProfile, line_profiler
- Node:clinic.js, 0x
模式 3:生产环境错误
## 生产环境调试
1. **收集证据**
- 错误追踪(Sentry, Bugsnag)
- 应用程序日志
- 用户报告
- 指标/监控
2. **本地复现**
- 使用生产数据(匿名化)
- 匹配环境
- 遵循确切步骤
3. **安全调查**
- 不要直接修改生产环境
- 使用功能开关
- 添加监控/日志
- 在预发布环境测试修复
最佳实践
- 先复现:不能复现的问题就无法修复
- 隔离问题:简化到最小情况
- 阅读错误信息:它们通常很有帮助
- 检查近期更改:大多数错误是近期引入的
- 使用版本控制:Git bisect, blame, 历史记录
- 适时休息:换换思路看得更清
- 记录发现:帮助未来的自己
- 修复根本原因:而不仅仅是症状
常见调试错误
-
- 同时做多个更改:一次只改一个地方
- 不看错误信息:阅读完整的堆栈跟踪
- 假设问题很复杂:往往很简单
- 在生产环境保留调试日志:发布前移除
- 不使用调试器:console.log 不一定是最好的
- 过早放弃:坚持就有回报
- 不测试修复:验证它是否真的有效
快速调试清单
## 卡住时,检查:
- [ ] 拼写错误(变量名拼错)
- [ ] 大小写敏感性(fileName 与 filename)
- [ ] 空值/未定义值
- [ ] 数组索引差一错误
- [ ] 异步时序(竞态条件)
- [ ] 作用域问题(闭包,变量提升)
- [ ] 类型不匹配
- [ ] 缺失依赖项
- [ ] 环境变量
- [ ] 文件路径(绝对路径与相对路径)
- [ ] 缓存问题(清除缓存)
- [ ] 数据陈旧(刷新数据库)
📄 原始文档
完整文档(英文):
https://skills.sh/wshobson/agents/debugging-strategies
💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

评论(0)