L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Développement · SKILL.md

systematic-debugging

systematic-debugging

系统化调试助手,帮助科研人员使用结构化方法定位和修复代码问题。支持4阶段调试流程、根因追踪技术、日志和断点策略、条件等待方法、防御式编程。当用户需要:(1) 调试科研代码错误、(2) 追踪数据分析问题、(3) 定位统计计算错误、(4) 解决实验代码bug、(5) 优化代码性能时触发。关键词:debugging、调试、bug修复、错误追踪、问题定位、troubleshooting。

Independent GitHub snapshot · Automated classification · Original documentation governs

Hhollis36 / claude-skillApache-2.0

Documentation de la Skill

GitHub

name: systematic-debugging description: | 系统化调试助手,帮助科研人员使用结构化方法定位和修复代码问题。支持4阶段调试流程、根因追踪技术、日志和断点策略、条件等待方法、防御式编程。当用户需要:(1) 调试科研代码错误、(2) 追踪数据分析问题、(3) 定位统计计算错误、(4) 解决实验代码bug、(5) 优化代码性能时触发。关键词:debugging、调试、bug修复、错误追踪、问题定位、troubleshooting。 license: Apache-2.0

系统化调试助手

帮助科研人员使用系统化、可重复的方法定位和修复代码问题,避免随机猜测和无效尝试。

核心原则

系统化调试的黄金法则

  1. 重现优先 - 不能重现的问题无法修复
  2. 假设驱动 - 基于证据提出假设,而不是猜测
  3. 隔离问题 - 逐步缩小问题范围
  4. 验证修复 - 确保问题真正解决,而非表面消失

四阶段调试流程

阶段 1:问题重现 (Reproduce)

目标:可靠地触发问题

步骤 1.1:记录问题症状

## Bug报告模板

### 问题描述
简要描述问题是什么

### 预期行为
代码应该做什么

### 实际行为
代码实际做了什么

### 重现步骤
1. 运行 `python analysis.py`
2. 使用数据集 `data/experiment_001.csv`
3. 观察输出

### 环境信息
- Python版本: 3.11.0
- 操作系统: Ubuntu 22.04
- 关键依赖: numpy==1.24.0, pandas==2.0.0

### 错误消息

ValueError: could not convert string to float: 'NA' File "analysis.py", line 45, in process_data


### 数据样本
提供最小化的测试数据

步骤 1.2:创建最小可重现示例 (MRE)

# ❌ BAD - 完整复杂的代码
def analyze_experiment():
    # 100+ 行代码
    data = load_data()
    preprocessed = preprocess(data)
    results = complex_analysis(preprocessed)
    # 错误在某处发生...
    return results

# ✅ GOOD - 最小可重现示例
import pandas as pd

# 错误:无法将'NA'转换为浮点数
data = pd.DataFrame({
    'value': ['1.0', '2.0', 'NA', '4.0']
})

# 这行会失败
numeric_data = data['value'].astype(float)  # ValueError!

创建MRE的原则:

  • 移除所有无关代码
  • 使用小数据集(< 10行)
  • 保持错误可重现
  • 独立运行(无外部依赖)

步骤 1.3:固定输入和环境

import numpy as np
import random

# 固定随机种子确保可重现
np.random.seed(42)
random.seed(42)

# 使用固定的测试数据
test_data = np.array([1.0, 2.0, 3.0, 4.0, 5.0])

# 记录环境
print(f"NumPy版本: {np.__version__}")
print(f"测试数据: {test_data}")

重现检查清单:

  • 问题可在自己机器上重现
  • 创建了最小可重现示例
  • 固定了随机性(如有)
  • 记录了环境信息
  • 问题可在他人机器上重现(可选)

阶段 2:定位问题 (Locate)

目标:找到导致问题的具体代码行

策略 2.1:二分查找法

def process_data(data):
    """使用打印语句进行二分查找"""
    print("开始处理数据")  # 检查点 1

    # 步骤 1: 加载
    loaded = load_data(data)
    print(f"加载完成: {len(loaded)} 行")  # 检查点 2

    # 步骤 2: 清理
    cleaned = clean_data(loaded)
    print(f"清理完成: {len(cleaned)} 行")  # 检查点 3

    # 步骤 3: 转换
    transformed = transform_data(cleaned)
    print(f"转换完成: shape = {transformed.shape}")  # 检查点 4

    # 步骤 4: 分析
    results = analyze(transformed)
    print(f"分析完成: {results}")  # 检查点 5

    return results

# 运行并观察哪个检查点后出现问题

二分法原则:

  1. 在代码中间添加检查点
  2. 运行代码观察输出
  3. 如果问题在检查点前:在前半部分继续二分
  4. 如果问题在检查点后:在后半部分继续二分
  5. 重复直到找到问题行

策略 2.2:日志记录

import logging

# 配置日志
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('debug.log'),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)

def analyze_data(data):
    """使用日志记录详细信息"""
    logger.debug(f"开始分析, 数据形状: {data.shape}")

    try:
        # 记录输入
        logger.debug(f"输入数据样本:\n{data.head()}")

        # 计算
        mean_value = data['value'].mean()
        logger.info(f"计算均值: {mean_value}")

        # 检查异常值
        std_value = data['value'].std()
        outliers = data[abs(data['value'] - mean_value) > 3 * std_value]
        if len(outliers) > 0:
            logger.warning(f"发现 {len(outliers)} 个异常值")

        return mean_value

    except Exception as e:
        logger.error(f"分析失败: {str(e)}", exc_info=True)
        raise

日志级别使用:

  • DEBUG: 详细的诊断信息
  • INFO: 正常的操作信息
  • WARNING: 警告但不影响运行
  • ERROR: 错误导致功能失败
  • CRITICAL: 严重错误导致程序崩溃

策略 2.3:断点调试

# 使用Python调试器 (pdb)
import pdb

def problematic_function(data):
    # 在此处设置断点
    pdb.set_trace()

    # 调试时可用命令:
    # n (next) - 执行下一行
    # s (step) - 进入函数
    # c (continue) - 继续执行
    # p variable - 打印变量
    # l - 显示当前代码
    # q - 退出调试器

    result = process(data)
    return result

Jupyter Notebook 调试:

# 在单元格中使用
%debug  # 在异常后进入调试器

# 或使用 IPython 调试器
from IPython.core.debugger import set_trace
set_trace()  # 设置断点

VS Code / PyCharm 断点:

  • 在行号左侧点击设置断点
  • F5 启动调试
  • F10 单步执行
  • F11 进入函数
  • 查看变量面板

策略 2.4:数据检查

import pandas as pd
import numpy as np

def inspect_data(data, name="data"):
    """全面检查数据状态"""
    print(f"\n{'='*60}")
    print(f"检查 {name}")
    print(f"{'='*60}")

    # 基本信息
    print(f"类型: {type(data)}")
    print(f"形状: {data.shape if hasattr(data, 'shape') else 'N/A'}")

    # DataFrame 特定检查
    if isinstance(data, pd.DataFrame):
        print(f"\n列: {list(data.columns)}")
        print(f"\n数据类型:\n{data.dtypes}")
        print(f"\n缺失值:\n{data.isnull().sum()}")
        print(f"\n描述性统计:\n{data.describe()}")
        print(f"\n前5行:\n{data.head()}")

    # NumPy 数组检查
    elif isinstance(data, np.ndarray):
        print(f"数据类型: {data.dtype}")
        print(f"最小值: {data.min()}")
        print(f"最大值: {data.max()}")
        print(f"均值: {data.mean()}")
        print(f"NaN数量: {np.isnan(data).sum()}")
        print(f"Inf数量: {np.isinf(data).sum()}")

    # 列表或其他
    else:
        print(f"长度: {len(data) if hasattr(data, '__len__') else 'N/A'}")
        print(f"样本: {data[:5] if hasattr(data, '__getitem__') else data}")

# 使用示例
data = pd.read_csv('data.csv')
inspect_data(data, "原始数据")

processed = preprocess(data)
inspect_data(processed, "处理后数据")

阶段 3:诊断根因 (Diagnose)

目标:理解为什么会出现问题

技术 3.1:假设-验证循环

## 根因分析模板

### 问题症状
ValueError: could not convert string to float: 'NA'

### 假设 1: 数据包含非数值字符串
**证据需求**: 检查数据中的唯一值
**验证代码**:
```python
print(data['value'].unique())
# 输出: ['1.0', '2.0', 'NA', '4.0']

结论: ✅ 确认 - 数据中确实有 'NA' 字符串

假设 2: 数据加载时未正确处理缺失值

证据需求: 检查 read_csv 的 na_values 参数 验证代码:

# 当前代码
data = pd.read_csv('data.csv')

# 正确代码
data = pd.read_csv('data.csv', na_values=['NA', 'N/A', 'null'])

结论: ✅ 确认 - 未指定 na_values 参数

根本原因

数据加载时未将 'NA' 识别为缺失值,导致被当作字符串处理, 后续类型转换时失败。


#### 技术 3.2:差异分析

```python
# 比较工作版本和问题版本
def compare_versions(working_data, broken_data):
    """比较两个版本的差异"""
    print("差异分析:")

    # 形状差异
    if working_data.shape != broken_data.shape:
        print(f"形状不同: {working_data.shape} vs {broken_data.shape}")

    # 数据类型差异
    if not working_data.dtypes.equals(broken_data.dtypes):
        print("\n数据类型差异:")
        for col in working_data.columns:
            if working_data[col].dtype != broken_data[col].dtype:
                print(f"  {col}: {working_data[col].dtype} -> {broken_data[col].dtype}")

    # 值差异
    differences = working_data != broken_data
    if differences.any().any():
        print(f"\n发现 {differences.sum().sum()} 个不同的值")
        print("\n不同值的位置:")
        print(differences[differences.any(axis=1)])

技术 3.3:回溯追踪

import traceback

try:
    result = problematic_function()
except Exception as e:
    # 打印完整的调用栈
    print("完整错误追溯:")
    traceback.print_exc()

    # 获取追溯对象
    tb = traceback.extract_tb(e.__traceback__)

    # 分析每一层
    print("\n调用栈分析:")
    for frame in tb:
        print(f"\n文件: {frame.filename}")
        print(f"函数: {frame.name}")
        print(f"行号: {frame.lineno}")
        print(f"代码: {frame.line}")

技术 3.4:五个为什么

## 五个为什么分析

### 问题: 统计分析结果不正确

**为什么1**: 为什么结果不正确?
→ 因为p值异常小 (p < 1e-50)

**为什么2**: 为什么p值异常小?
→ 因为样本量被错误计算为100000而不是100

**为什么3**: 为什么样本量计算错误?
→ 因为使用了 len(data) 而不是 len(data.dropna())

**为什么4**: 为什么使用了错误的长度?
→ 因为没有考虑缺失值的影响

**为什么5**: 为什么没有考虑缺失值?
→ 因为没有在数据加载后立即检查数据质量

### 根本原因
缺乏数据质量检查步骤

### 预防措施
在分析前添加数据验证函数

阶段 4:修复和验证 (Fix & Verify)

目标:修复问题并确保不再发生

步骤 4.1:最小化修改

# ❌ BAD - 大规模重写
def analyze_data(data):
    # 完全重写了整个函数
    # 引入了新的潜在问题
    pass

# ✅ GOOD - 最小化修改
def analyze_data(data):
    # 原有代码保持不变
    # ...

    # 只修复具体问题
    # 之前: numeric_data = data['value'].astype(float)
    # 之后:
    numeric_data = pd.to_numeric(data['value'], errors='coerce')

    # 原有代码继续
    # ...

步骤 4.2:添加测试用例

import pytest
import pandas as pd

def test_handle_missing_values():
    """测试缺失值处理"""
    # 创建包含缺失值的测试数据
    test_data = pd.DataFrame({
        'value': ['1.0', '2.0', 'NA', '4.0', 'N/A']
    })

    # 加载并处理
    result = process_data(test_data)

    # 验证
    assert result['value'].dtype == 'float64'
    assert result['value'].isna().sum() == 2  # 应该有2个NaN

def test_edge_case_empty_data():
    """测试空数据的边界情况"""
    test_data = pd.DataFrame({'value': []})
    result = process_data(test_data)
    assert len(result) == 0

def test_edge_case_all_na():
    """测试全为缺失值的情况"""
    test_data = pd.DataFrame({'value': ['NA', 'NA', 'NA']})
    result = process_data(test_data)
    assert result['value'].isna().all()

步骤 4.3:回归测试

# 运行所有测试确保修复没有破坏其他功能
pytest tests/ -v

# 检查测试覆盖率
pytest tests/ --cov=src --cov-report=html

# 查看覆盖率报告
# open htmlcov/index.html

步骤 4.4:防御式编程

# 添加输入验证
def analyze_data(data):
    """分析数据(带防御性检查)"""

    # 验证输入
    if not isinstance(data, pd.DataFrame):
        raise TypeError(f"期望 DataFrame, 得到 {type(data)}")

    if 'value' not in data.columns:
        raise ValueError("数据必须包含 'value' 列")

    if len(data) == 0:
        raise ValueError("数据不能为空")

    # 处理缺失值
    numeric_data = pd.to_numeric(data['value'], errors='coerce')

    # 检查转换后的数据
    if numeric_data.isna().all():
        raise ValueError("所有值都无法转换为数字")

    # 继续分析
    result = numeric_data.mean()

    # 验证输出
    if not isinstance(result, (int, float)):
        raise TypeError(f"结果应为数值, 得到 {type(result)}")

    return result

步骤 4.5:记录修复

## 修复记录

### Bug ID: #123
**标题**: ValueError when processing data with 'NA' strings

### 根本原因
数据加载时未将 'NA' 识别为缺失值,导致类型转换失败

### 修复方案
在 `pd.read_csv()` 中添加 `na_values=['NA', 'N/A']` 参数

### 代码变更
```diff
- data = pd.read_csv('data.csv')
+ data = pd.read_csv('data.csv', na_values=['NA', 'N/A', 'null'])

测试

  • 添加了测试用例 test_handle_missing_values
  • 所有现有测试通过
  • 手工测试原始问题数据

防止复发

  • 添加了数据验证函数
  • 更新了数据加载文档
  • 在代码审查检查清单中添加了此项

修复人: [姓名]

日期: 2026-04-06


---

## 常见问题类型和调试策略

### 类型 1: 数据相关问题

#### 问题:数据格式错误

```python
# 症状:类型转换失败
# TypeError: unsupported operand type(s) for +: 'int' and 'str'

# 诊断工具
def diagnose_data_types(df):
    """诊断数据类型问题"""
    print("数据类型检查:")
    print(df.dtypes)

    print("\n每列的唯一类型:")
    for col in df.columns:
        unique_types = df[col].apply(type).unique()
        print(f"{col}: {unique_types}")

    print("\n可能的问题值:")
    for col in df.columns:
        if df[col].dtype == 'object':
            # 尝试转换为数值
            try:
                pd.to_numeric(df[col])
            except:
                problem_values = df[col][pd.to_numeric(df[col], errors='coerce').isna()]
                if len(problem_values) > 0:
                    print(f"{col}: {problem_values.unique()[:5]}")

问题:缺失值处理

# 症状:计算结果为 NaN
# result = data.mean()  # returns NaN

# 诊断
print(f"缺失值数量: {data.isna().sum()}")
print(f"缺失值比例: {data.isna().mean():.2%}")

# 修复选项
# 1. 删除缺失值
clean_data = data.dropna()

# 2. 填充缺失值
filled_data = data.fillna(data.mean())

# 3. 插值
interpolated_data = data.interpolate()

类型 2: 逻辑错误

问题:循环逻辑错误

# ❌ BAD - 索引错误
for i in range(len(data)):
    if data[i] > threshold:
        data[i+1] = 0  # IndexError when i = len(data)-1!

# ✅ GOOD - 正确的边界检查
for i in range(len(data) - 1):  # 注意范围
    if data[i] > threshold:
        data[i+1] = 0

问题:条件逻辑错误

# ❌ BAD - 逻辑错误
if age >= 18 and age <= 65:
    category = 'adult'
elif age >= 65:
    category = 'senior'
else:
    category = 'minor'
# 问题:65岁既满足第一个也满足第二个条件

# ✅ GOOD - 互斥条件
if age < 18:
    category = 'minor'
elif age < 65:
    category = 'adult'
else:
    category = 'senior'

类型 3: 数值计算问题

问题:浮点数精度

# 症状:相等比较失败
a = 0.1 + 0.2
print(a == 0.3)  # False!
print(a)  # 0.30000000000000004

# 修复:使用容差比较
import math
print(math.isclose(a, 0.3))  # True

# 或使用 numpy
import numpy as np
print(np.isclose(a, 0.3))  # True

问题:整数除法

# Python 2 风格的整数除法
result = 5 / 2  # Python 3: 2.5, Python 2: 2

# 确保浮点除法
result = 5 / 2.0
# 或
result = 5 / float(2)
# 或
from __future__ import division  # Python 2

类型 4: 性能问题

问题:代码运行缓慢

import time

# 性能分析
start = time.time()
result = slow_function(data)
end = time.time()
print(f"执行时间: {end - start:.2f} 秒")

# 使用 cProfile
import cProfile
cProfile.run('slow_function(data)')

# 使用 line_profiler
# pip install line_profiler
# kernprof -l -v script.py
@profile
def slow_function(data):
    # 代码
    pass

调试工具箱

Python 调试工具

# 1. pdb - Python调试器
python -m pdb script.py

# 2. ipdb - IPython调试器
pip install ipdb
ipdb.set_trace()

# 3. pudb - 全屏调试器
pip install pudb
pudb.set_trace()

# 4. 性能分析
python -m cProfile -o output.prof script.py
python -m pstats output.prof

# 5. 内存分析
pip install memory_profiler
python -m memory_profiler script.py

Jupyter Notebook 调试

# 魔法命令
%pdb  # 自动进入调试器
%debug  # 在异常后进入调试器
%time statement  # 计时单个语句
%timeit statement  # 多次运行并计时
%lprun -f func func(args)  # 行级性能分析
%memit statement  # 内存使用

R 调试工具

# 1. 基本调试
debug(function_name)  # 启用调试
undebug(function_name)  # 禁用调试

# 2. 浏览器调试
browser()  # 在代码中设置断点

# 3. 追踪
traceback()  # 显示调用栈

# 4. 条件断点
if (x > 100) browser()

# 5. 性能分析
Rprof("output.out")
# ... 运行代码 ...
Rprof(NULL)
summaryRprof("output.out")

调试检查清单

🔍 开始调试前

  • 保存当前工作(Git commit)
  • 阅读错误消息完整内容
  • 确认问题可重现
  • 创建问题的最小示例
  • 检查最近的代码变更

🔎 调试过程中

  • 使用系统化方法(四阶段流程)
  • 记录假设和验证结果
  • 避免同时修改多处
  • 每次只测试一个假设
  • 保持调试日志

✅ 修复后

  • 问题确实解决(不是表面消失)
  • 添加了测试用例
  • 所有测试通过
  • 代码审查通过
  • 记录了根本原因和修复方法

参考资源

Skills enregistrées