跳转到内容
幸运的蜗牛 Logo 幸运的蜗牛
EN

和AI一起修复了上百个bug后,我将其沉淀为了一套 Skills

/ 17 分钟阅读 /

大家好,我是幸运的蜗牛,今天想分享一个非常实用的 skills,它能够帮你在网页开发过程中遇到 bug 的情况下,快速定位到 bug 的真正原因,并且修复,最后产出 bug 修复文档进行沉淀。这是一个非常实用也非常重要的 skills。

我自己在 AI vibe coding 的过程中,百分之七八十的时间,其实都是在解决 AI 没有完全理解需求,或者说是因为它过时的训练数据,或者说是误导的上下文等等各种原因所产生的问题。一句话说,就是在修复 bug。很多时候我都觉得还不如我自己 debug,可能来得更快一点。

最终在我快受不了的时候,我认真观察了 AI 修复不好 bug 的原因,发现很多时候它都是因为缺少上下文,或者说是因为把假设当成了事实。于是我基于不断地修复 bug,沉淀了一个 skills:bug-hunt。简单来说,它就是帮我把「AI 凭感觉乱改」这件事,硬掰成「先拿到证据,再动一行代码」的固定流程。

先说说,AI 为什么修 bug 这么蠢?

如果你经常用 AI 写代码,下面这几个场景应该不陌生:

  • 理解错误导致乱改:你说这个按钮点了没反应,AI 直接你改了三个文件,结果按钮还是没反应,但是顺手又带出来两个新 bug。
  • 喜欢拿一个错误的假设当事实:比如我最近让 AI 帮我改元素的定位问题,它默认了 fixed 定位相对于是 body,但实际上并不是
  • 爱兜底:很多时候让AI改东西,它其实并没有找到问题的真正原因。但是呢,它又想要把这件事情解决掉,它就根据我们的提示词,然后进行添加兜底逻辑。这种是最可怕的,因为你测试的时候会发现它好了,但其实它只是接入了这个特殊的 case
  • 喜欢顺手做不需要的事情:很多时候,AI很讨厌人的一点就是,它会在修复bug的时候,它说它自己发现了一个问题,然后顺手也修复了。

这些行为背后其实是同一个病根:AI 在没有任何证据的情况下,就开始猜原因、改代码了。 它把「我觉得可能是 X」直接当成了「就是 X」,然后基于这个假设一路改下去。更糟的是,它的知识很多来自训练数据里那些早就过时的博客、CSDN 上互相抄来抄去的错误答案——你让它查个库的行为,它给你的是三年前的旧版本逻辑。

但是我们是怎么 debug 的呢?先复现,再加日志,看到真实的运行数据,才下结论。bug-hunt 干的事情,就是把这套逻辑,把它写成一份 AI 必须遵守的契约。

bug-hunt 的核心:五个步骤

bug-hunt 的整个方法论可以浓缩成五个阶段,AI 必须按顺序走,如果后面的证据推翻了前面的结论,就退回去重来,绝不允许直接跳到「我来改代码」

Phase 0 — 复现与框定。 第一件事不是改代码,是先稳定复现,并且定一个可验证的成功标准。注意这里有个关键区别:成功标准不能是「不卡了」这种模糊的话,而必须是「页面加载 5 秒内 spinner 消失,并且价格区域渲染出一个大于 0 的数字」。为什么这么较真?因为如果你只盯着「不卡了」,AI 很容易在第一个症状缓解的瞬间就宣布胜利——spinner 是没了,但数字是错的,你反而上线了一个更隐蔽的 bug。

Phase 1 — 自顶向下定位。 从你看到症状的那一行 UI 开始,往上游一层一层追:这个值是谁写进去的?它的上一层又是从哪来的?每一层都记下来 文件:行号,最后形成一张「分层表」,并产出几个有名字的假设(H1、H2、H3)。追到网络边界、原始用户输入,或者能说出一个具体猜测为止。这一步实现通过数据&逻辑链路逐步的定位到问题的真正原因。

Phase 2 — 全链路埋点。 这是整个方法论最硬核的一步。AI 要在每一层都打上结构化日志,而且全程只用一个唯一的日志前缀(比如 [YUGU_DEBUG]),方便最后一把清干净。日志不是简单 console.log(变量),而是用 JSON.stringify(对象, null, 2) 输出结构化数据,里面要包含:

  • 身份信息:哪个调用点、第几次循环、第几个元素;
  • 哨兵值:那个决定下游走向的布尔判断,比如 willTriggerLoading: x === 0
  • 数据形状Object.keys()lengthtypeof,而不只是值本身;
  • 首个样本arr[0],确认元素结构但不刷屏。

还有几个很实用的小技巧:打 error 永远不要直接 log 那个 Error 对象(JSON.stringify(err) 会给你一个空的 "{}"),要拆成 { message, stack, raw };如果某个 await 一直不返回,就在它前后各打一行日志,最后一行打印出来的位置就是卡住的地方。埋好点之后,AI 必须停下来,让你去复现、把控制台日志贴回来,在拿到真实输出之前不许再改任何代码。

Phase 3 — 分层根因。 高质量的排查往往不止一层原因,而是一条因果链:症状 → 直接原因 → 深层原因 → 根本原因。怎么判断你还没挖到根?很简单——如果「为什么会这样」这个问题还能有意义地往下问,那就还没到底。这一阶段还有一条特别重要的规则,叫信源分级,下面单独讲,因为它正是治 AI「把过时博客当事实」这个病的关键。

Phase 4 — 外科手术式修复。 找到根因后,只改能修复这个已被证明的原因的最小代码集。不重构、不顺手改命名、不删无关的死代码。改完之后,带着 Phase 2 的日志重新跑一遍,确认每个原来报错的日志现在都输出了预期值、哨兵布尔值翻转了、其他没受影响的路径依然正常。这里还专门有一条反面清单——下面这些都不算修复

  • value || 0 掩盖 undefined——只是把 bug 推到下游;
  • try { ... } catch {}——藏起了下一个 bug;
  • setTimeout(check, 100)——你根本没解决竞态;
  • // HACK: ...——在代码里写下「我投降了」。

只要你发现 AI 想写上面任何一种,让它退回 Phase 1。

Phase 5 — 文档沉淀 + 清理。 排查不写文档不算结束。AI 要按模板在 docs/ 下生成一份 bugfix-<slug>.md,包含:现象、根因链、排查过程(Phase 1 的分层表 + Phase 2 的关键日志)、修复 diff、前后对比验证表、改动文件清单、后续建议。最后用一行 grep 把所有调试日志清干净——期望结果是 0 条命中,泄漏到生产环境的调试日志本身就是 bug。

解决 AI 把错误信息当事实:信源分级

我前面说 AI 修 bug 蠢,很大一部分是因为它「把假设当事实」,而这些假设又常常来自互联网上的垃圾信息。bug-hunt 在 Phase 3 里定了一套严格的信源优先级,强制 AI 只能相信高质量的来源:

  • 第一档(永远先查):官方文档、库的 GitHub 源码本身、官方 changelog / 迁移指南、类型定义文件、官方规范(RFC / W3C)。
  • 第二档:维护者自己仓库里的 GitHub issues 和 discussions,尤其是那些关联了 commit / PR 的 closed issue——里面往往有对某个行为变化的权威解释。
  • 第三档(前两档都查不到才用):库作者或平台官方的工程博客、被维护者认可或高赞且引用了一二档来源的 Stack Overflow 答案。

而下面这些是明令禁止的:CSDN、低质量博客园镜像、百度知道 / 百度经验、360doc、各种内容农场聚合站、机翻文章、没作者没日期的 AI SEO 文。原因很现实:这些来源经常一本正经地把原因归错,AI「拿它验证」了自己的假设,然后给你上线一个修错了层的 fix。最后,所有参考过的外部链接都要写进 bug 报告里,让整条证据链可审计。

说人话就是:让 AI 去读库的真实源码,而不是去信某篇三年前的博客。

一个真实案例:YouTube 报价卡永久 loading

光讲方法太抽象,我用我在排查显示问题的真实案例带作为示例。

症状:浏览器插件里,YouTube 博主信息卡的「预估报价」区域永久转圈,但同样的代码在 TikTok / Instagram 上完全正常。

Phase 0 定下成功标准:spinner 5 秒内消失、渲染出金额、而且金额量级要对(一个中位播放量 2.75 万的博主,应该显示四位数美元,而不是「30」)。最后这条「量级要对」,后面救了我一命。

Phase 1 顺着 spinner 往上追,发现 isLoading = medianViews === 0,于是问题变成「为什么 medianViews 是 0」,一路追到调用 youtubei.js 这个库的地方。

Phase 2[YUGU_DEBUG] 前缀全链路埋点,在每个 await 前后打日志。

Phase 3 这才是精彩的地方,整整挖出四层因果链:

  1. 日志显示 query 其实正常 resolve 了,但 videos 数组是空的;
  2. 把响应的 memo 打出来一看,30 个视频明明在里面,但被归类成了 LockupView——YouTube 新换的一种容器类型,而我装的 youtubei.js@10.3.0.videos getter 根本不认它;
  3. 升级到 v17 后,翻看库的源码类型定义发现,Feed.videos 的联合类型里依然漏了 LockupView——这一步如果去信博客,永远查不出来,因为所有博客描述的都是旧的响应结构;
  4. 绕过 getter 直接读出视频后,数量对了,但中位播放量算出来是 30(应该是 2.7 万)。原来 LockupView 的播放量文本是 UI 字符串 "4.6K views",而下游的 extractCountreplace(/[^0-9]/g, '') 把所有非数字都删了,"4.6K" 直接变成了 46,那个代表「千」的 K 被吃掉了。

完整的因果链长这样:

YouTube 迁移到 lockupViewModel
└─ youtubei.js@10.3.0 不解析它 → videos: []
└─ 升到 v17,但 Feed.videos 联合类型仍漏了 LockupView → 还是 []
└─ 从 memo 取出的 LockupView 是 "4.6K views" UI 字符串
└─ extractCount 把 K 删掉 → 中位数缩小了 1000 倍
→ medianViews: 30 → 转圈没了但数字是错的

看到没?如果当初只盯着「spinner 没了」,到第 3 层就会收工,然后上线一个显示「$30」的离谱报价。是 Phase 0 那条「量级要对」的成功标准,逼着继续往下挖到了第 4 层。

Phase 4 最终只改了两个文件三处:升级库版本、空数组时回退到 memo.get('LockupView')、在交给下游之前先把 K/M/B 展开成完整整数。extractCount 本身一个字没动,所以 TikTok / IG 的路径完全不受影响。

Phase 5 写好修复文档,grep 确认 YUGU_DEBUG 零残留,收工。

这个案例几乎把 AI 平时会犯的错全踩了一遍:空数组就想兜底(被禁止)、信博客查旧结构(被信源分级拦下)、数字小了就乱乘(没有,因为要求每层都有证据)。

怎么用?

这个 skills 同时支持 Claude CodeOpenAI Codex CLI,一套源码不用分叉。安装很简单:

Terminal window
git clone https://github.com/coderPerseus/skills ~/code/personal/skills-luckySnail
~/code/personal/skills-luckySnail/scripts/install.sh

安装脚本会把每个 skill 软链到 ~/.claude/skills/~/.agents/skills/ 下,之后 git pull 一下就能更新,不用重装。如果只想装 bug-hunt 一个:

Terminal window
~/code/personal/skills-luckySnail/scripts/install.sh --only bug-hunt

装好之后,下次你跟 AI 说「排查一下为什么 X 不工作」「这个 bug 帮我查一下」「why is X broken」,它就会自动按这五个阶段来排查,而不是上来就乱改。

写在最后

说到底,bug-hunt 解决的不是「AI 不会写代码」,而是「AI 太自信、太爱猜」。它把 debug 这件最依赖经验和耐心的事,拆成了一条 AI 也能照着走的流水线:先复现,再定位,全链路埋点拿真实数据,分层挖到根因,最小改动验证,最后沉淀成文档。 每一步都要有证据,不许靠猜。

我自己用下来,最大的变化是——AI 不再「假装」修好了。它会老老实实让我贴日志,会承认「现在还没到根因」,会拒绝写那种掩盖问题的兜底代码。这种「慢」,其实比之前那种「快速乱改再快速制造新 bug」要快得多。

仓库地址在这里,欢迎试用、 star 一下: https://github.com/coderPerseus/skills

如果这篇对你有帮助,记得点个赞、加个关注。我是幸运的蜗牛,我们下期见~

图片预览