外观
一次真实的重构:从 3000 行到 800 行
约 1371 字
一次真实的重构:从 3000 行到 800 行
讲一个我经手的真实重构(细节脱敏)。主角是个 订单模块:三年历史、五个作者、3000 行的 order.service.js。最后变成 800 行 + 五个清晰的小模块。 过程没有神话,全是土办法。
一、动机:三次「改到崩溃」
引爆点是三次事故:
第一次:加「部分退款」,改了状态机,
结果「全额退款」的分支被影响,线上错退 3 单
第二次:改金额计算(加优惠券),
改了 A 处忘了 B 处,两处逻辑不一致
第三次:性能优化(查订单列表),
发现一个函数里嵌了 6 层循环 + 嵌套查询每次都是「就改一小处」的诉求,每次都引出大坑。 团队共识:不是人不行,是这块代码到了 「改不动」的临界点。
二、动手前:两件事
1. 补测试( Fowler 的规矩)
先给核心路径补测试(不追求全覆盖):
- 下单(各种商品类型组合);
- 状态流转(每个状态 + 每个转移);
- 金额计算(优惠券/折扣/运费组合)。
花了一周,写了几十个测试。 期间就发现 两个「现有行为和文档不一致」的 bug —— 这说明测试开始值回票价了。
2. 画现状(理解先于动手)
把 3000 行读一遍,画调用图:
入口(3 个 API)→ 校验 → 计价 → 落库 → 通知
发现:
- 「校验」逻辑散落在 5 个函数里,互相调用
- 「计价」在一个 400 行的函数里,
优惠券/活动/运费三种逻辑缠在一起
- 「通知」同步发送,拖慢主流程画完图,重构方案自然浮现 —— 不是设计出来的, 是「看清了之后」知道该往哪去。
三、重构:小步走,六次 PR
没搞「重构大月」,分六个 PR,每个可独立上线:
PR 1:抽取「校验」模块(纯搬砖,不改逻辑)
测试全绿 → 合
PR 2:抽取「计价」模块,拆三个子函数
测试全绿 → 合
PR 3:修 PR 2 中暴露的「两处逻辑不一致」bug
加测试 → 合
PR 4:通知改异步(队列)
测试 + 观察 → 合
PR 5:删死代码(300 行没人调的)
测试全绿 → 合
PR 6:状态机收敛成一个表驱动
测试全绿 → 合关键纪律:
- 每个 PR 只做一类事(「抽取」和「改行为」 绝不混在一个 PR —— 混了,出问题说不清是谁的锅);
- 每个 PR 都跑全部测试(绿的才合);
- 每个 PR 都可回滚(出问题回一个,不用回六个);
- PR 之间间隔几天(观察线上,别连发)。
四、踩的坑
坑一:「顺手优化」 PR 2 里我顺手把一个循环改成了 map/filter, 看着优雅 —— 但行为和原来不一致 (原来会跳过 null,我的不会)。测试抓住了, 但我花了半小时才想明白「为什么原来的行为 其实是故意的」(兼容脏数据)。 教训:重构 PR 里,优雅感是敌人。
坑二:删了「没人调」的代码 PR 5 删函数时,全局搜没找到调用 —— 删了。 上线一周后,一个定时任务挂了: 那个函数是字符串反射调用的 (global[funcName]()),静态搜索看不见。 教训:「没人调」要加限定 —— 静态搜索没人调。 (后来靠 log 监控才发现。)
坑三:低估联调成本 以为「行为不变」的纯重构,前端不用动。 结果有个字段的顺序变了(对象 key 顺序), 某处前端依赖了这个顺序渲染。测试没覆盖 前端渲染,它不知道。教训:重构的影响面, 不止测试覆盖的那部分。
五、结果与复盘
代码:3000 行 → 800 行(+ 600 行测试)
性能:订单列表 P99 从 2.3s → 180ms(异步化 + 循环优化)
后续:加新状态从「半天+提心吊胆」→「一小时+有测试兜底」
Bug:上线后一个月,订单模块 0 事故事后复盘的三个「下次还会做」:
- 先补测试再动手 —— 没测试的重构是裸奔, 这次没它,PR 2 就翻车了;
- 小 PR + 可回滚 —— 六个小 PR 的风险 远小于一个大 PR,这是纪律不是风格;
- 画图 + 搜索 + log 三重确认「没人调」 —— 反射/动态调用是静态分析的盲区。
一个「下次要改」:联调(前端+后端+定时任务) 应该更早介入 —— 我在最后一周才发现前端依赖, 应该在 PR 1 之后就同步各方。
六、写给要重构的你
如果看完心动了,先记住三句话:
- 「代码跑着就别动」和「天天改到崩溃」之间, 隔着的是测试和小步;
- 重构不是重写 —— 我没推翻任何设计, 只是「把缠在一起的拆开」。推翻重来的 大概率是自嗨;
- 重构的价值不在代码行数(3000→800 只是结果), 在「以后敢改」 —— 这个敢字, 是给团队的定心丸。
小结
一次重构的实录:动机(三次事故)→ 补测试 → 画图 → 六个小 PR → 三个坑 → 复盘。 没有银弹,全是纪律。想系统学手法, 看 《重构》读书笔记; 想动手了,回来看看这篇的「纪律清单」。