🚀 快速安装

复制以下命令并运行,立即安装此 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. **安全调查**
   - 不要直接修改生产环境
   - 使用功能开关
   - 添加监控/日志
   - 在预发布环境测试修复

最佳实践

  1. 先复现:不能复现的问题就无法修复
  2. 隔离问题:简化到最小情况
  3. 阅读错误信息:它们通常很有帮助
  4. 检查近期更改:大多数错误是近期引入的
  5. 使用版本控制:Git bisect, blame, 历史记录
  6. 适时休息:换换思路看得更清
  7. 记录发现:帮助未来的自己
  8. 修复根本原因:而不仅仅是症状

常见调试错误

    • 同时做多个更改:一次只改一个地方
    • 不看错误信息:阅读完整的堆栈跟踪
    • 假设问题很复杂:往往很简单
    • 在生产环境保留调试日志:发布前移除
    • 不使用调试器:console.log 不一定是最好的
    • 过早放弃:坚持就有回报
    • 不测试修复:验证它是否真的有效

快速调试清单

## 卡住时,检查:

- [ ] 拼写错误(变量名拼错)
- [ ] 大小写敏感性(fileName 与 filename)
- [ ] 空值/未定义值
- [ ] 数组索引差一错误
- [ ] 异步时序(竞态条件)
- [ ] 作用域问题(闭包,变量提升)
- [ ] 类型不匹配
- [ ] 缺失依赖项
- [ ] 环境变量
- [ ] 文件路径(绝对路径与相对路径)
- [ ] 缓存问题(清除缓存)
- [ ] 数据陈旧(刷新数据库)

📄 原始文档

完整文档(英文):

https://skills.sh/wshobson/agents/debugging-strategies

💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。