重写是把旧问题换成新问题,重构是把旧问题拆小。
一、他差点为一个模块停发四周
2026 年九月第二个周二,陆远打开 projects 模块的目录,光标停在 permission.ts 上。这个文件他已经看了四十分钟,仍然不敢改:3,200 行的模块里,权限、项目状态与导出逻辑缠在一起,上一次只改一个筛选条件,就连带弄坏了两个后台页面。他按每小时 20 行算了一笔账,整块重写要 160 小时,正好四周。他在日历上标了「十月停发」,准备第二天动手。
以下情境为教学假设,人物、引语和数字不代表经核实的真实案例。
停发四周等于停掉八次发布。 每周两次发布是这个产品的节奏,四周就是八次;对一个还在找留存的产品,八次发布窗口意味着八次错过用户反馈的机会。
重写的分母是整个代码库,不只是那一块。 他只想重写 3,200 行,可这 3,200 行要对接 34 张表、7 个外部服务与 344 个账号的历史数据,真正的工作量从来不在被重写的那一块里。
能分十二次做的,不要一次做完。 同样是这 3,200 行,拆成 12 次、每次 4 小时,总工时 48 小时,而且中间不停发;差别不在勤奋,在风险被切成了十二份。
二、三个判据,同时成立才考虑动
三个判据必须同时成立,才进入「考虑动」的名单;只成立一个,先记账,不动刀。判据不是用来证明该重写,而是用来排除「只是看着丑」。
| 判据 | 阈值 | 怎么量 | 只有它成立时 |
|---|---|---|---|
| 改动频率 | ≥ 2 次/月 | 近 90 天该模块的提交与工单数 ÷ 3 | 只是热点,先补测试再谈结构 |
| 缺陷密度 | ≥ 2 个/千行/年 | 近 12 个月该模块缺陷单 ÷ 行数 × 1,000 | 只是质量差,补用例比换结构便宜 |
| 理解时间 | ≥ 30 分钟才能改一处 | 最近五次改动「打开文件到首次提交」的中位数 | 只是不熟,写一篇模块导读即可 |
2 次/月是「已经被它拖住」的信号。 全库近 90 天有 38 次功能改动,平均每月 12.7 次;一个模块占到 2 次/月,等于吃掉了约六分之一的需求吞吐,你每个月都要重新进入它的上下文。
2 个/千行/年是「缺陷在复利」的信号。 3,200 行对应一年 6.4 个缺陷;低于这条线,重构省下的排查时间很难覆盖切换本身引入的风险。
30 分钟是「上下文重建成本」的信号。 打开文件到敢下第一笔超过半小时,说明你要先读完三处间接调用、两张表的关系和一个开关的默认值;这个成本每次改动都要付一遍。
三个都成立也只到「考虑」。 判据回答的是「值不值得动」,不回答「怎么动」;接下来还要过工期的账、停发的账与迁移风险的账。
三、160 小时对 48 小时
把同一个 3,200 行拆成两种做法,账立刻不一样。重写的工时集中在四周内,重构的工时摊在十二周里,但两者对发布的挤压完全不同。
| 维度 | 整体重写 | 渐进重构 |
|---|---|---|
| 工时 | 3,200 ÷ 20 = 160 小时 | 12 次 × 4 小时 = 48 小时 |
| 周期 | 160 ÷ 40 = 4 周 | 12 周,每周一次 |
| 发布 | 停发 4 周,停掉 8 次发布 | 不停发,0 停机 |
| 测试 | 重写后集中补 11 个用例 | 11 次各带 1 个,1 次纯搬移 |
| 失败代价 | 4 周全部沉没 | 单次最多 4 小时 |
112 小时的差是 2.8 周。 160 − 48 = 112,按每周 40 小时算是 2.8 周;这 2.8 周不是省下来的假,是可以继续发版、继续收反馈的真时间。
全库重写的账更难看。 62,400 ÷ 20 = 3,120 小时,即 78 周、1.5 年;而这座代码库原建只用了 14 个月,重写还要兼容历史数据与现有 344 个账号,实际只会比 1.5 年更慢。
重写只在三个前提同时成立时才值得做。 技术栈已无法维护,比如关键依赖停更或语言版本 EOL;用户量与数据量足够小,可以一次迁移;账上有 4 周以上的现金缓冲。缺任意一条,退回重构。
Strangler 是两者的折中。 新路径走新代码,旧路径保留 6 个月,一个开关两个入口,按用户或按对象逐步切;切完再删旧代码,每一步都能单独回滚。
三个前提写成一张表,缺一条就划掉重写。 前提不是感觉,是三条能当场核对的判据。
| 前提 | 可核对的判据 | 不成立时 |
|---|---|---|
| 技术栈可维护 | 关键依赖近 12 个月有更新、语言版本未 EOL | 先做依赖升级,不是重写 |
| 数据可一次迁移 | 表数量与行数能在一次窗口内迁完 | 用双写与分批,回到迁移三步 |
| 现金可撑 4 周 | 账上现金 ÷ 月支出 ≥ 1 | 先攒缓冲,重写排到下一季 |
11 个测试是重构的验收物。 12 次重构里 11 次各带 1 个测试,另一次是纯搬移;这 11 个用例覆盖的是被改动的路径,不是全模块覆盖率,但足以在切换时告诉你哪条路断了。
开关要能按对象切。 按用户切适合权限类改动,按对象切适合数据类改动;两种都做不到,说明新旧路径还共用同一份状态,这时先拆状态再谈切换。
账算完还要乘一次概率。 如果重写有 25% 的概率拖到第八周,期望停发就是 4 × 75% + 8 × 25% = 5 周,而不是 4 周;把这个数写进决策,比事后解释「没想到」便宜。
重写最贵的不是写,是删旧路径。 新旧两套代码并存越久,同步修 bug 的次数越多;Strangler 给的 6 个月是上限,不是免费窗口。
四、复算:160 小时、48 小时与 1.5 年
把上面的判断压进一组算式,任何一个数字都能手工复算。
目标模块 = 3,200 行(projects 12,800 × 25%)
整体重写 = 3,200 ÷ 20 行/小时 = 160 小时
重写周期 = 160 ÷ 40 小时/周 = 4 周
渐进重构 = 12 次 × 4 小时 = 48 小时
工时差 = 160 − 48 = 112 小时
周数差 = 112 ÷ 40 = 2.8 周
停发代价 = 4 周 × 2 次/周 = 8 次发布
全库重写 = 62,400 ÷ 20 = 3,120 小时
全库周期 = 3,120 ÷ 40 = 78 周
年数 = 78 ÷ 52 = 1.5 年
原建周期 = 14 个月(对照)
160 对 48 不是效率差,是风险差。 重写把四周的产出押在一次切换上,重构把同样的问题拆成十二次可回滚的小赌注;一个人做产品,输不起的是那一次全押。
4 周与 12 周的区别在反馈。 停发四周意味着四周没有真实用户数据,重写完成时你甚至不确定方向还是不是当初那个;十二周里每周都在发布,方向偏了还能拐回来。
1.5 年是提醒,不是计划。 把全库重写当成「再花一年半」的选项放在桌上,答案会自己浮现:不是不能重写,是重写的机会成本高到没有必要。
五、行动清单与复算验收
先量,再算,最后才决定动哪一块。
- 拉出近 90 天该模块的提交与工单,除以 3 得到每月改动次数。
- 统计近 12 个月该模块的缺陷单,按千行折算年密度。
- 记录最近五次改动从打开文件到首次提交的分钟数,取中位数。
- 三条判据逐条打勾,只有全中才把「重写」写进候选。
- 把目标模块行数除以 20,算出重写小时数并换算成周。
- 把同一目标拆成 12 次、每次 4 小时的重构批次,排进周历。
- 除纯搬移外每次重构带一个测试,累计到 11 个再评估是否还需要重写。
- 若要重写,先核对三个前提并写下现金缓冲能撑几周。
验收标准:在目标模块 3,200 行、重写 160 小时对重构 48 小时的条件下,你能指出选择重构的具体判据、说清省下的 112 小时去了哪里;说不出,就算未通过。
这套判据不承诺重构一定成功。它只保证一件事:在你决定动刀之前,已经知道动的是哪一块、要花多少、以及最坏会失去什么。
先把 112 小时记进账本。 它不是抽象收益,是十二周里多出来的发布次数与少掉的四周停发;下一次有人提议重写,先拿这张账对一遍。
使用边界与实操验收
合规驱动的重写不走这套判据。 语言版本 EOL、关键依赖停止安全更新、许可证变更,这些是硬约束,没有投票空间;该换就得换,只是仍要按 Strangler 的方式切。
判据阈值要按代码量调。 2 个/千行/年对 3,200 行是 6.4 个缺陷;模块只有 800 行时,两个缺陷就会越过阈值,这时应该看绝对条数而不是密度。
重构不等于不设计。 12 次 × 4 小时如果没有目标结构,只是把代码从左边搬到右边;每次开始前写一句「这次之后依赖方向应该长什么样」。
重写失败的常见形状不是技术问题。 是停发期现金、用户迁移与旧数据兼容三件事同时到期;本文只给判据与工期,不给资金计划,现金缓冲要单独算。
该停下来的信号。 连续三次重构都在同一处返工,说明边界本身错了;回到模块边界那篇重新画依赖方向,而不是加大重构剂量。
关键参考与核验范围
核验日期:2026-10-26。以下文档用于核对概念与规则,本文所有数字均取自全组教学基线,不取自这些文档。
- Strangler Fig Application :用于核对「新路径走新代码、旧路径逐步替换」这一迁移形态的定义。
- Refactoring Catalog :用于核对小步重构手法的分类与命名。
- Technical Debt :用于核对「债务利息」这一比喻的原始表述。
- The Twelve-Factor App :用于核对配置与依赖显式声明的一般原则。
引用口径。 本文只借用上述材料中的概念名称与一般原则;3,200 行、160 小时、48 小时、112 小时与 1.5 年全部是教学假设,与这些文档无关。
核验范围。 上述四个入口在 2026-10-26 实测可公开访问(HTTP 200);未核验各项目的最新版本、价格与配额。
如何自行复核。 取目标模块最近 90 天的提交数除以 3,再数一遍近 12 个月的缺陷单;两个数有一个不达标,就先别把重写排进日历。
系列导航
- 上一篇:抽象与复用:第三次重复才抽,一次抽错的代价
- 下一篇:可替换性:七个外部服务与一层适配
- 回到目录:一个人的架构:技术选型、模块边界与演进
- 相关阅读:技术债预算:十八条债、二十四分钟与 7.5% 的工程时间 、独立开发者生存指南(四):工程与技术策略
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。