根因追踪Skill RootCauseTracing

根因追踪是一种软件调试技能,用于系统地通过调用栈向后追踪bug,定位原始触发点,避免仅治标。适用于软件开发中的错误处理和测试,提升代码质量和可靠性。关键词:根因追踪,bug调试,调用栈,软件测试,错误追踪,纵深防御。

测试 0 次安装 0 次浏览 更新于 3/18/2026

名称: 根因追踪 描述: 系统地追踪bug向后通过调用栈以找到原始触发点 使用时机: 当错误在执行深处发生且需要追踪回找到原始触发点时 版本: 1.1.0 语言: 所有

根因追踪

概述

Bug经常在调用栈深处显现(例如,git init在错误目录,文件创建在错误位置,数据库用错误路径打开)。你的直觉是在错误出现的地方修复,但那只是治标。

核心原则: 通过调用链向后追踪,直到找到原始触发点,然后在源头上修复。

使用时机

digraph when_to_use {
    "bug出现在调用栈深处?" [shape=diamond];
    "可以向后追踪?" [shape=diamond];
    "在症状点修复" [shape=box];
    "追踪到原始触发点" [shape=box];
    "更好:同时添加纵深防御" [shape=box];

    "bug出现在调用栈深处?" -> "可以向后追踪?" [label="是"];
    "可以向后追踪?" -> "追踪到原始触发点" [label="是"];
    "可以向后追踪?" -> "在症状点修复" [label="否 - 死胡同"];
    "追踪到原始触发点" -> "更好:同时添加纵深防御";
}

使用时机:

  • 错误发生在执行深处(非入口点)
  • 堆栈跟踪显示长调用链
  • 不清楚无效数据源自何处
  • 需要找到哪个测试/代码触发问题

追踪过程

1. 观察症状

错误:git init在 /Users/jesse/project/packages/core 失败

2. 找到直接原因

什么代码直接导致这个?

await execFileAsync('git', ['init'], { cwd: projectDir });

3. 询问:什么调用了这个?

WorktreeManager.createSessionWorktree(projectDir, sessionId)
  → 由 Session.initializeWorkspace() 调用
  → 由 Session.create() 调用
  → 由测试在 Project.create() 处调用

4. 持续向上追踪

传递了什么值?

  • projectDir = ''(空字符串!)
  • 空字符串作为 cwd 解析为 process.cwd()
  • 那是源代码目录!

5. 找到原始触发点

空字符串从哪里来?

const context = setupCoreTest(); // 返回 { tempDir: '' }
Project.create('name', context.tempDir); // 在 beforeEach 之前访问!

添加堆栈跟踪

当你无法手动追踪时,添加检测:

// 在问题操作之前
async function gitInit(directory: string) {
  const stack = new Error().stack;
  console.error('DEBUG git init:', {
    directory,
    cwd: process.cwd(),
    nodeEnv: process.env.NODE_ENV,
    stack,
  });

  await execFileAsync('git', ['init'], { cwd: directory });
}

关键: 在测试中使用 console.error()(不是 logger - 可能不显示)

运行并捕获:

npm test 2>&1 | grep 'DEBUG git init'

分析堆栈跟踪:

  • 查找测试文件名
  • 找到触发调用的行号
  • 识别模式(同一测试?同一参数?)

找出哪个测试导致污染

如果某物在测试中出现,但不知道哪个测试:

使用二分脚本:@find-polluter.sh

./find-polluter.sh '.git' 'src/**/*.test.ts'

逐个运行测试,在第一个污染者处停止。参见脚本用法。

真实示例:空项目目录

症状: .git 创建在 packages/core/(源代码)中

追踪链:

  1. git initprocess.cwd() 中运行 ← 空 cwd 参数
  2. WorktreeManager 以空 projectDir 调用
  3. Session.create() 传递空字符串
  4. 测试在 beforeEach 之前访问 context.tempDir
  5. setupCoreTest() 最初返回 { tempDir: '' }

根因: 顶层变量初始化访问空值

修复: 使 tempDir 为 getter,如果在 beforeEach 之前访问则抛出

同时添加纵深防御:

  • 层 1: Project.create() 验证目录
  • 层 2: WorkspaceManager 验证非空
  • 层 3: NODE_ENV 防护拒绝在 tmpdir 外进行 git init
  • 层 4: 在 git init 之前堆栈跟踪日志

关键原则

digraph principle {
    "找到直接原因" [shape=ellipse];
    "可以向上追踪一级?" [shape=diamond];
    "向后追踪" [shape=box];
    "这是源吗?" [shape=diamond];
    "在源头修复" [shape=box];
    "在每一层添加验证" [shape=box];
    "bug不可能" [shape=doublecircle];
    "切勿只修复症状" [shape=octagon, style=filled, fillcolor=red, fontcolor=white];

    "找到直接原因" -> "可以向上追踪一级?";
    "可以向上追踪一级?" -> "向后追踪" [label="是"];
    "可以向上追踪一级?" -> "切勿只修复症状" [label="否"];
    "向后追踪" -> "这是源吗?";
    "这是源吗?" -> "向后追踪" [label="否 - 继续"];
    "这是源吗?" -> "在源头修复" [label="是"];
    "在源头修复" -> "在每一层添加验证";
    "在每一层添加验证" -> "bug不可能";
}

切勿只修复错误出现的地方。 向后追踪以找到原始触发点。

堆栈跟踪提示

在测试中: 使用 console.error() 而不是 logger - logger 可能被抑制 在操作前: 在危险操作前记录,而不是在失败后 包括上下文: 目录,cwd,环境变量,时间戳 捕获堆栈: new Error().stack 显示完整调用链

实际影响

从调试会话(2025-10-03):

  • 通过 5 级追踪找到根因
  • 在源头修复(getter 验证)
  • 添加 4 层防御
  • 1847 个测试通过,零污染