Debug Root Cause

排错纪律。报错、测试失败、行为不符预期、偶发问题时使用;先复现再定位,找到根因再修,不猜、不连试三次、不用 retry / try-catch / 重启掩盖;修完说明根因在哪一层、为什么之前没拦住。

RedGranite c0cfd52 2.5 KB Updated

File contents

debug-root-cause:先复现,再动手

何时用

任何"不对劲":报错、测试红、结果不符、偶尔失败。

硬规则

  • MUST 先复现:拿到能稳定触发的最小步骤或输入;复现不了就先说复现不了,不修。
  • MUST 定位到具体位置(文件、函数、哪一步的状态不对)再改代码;定位靠读代码、加日志、缩小范围,不靠改一处试一次。
  • NEVER 连续三次"改了试试";第二次不中就停下重新定位。
  • MUST 区分根因与症状:修的是根因;只能先止血时明说"这是止血,根因是 X,待修"。
  • NEVER 用 retry、try/catch 吞异常、sleep、重启、清缓存当作修复;它们掩盖竞态与状态问题(见 concurrency-modeling)。
  • MUST 修完加回归检查:一条测试或一条可重跑的验证命令,保证同样的错再出现时能被抓住。
  • MUST 修完说明根因与所在层(输入校验 / 状态 / 并发 / 依赖 / 环境);之前有检查却没拦住的,说明为什么;需要补守卫的,说补什么(见 guardrails)。

审问清单

  1. 我能让它稳定再错一次吗?
  2. 出错时哪个变量、哪个状态和预期不一样?在哪一行第一次不一样?
  3. 我现在这个改法,是知道为什么,还是在试?
  4. 修完了,同样的错还能不能悄悄回来?
  5. 这个 bug 该在哪一层被拦住?

反模式

  • 错误:报 KeyError: 'user_id',加 .get('user_id', None)。→ 正确:查为什么这条请求没有 user_id——上游哪一步漏了;漏的地方修,入口加校验。
  • 错误:偶发 500,加 retry(3)。→ 正确:抓到失败时的输入和状态,多半是并发或脏数据,按根因建模。
  • 错误:改了五处,最后不知道哪处起作用。→ 正确:一次改一处,不中就回退。
  • 错误:"修好了。" → 正确:"根因:订单状态在支付回调前被定时任务改成 expired;层:并发;补了状态机转移约束和回归测试 test_callback_after_expire。"

输出要求

定位阶段:复现步骤 + 首次异常位置。修复后:根因一句、所在层、回归检查命令;为什么没拦住与补什么守卫,仅在适用时写。

RedGranite/smartskill/tree/main/skills/coding/debug-root-cause commit c0cfd528cd

Frequently asked questions

npx skillmds@latest add redgranite/debug-root-cause