每个迭代密集的项目都有一串事故故事。这篇挑四个最有代表性的:一个报错反转三次才找到真凶、一次发版翻车被自己痛斥、一夜之间 175 章全失败的潜伏 bug、还有那个让所有修复”看起来没生效”的缓存幽灵。比起故事本身,更值钱的是从里面沉淀出的排查方法论。
案例一:”require is not defined” 的三次反转
用户报障:项目页面刷新,弹出”请求失败:require is not defined”。
第一轮排查:搜自己的代码——整个前端 0 处 require 调用;curl 页面输出与磁盘文件 md5 一致(排除服务端半写);服务日志全是 200;开两个标签狂刷 7 次,零复现。结论:非应用代码问题,疑似工具链注入,暂时挂起。顺手加固了五个轮询定时器的错误处理(这波加固本身是对的)。
第二轮反转:用户又遇到了。这次不再猜,给全局错误处理器加上来源分析——直到用户贴出控制台完整堆栈,首行直接指向他浏览器里的油猴脚本。真凶是一个屏蔽广告的 userscript 在页面上执行失败,被我的全局 unhandledrejection 处理器捕获,套上”请求失败”的 UI 外壳展示出来。
教训是双向的:一方面应用不该把来历不明的错误冒充成自己的(后来加了 _foreignErr 过滤:错误堆栈命中 userscript/extension/content-script 特征的直接静默);另一方面**”无法复现”不等于”不存在”**,复现不了的时候,去拿一手证据(堆栈),别停在推理。
这个案例还有个哲学尾巴:全局错误提示框是双刃剑——它让你看得见所有错误,也让你背上了所有别人的错误。
案例二:v2.0.0 全站裸文本事故
前端从原生 JS 重构成 Vue3+Vuetify,发版即翻车:整个页面渲染成一坨没有样式的裸文本,所有 v-app/v-btn 全部以原始自定义元素的形式裸奔。用户一句话定性:”这能看吗”。
根因极其隐蔽:实际安装的是 vuetify 4.1.11 而不是文档以为的 3.x——v4 的根包 createVuetify() 返回的实例 components=0,不再自动注册组件。而发版前的验证只做了”DOM 结构存在 + 路由 200”,没做任何视觉验证。组件注册失败时 DOM 里照样有 <v-app> 标签——结构在,渲染死。
修复本身一行(显式 import 全部 components/directives 传入),但它改变了我的发版纪律:
- 发版验证必须包含视觉证据(截图或真实渲染断言),DOM 存在性检查会漏掉”组件注册失败”这类渲染级事故
- 锁死依赖大版本,
package.json里写死的版本号要和文档假设对得上——“我以为我在用 3”本身就是事故
顺带一提,修复上线后部分用户仍看到白屏,那是 PWA 的 Service Worker 在喂旧缓存的预缓存包(读 script src 的哈希就能确认)——同一个症状(页面坏了),两个不同病因(代码 bug + 缓存幽灵),修复后仍有残留症状时,先验证用户拿到的是不是新版。
案例三:夜间 175 章全失败的潜伏 bug
夜间挂机批量写作,第二天早上发现 26-200 章全部失败,175 章无一幸免。烧钱了吗?没有——全部失败在 LLM 调用之前,一分钱额度没浪费。这是这个 bug 唯一的温柔。
根因:pipeline.py 里缺一个 import json。这个 NameError 只在”启用了分层大纲 + 跨弧展开”的路径上触发——用户项目前 25 章(单弧)跑得好好的,第 26 章进入跨弧路径的瞬间集体爆炸。
更阴的是第二个 bug 在它背后:修复后继续生成,系统报”全部已定稿”拒绝工作——滚动窗口的 hi 指针被 len(bp) 封顶了(bp 只有前 25 章),它真心认为全书 25 章就是全部。两个 bug 叠加,一个让你做不了,一个让你以为做完了。
沉淀的方法论:潜伏 bug 集中在”多层特性叠加路径”。单特性测试(25 章单弧)全绿,组合路径(layers × rolling × review)才是事故温床。此后的 E2E 加了”30 章跨窗口全定稿”的组合路径用例,专测特性交界处。
案例四:缓存幽灵——所有修复的”看起来没生效”
贯穿整个项目周期的元问题:修了 bug,用户说没修好。排查无数次,最终锤实三个来源:
- 浏览器缓存旧页面。根治方案狠一点:
/路由直接返回Cache-Control: no-store——本地工具不需要缓存首页,此后普通刷新必是最新版 - PWA 预缓存喂旧包。上面说过,读 script 哈希即知
- 服务端配置缓存。改了 settings.json 文件但服务进程内存里是旧的(这个还引发过一次事故:测试时以为切了 mock,实际服务还在用真实模型跑了 10 次调用——从此立下纪律:运行时配置一律走 API 改,不直接改文件)
三层缓存各有各的解法,共同的原则是:“用户看到的”和”你部署的”之间隔着整个缓存栈。排查任何”没生效”问题的第一步永远是验证两端版本一致(页面 script 哈希、/api/version、配置回读),而不是重新检查代码。
排查方法论沉淀
四个案例汇成一张流程图:
报错 → 先分层:是应用代码、依赖、运行环境,还是用户环境? |
还有一条软技能:给用户的每个报障都保留完整时间线。这个项目的开发日志里,每轮修复都记”用户原话 → 根因 → 修复 → 验证”,事后写这篇博客时,细节全部可回溯——好的事故记录是未来的内容资产。
踩坑小结
- 全局错误捕获要区分敌我:你的错误你处理,别人的错误(插件/脚本)静默放行
- 发版验证必须含视觉证据,DOM 存在性检查漏掉渲染级事故
- 多层特性的组合路径是潜伏 bug 温床,E2E 要覆盖交界处
- “没生效”九成是缓存栈里某一层,先验版本一致再看代码
- 运行时配置走 API 不走文件——服务端缓存比浏览器缓存更隐蔽
下一篇换个视角:这个项目后期最有趣的工作模式——多个 AI 分工协作,人类当调度器。