我把 bug 修得太干净了,测试组以为这个模块本来就没问题

有些 bug,修得太干净,反而没人知道它存在过。这大概是程序员最孤独的浪漫。

一、事情的起因:一个"看起来没问题"的模块

公司有个核心订单模块,代码历史可以追溯到 2018 年。五年间,前后端加起来换了 8 拨人,注释里还留着初代架构师的 QQ 号。

这个模块有个特点:看起来一切正常

  • 线上没出过 P0 故障
  • 测试用例通过率 100%
  • 产品经理三年没提过需求变更

但老程序员都知道, "看起来正常"往往是最危险的信号

我接手的时候,代码里充斥着这样的"艺术":

JavaScript

scss 复制代码
// 不要删,删了会崩,原因未知
setTimeout(() => {
  if (window.__magic_flag) {
    // 2021.3.15 临时修复,待验证
    fixSomething();
  }
}, 1000);

__magic_flag 是什么?不知道。谁加的?Git blame 显示是一个已注销的账号。fixSomething 干了什么?注释写着"待验证"------已经待了三年了。

二、我决定"考古"

周末我花了两天时间,用 Chrome DevTools 的 Performance 面板 + 手动断点,终于还原了这个模块的"黑历史":

第一层:定时器地狱

那个 setTimeout 是为了解决一个 race condition。2019 年某个版本,后端接口响应变慢,前端为了"兜底",加了个 1 秒延迟。后来接口好了,定时器没删,反而被后续代码依赖上了。

第二层:全局变量瘟疫

__magic_flag 是另一个同事加的,用来标记"是否已经修复过"。但问题是:如果用户快速切换页面,flag 还没重置,修复逻辑就跳过了。

第三层:沉默的灾难

最可怕的是,这个 bug 几乎不会触发。只有在网络抖动 + 用户手速极快 + 特定浏览器版本的叠加态下才会出现。过去五年,线上大概出现了 12 次,每次都被客服用"刷新一下就好了"敷衍过去。

没人报 bug,因为用户刷新后确实好了。测试没发现,因为自动化测试不会"快速切换页面"。

这个模块就像一个埋了五年的地雷,上面还铺了地毯。

三、修复方案:不是打补丁,是拆雷

我没有选择"再加一个 if 判断"这种祖传手艺,而是做了三件事:

1. 用 Promise 链替代定时器

JavaScript

scss 复制代码
// 之前
setTimeout(() => {
  if (window.__magic_flag) fixSomething();
}, 1000);

// 之后
await ensureOrderDataReady();
// 明确的数据就绪信号,不再需要魔法数字

2. 引入状态机管理页面生命周期

TypeScript

ini 复制代码
type OrderPageState = 'idle' | 'loading' | 'ready' | 'error';

const [pageState, setPageState] = useState<OrderPageState>('idle');

全局变量 __magic_flag 被彻底移除。页面切换时,状态自动重置,不存在"脏状态"残留。

3. 补了 47 个单元测试

覆盖了所有边界条件:网络超时、快速切换、接口返回异常结构、用户断网重连......

@testing-library/react 模拟了用户"疯狂点击"的场景,确保状态机不会崩溃。

四、最荒诞的结果

周一我提交 PR,代码审查很顺利。 senior 看了眼 diff,说了句:"就重构了一下?没加新功能?"

我说:"修了几个隐藏 bug。"

他问:"有工单吗?"

我说:"没有,是我周末顺手看的。"

他点点头,合并了。

两周后,更魔幻的事情发生了:

测试组的小妹妹跑来问我:"哥,你那个订单模块的用例能不能分享一下?我们组最近在整理最佳实践,发现你们那个模块特别稳定,三年没出过线上问题,想学习一下。"

我愣了一下,说:"哦......那个啊,确实挺稳定的。"

她认真地点头:"嗯,我们领导说这种'原生稳定'的模块最有参考价值,不像有些模块,天天修 bug。"

我张了张嘴,最终没告诉她真相。

五、程序员的"修 bug 悖论"

这件事让我想明白了一个道理:

在软件工程里,bug 的可见度和它的危险程度,往往是成反比的。

  • 那些天天报错的模块,大家抢着修,反而很健康。
  • 那些"从来没问题"的模块,可能藏着最可怕的债务。

而当你把后者真正修好,没有人会感谢你------因为他们根本不知道原来有问题。

测试组以为这个模块"本来就没问题"。 领导以为我只是"优化了一下代码结构"。 甚至三个月后的我自己,如果不看 Git 历史,也会以为这模块天生就这么干净。

六、唯一的证据

唯一知道这个秘密的,是 Git 里那条 commit message:

plain

diff 复制代码
refactor(order): 移除魔法定时器与全局状态标记

- 用 Promise 替代 setTimeout 轮询
- 引入 OrderPageState 状态机
- 补充边界条件测试 47 例
- 移除 __magic_flag(2019 年遗留)

BREAKING CHANGE: 无,行为保持一致

以及我本地那份 3000 字的"考古笔记",记录了 __magic_flag 的前世今生。

七、写在最后

有人说,程序员最好的作品是"没人注意到的代码"------它稳定运行,不报错,不卡顿,不需要解释。

但我想说,程序员最孤独的作品,是"没人知道它曾经坏过"的修复。

你花了两个周末,拆掉了一颗五年的地雷,铺上了真正坚固的地基。然后看着测试组把它当成"原生优秀案例"写进最佳实践文档。

这算不算一种成功?

算吧。只是有点寂寞。


如果你也拆过这样的雷,欢迎在评论区留下你的故事。至少在这里,有人懂。

👉纸飞机自己上,加八六也行典喔👈

相关推荐
Null1551 小时前
浏览器唤起桌面端应用(终极篇)
前端·javascript
Quz1 小时前
QML Label 完整用法与 Text 组件选型
前端·qt
妙码生花1 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(三十四):后台初始化请求实现
前端·javascript·go
天若有情6732 小时前
前端性能优化:从“按下预加载”到“AI级智能预判系统”
前端·性能优化·js·active·hover·首屏加载
JackieZhengChina2 小时前
零依赖轻量级JS悬浮提示插件title2tip-js的使用教程
开发语言·javascript·ecmascript
Csvn2 小时前
📦 CSS Container Queries 实战踩坑:5 个让你怀疑人生的隐藏陷阱
前端
大家的林语冰2 小时前
👍 Rust 为 Node 插上翅膀,反超 Bun 和 pnpm,一体化工具我也有!
前端·javascript·node.js
退休倒计时2 小时前
【每日一题】LeetCode 39. 组合总和 TypeScript
算法·leetcode·typescript
坐而论道_2 小时前
兼容低版本 Android:让现代 Web 应用在旧 WebView 上稳定运行
前端·面试