Skip to content

Latest commit

 

History

History
187 lines (150 loc) · 6.68 KB

File metadata and controls

187 lines (150 loc) · 6.68 KB

Debug 标准操作流程 (SOP)

目的

提供系统性的问题排查方法,避免"随机试错"式 debug,快速定位根因并修复,同时将经验沉淀为知识。

适用时机

  • 程序报错/崩溃
  • 功能行为不符合预期
  • 性能突然下降
  • 环境相关问题(本地正常/生产异常)
  • 间歇性/难以复现的问题

流程步骤

第一步:问题分类

先判断问题类型,选择对应排查路径:

类型 特征 典型原因
崩溃/异常 程序直接报错退出 空指针、类型错误、未处理异常
逻辑错误 运行正常但结果不对 条件判断错、边界遗漏、算法错误
性能问题 慢、卡、超时 N+1 查询、内存泄漏、死循环
环境问题 本地好/线上坏 配置差异、版本不同、权限问题
并发问题 时好时坏 竞态条件、死锁、资源争用
集成问题 调用外部服务出错 网络、认证、格式不匹配

第二步:复现问题

复现是 debug 的前提,不能复现就不能确认修复。

  1. 记录错误信息(完整堆栈/日志/截图)
  2. 确定复现步骤:
    • 什么操作触发的?
    • 需要什么前置数据/状态?
    • 是否 100% 复现?还是概率性?
  3. 尝试在本地复现
  4. 如果无法复现:
    • 增加日志/监控
    • 检查环境差异
    • 查看是否是数据相关问题

复现记录格式:

环境:[OS/浏览器/Node版本/...]
步骤:1. ... 2. ... 3. ...
期望:[应该发生什么]
实际:[实际发生了什么]
频率:[必现 / 偶现(约X次中Y次)]

第三步:隔离问题

缩小范围,确定问题在哪一层:

前端 UI → API 请求 → 后端逻辑 → 数据库 → 外部服务

隔离方法:

  • 二分法:在中间点检查,确定问题在前半段还是后半段
  • 最小复现:剥离无关代码,构建最小复现用例
  • 替换法:用已知正常的组件替换怀疑组件
  • 对比法:对比正常情况和异常情况的差异

检查点:

  • 请求是否正确发出?(Network tab)
  • 后端是否收到请求?(日志)
  • 逻辑处理是否正确?(断点/日志)
  • 数据库查询是否正确?(SQL 日志)
  • 响应是否正确返回?(Response)

第四步:定位根因

常用定位手段:

手段 适用场景
断点调试 逻辑错误,需要逐步跟踪
日志输出 生产环境、异步流程
堆栈追踪 异常/崩溃
网络面板 前后端交互问题
数据库日志 查询性能/结果问题
Git bisect 回归问题(哪个提交引入的)
性能分析器 性能瓶颈

Git bisect(定位引入问题的提交):

git bisect start
git bisect bad          # 当前版本有 bug
git bisect good v1.0.0  # 这个版本是好的
# Git 会自动 checkout 中间提交,你测试后标记 good/bad
git bisect reset        # 结束

提问框架(5 Whys):

为什么报错?→ 因为 user 是 null
为什么 user 是 null?→ 因为查询没找到记录
为什么没找到?→ 因为 user_id 传了错误的值
为什么值错误?→ 因为前端传了 string 而非 number
为什么类型不对?→ 因为 API 没有做类型验证
→ 根因:API 缺少输入验证

第五步:修复

修复原则:

  1. 修复根因,不是症状(不要只处理报错,要解决根本问题)
  2. 最小改动原则(不要顺手重构)
  3. 先写失败测试,再修复(TDD 思路)
  4. 考虑修复是否引入新问题

修复检查:

  • 修复是否针对根因?
  • 是否有更多类似问题需要一并修复?
  • 是否需要添加防御性代码?
  • 是否需要补充测试?

第六步:验证

  1. 原始复现步骤不再触发问题
  2. 相关功能回归测试通过
  3. 边界条件测试通过
  4. 如果是性能问题,确认指标恢复
  5. 全量测试套件通过

第七步:记录与沉淀

问题记录格式:

## [问题标题]
- 日期:
- 影响范围:
- 根因:
- 修复方案:
- 耗时:
- 教训:[下次如何避免/更快定位]
- 相关提交:[commit hash]

检查清单

  • 问题已分类(崩溃/逻辑/性能/环境/并发)
  • 已记录完整错误信息和复现步骤
  • 问题已在本地复现(或有明确复现路径)
  • 已隔离到具体层/模块
  • 根因已确定(不是只修症状)
  • 修复前有对应的失败测试
  • 修复后原始问题不再复现
  • 回归测试通过
  • 问题已记录(根因/修复/教训)
  • 必要时补充了防御性代码/测试

输出物

输出物 格式 存放位置
Bug 修复代码 提交 fix/ 分支
回归测试 测试代码 tests/
问题记录 Markdown docs/issues/ 或知识库

常见误区

误区 正确做法
不复现就开始改 先复现,确认问题存在
随机改代码碰运气 系统性隔离和定位
只修症状不修根因 用 5 Whys 追到根本原因
修完不测试 必须有验证步骤
修完就忘了 记录下来,避免重复踩坑
一次改太多 最小改动,一次修一个问题
忽略"偶现"问题 偶现往往是并发/竞态,更严重
根据上游 issue 的相似结论直接下结论 那只是候选假设;按其修改后仍失败就应立即放弃并回到堆栈证据,同时清除被证伪的修改与其误导性注释
性能问题只测自己的服务就断定服务端慢 必须做对照实验(同一客户端测一个已知高速的第三方源)以排除客户端/链路因素;对照时注意重定向(curl 需 -L)否则只测到几十字节的 301/302 响应,得出虚假数字
用探测请求查看缓存命中状态并据此下结论 探测本身可能改变被观测对象(如小 Range 请求会使该分片被缓存,之后总显示 HIT);应以终端指标(如大范围实际下载速率)作为判据
报错信息在代码库零匹配仍继续深挖代码逻辑 先核对运行的构建与源码是否同版本(版本号、构建产物、UI 形态);源码中不存在的错误串只可能来自旧构建,可沉淀为知识卡(见 knowledge-card-template)

相关文档