中文 —
bugfix-protocol官方中文版本。
Bugfix Protocol: 系统的 6 阶段 Debugging 协议
结构化的 Bug 处理方法 — 从症状分析到验证。 防止盲目的试错,确保修复方案可持续。
概述与目的
| 阶段 | 名称 | 目标 | 最长时间 |
|---|---|---|---|
| 1 | 快速检查 | 排除明显原因 | 2 分钟 |
| 2 | 诊断 | 定位根本原因 | 10 分钟 |
| 3 | 隔离测试 | 使 Bug 可复现 | 5 分钟 |
| 4 | 修复 | 最小化修正 | 10 分钟 |
| 5 | 验证 | 验证修复 + 检查副作用 | 5 分钟 |
| 6 | 文档编写 | 保存知识经验 | 2 分钟 |
20分钟规则: 如果 20 分钟后仍无进展,请改变方法或寻求帮助。
阶段 1: 快速检查 (2 分钟)
在深入分析之前 — 检查最常见的原因:
检查清单
- 语法错误? 仔细阅读错误信息,检查对应行
- 导入错误? 模块已安装?名称正确?循环导入?
- 拼写错误? 变量/函数名称是否正确?
- 数据类型错误? 字符串误作为整型?本应为对象的地方为 None?
- 缓存过期? 删除
__pycache__,重新启动 - 环境错误? 是否激活了正确的 venv?Python 版本是否正确?
- 编码问题? UTF-8 vs. cp1252 (Windows 经典)
快速操作
# 清除缓存
find . -name "__pycache__" -type d -exec rm -rf {} + 2>&1
find . -name "*.pyc" -delete 2>&1
# 检查导入
python -c "import modulename"
# 检查语法
python -m py_compile file.py
阶段 2: 诊断 (10 分钟)
策略:由外入内 (Outside-In)
- 分析错误信息 — 从下往上阅读堆栈追踪 (traceback)
- 检查最近的修改 —
git diff,git log --oneline -10 - 使用诊断工具 — 使用项目专属的诊断工具
诊断工具 (示例)
根据项目的不同,专用的诊断脚本可能会大有帮助:
| 工具 | 用途 |
|---|---|
import_diagnose.py |
分析导入问题 |
method_analyzer.py |
检查方法签名 |
env_checker.py |
验证环境变量/路径 |
注意: 创建项目专属的诊断工具或使用现有的工具。 重要的是系统性的方法,而不是具体的工具。
调试技巧
# 1. Print 调试 (简单但高效)
print(f"DEBUG: variable={variable!r}, type={type(variable)}")
# 2. 断点 (交互式)
breakpoint() # Python 3.7+
# 3. 详细堆栈追踪
import traceback
traceback.print_exc()
# 4. 使用日志替代 print
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.debug(f"State: {state!r}")
阶段 3: 隔离测试 (5 分钟)
最小可复现示例 (MRE)
目标:用最少的代码复现 Bug。
# test_bug.py — 最小复现测试
"""
Bug: [简短描述]
Expected: [预期发生的结果]
Actual: [实际发生的结果]
"""
# 最小化设置
# ... 仅保留核心必要代码
# Bug 触发器
# ... 触发 Bug 的精确代码
# 预期结果
# assert result == expected, f"Got {result}"
隔离策略
- 新文件: 在独立文件中复现 Bug
- 移除依赖: 逐个移除依赖,直到 Bug 消失
- 二分查找: 将代码块切半,检查哪一半包含 Bug
- Git bisect:
git bisect start,git bisect bad,git bisect good <commit>
阶段 4: 修复 (10 分钟)
原则
- 最小化: 改动越少越好
- 彻底理解: 绝不盲目修复 — 必须理解为什么会发生故障
- 单件事: 每个 commit 仅包含一个修复,不要同时修复多个问题
- 向下兼容: 切勿破坏原有功能
修复模式
# 错误做法:仅处理表面症状
try:
result = broken_function()
except: # 吞掉所有异常
result = default_value
# 正确做法:修复根本原因
def broken_function():
if input_data is None: # 真正原因:缺少 None 检查
return default_value
return process(input_data)
常见修复类别
| 类别 | 典型修复方案 |
|---|---|
| None/Null | 卫语句:if x is None: return default |
| 索引错误 | 边界检查:if i < len(lst) |
| 类型错误 | 显式转换:str(x), int(x) |
| 导入错误 | 修复路径,安装包 |
| 编码问题 | 显式指定 UTF-8:encoding='utf-8' |
| 竞态条件 | 锁/互斥锁,或调整顺序 |
| 状态 Bug | 检查初始化,添加重置机制 |
阶段 5: 验证 (5 分钟)
检查清单
- Bug 已修复: 原始问题不再发生
- MRE 通过: 隔离测试顺利运行通过
- 无回归错误: 现有测试依然保持通过
- 边界情况: 已测试空输入、None、大数据量情况
- 项目工具: 检查项目工具目录中的相关测试/验证工具
测试命令
# 单元测试
python -m pytest tests/ -v
# 仅受影响的测试
python -m pytest tests/test_module.py -v -k "test_name"
# 类型检查
python -m mypy file.py
# Lint 检查
python -m flake8 file.py
阶段 6: 文档编写 (2 分钟)
Bug 报告模板
## Bug Report: [简短标题]
**Date:** YYYY-MM-DD
**Severity:** critical / high / medium / low
**Component:** [模块/文件]
### Symptom
[用户看到的现象 / 错误信息]
### Root Cause
[技术层面的根本原因]
### Fix
[修改了什么 + 为什么这样修改]
### Affected Files
- `file1.py` — [修改说明]
- `file2.py` — [修改说明]
### Prevention
[将来如何防止此类 Bug 再次发生?]
Commit 提交信息格式
fix: [修复的简短描述]
Cause: [一句话说明根本原因]
Fix: [修改了什么]
Test: [如何验证的]
PyQt6 / GUI 调试 — 常见陷阱
本节适用于使用 PyQt6/PySide6 的桌面 GUI 项目。
PyQt6 5 大常见陷阱
| 陷阱 | 问题 | 解决方案 |
|---|---|---|
| Signal-Slot 断开 | 信号已连接但处理函数未运行 | 在处理函数中加 print,检查签名 |
| 线程安全 | 从工作线程更新 GUI | 使用 QMetaObject.invokeMethod 或信号 |
| 布局层叠 | 控件不可见/位置错乱 | widget.show(),检查布局层级 |
| 事件循环阻塞 | GUI 界面冻结 | 将耗时操作移至 QThread |
| 垃圾回收 | 控件突然消失 | 将引用保存为 self.widget |
PyQt6 调试辅助函数
# 打印控件层级树
def dump_widget_tree(widget, indent=0):
print(" " * indent + f"{widget.__class__.__name__}: {widget.objectName()}")
for child in widget.findChildren(QWidget):
if child.parent() == widget:
dump_widget_tree(child, indent + 2)
# 信号调试
from PyQt6.QtCore import QObject
original_connect = QObject.connect
def debug_connect(self, *args, **kwargs):
print(f"CONNECT: {self.__class__.__name__} -> {args}")
return original_connect(self, *args, **kwargs)
快速参考
发现 BUG?
|
v
[阶段 1: 快速检查] ────── 明显原因? -> 修复
|
v
[阶段 2: 诊断] ────────── 原因明确? -> 阶段 4
|
v
[阶段 3: 隔离测试] ────── 可复现? -> 阶段 4
| |
| 不可复现?
| |
| 添加日志,
| 等待再次发生
v
[阶段 4: 修复] ─────────── 最小化 + 已理解
|
v
[阶段 5: 验证] ────────── 测试通过? -> 阶段 6
| |
| 测试失败? -> 返回阶段 4
v
[阶段 6: 文档编写] ────── Bug 报告 + commit
20分钟规则
如果你在 20 分钟后陷入困境:
- 改变方法 — 尝试不同的调试技巧
- 小黄鸭调试 — 大声解释问题(或写下来)
- 休息一下 — 离开 5 分钟,带上清醒的头脑重新开始
- 寻求帮助 — 询问同事、查阅 Stack Overflow 或官方文档
- 重置 —
git stash,完全重新开始