Deprecation and Migration(废弃与迁移)
Overview
代码是负债不是资产。每行代码都有持有成本:修 bug、跟依赖、打补丁、新人理解。废弃是主动清偿负债的纪律,迁移是把用户安全送到新实现的过程。组织擅长建造,不擅长拆除——本技能补这一块。
When to Use
- 删除旧 API、旧功能、旧分发渠道前
- 公开函数改名/改签名且有下游用户时
- 判断僵尸代码、注释代码、兼容 shim 能否删时
- 做选型替换(旧实现 → 新实现)需迁移用户时
- 新系统设计时(废弃计划从设计时开始:3 年后怎么拆掉它)
When NOT to use:
- 内部未公开、无下游的函数重构(直接改,跑测试即可)
- 一次性脚本、实验分支(删了就是,无需流程)
- 废弃流程已在走,只是执行迁移步骤
决策五问(删之前先回答)
1. 还有独特价值吗?有 → 留着维护;无 → 继续
2. 多少用户/调用方依赖?量化迁移范围(全局搜调用点)
3. 替代品存在吗?无 → 先造替代品,不许裸废弃
4. 每个调用方迁移成本多高?可自动化 → 直接迁;手工高成本 → 权衡持有成本
5. 不删的持有成本是什么?安全风险、人力、复杂度机会成本
Advisory vs Compulsory(两级废弃)
| 类型 | 何时用 | 机制 |
|---|---|---|
| Advisory(建议) | 迁移可选,旧系统稳定 | 警告 + 文档 + 引导,用户按自己节奏迁,无硬 deadline |
| Compulsory(强制) | 有安全问题、阻塞进展、持有成本不可持续 | 硬 deadline(某版本移除),必须配迁移工具 + 文档 + 支持 |
默认 Advisory。 Compulsory 必须同时给出:替代品可用、迁移指南、到期版本,三缺一即不许强制。
迁移四步
替代品就绪 → 公告 + 文档 → 逐个迁移 → 确认零使用后删除
- 替代品先行:覆盖旧系统关键用例、有文档、有迁移指南、已在生产验证(不是"理论上更好")。
- 公告:CHANGELOG 开 Deprecated 小节 + 迁移指南(旧→新对照、验证命令);旧入口保留但打印废弃警告(含新用法 + 移除版本)。
- 逐个迁移:一次迁一个调用方,每迁完验证行为一致(测试 + 集成检查),不批量闪迁。
- 删除:确认零活跃使用(搜索零引用、日志零触发、测试零覆盖)后删代码 + 删测试 + 删文档 + 删公告。删代码是成就,不是损失。
Churn Rule: 谁拥有被废弃的基础设施,谁负责迁用户——或提供无需迁移的向后兼容。不许只发公告让用户自己想办法。
删除标准(四条同时满足才删)
- 无引用:全局搜索零调用(含反射、动态导入、配置/路由字符串),测试零覆盖
- 无契约:不是公开 API / 插件点 / 外部依赖接口。对外暴露的即使内部无人调也不算僵尸
- 有替代:调用方已有替代品可用,或已过废弃 deadline 且公告充分
- 可回滚:git 历史可找回,删除 commit 写清原因,删完全量测试 + 构建通过,一次只删一批不混功能改动
特殊项:
- 注释掉的代码块 → 见光就删,git 历史就是它的坟墓
- 兼容 shim → 按版本契约删:有明确下线条件(最低支持版本/调用方全迁)才删;否则留但必须加
TODO + 下线版本 + 原因,无下线计划的兼容代码即永久僵尸 - 拿不准的 → 先标
deprecated+ 运行时警告跑一个版本,看触发再删,不永久保留
破坏性变更规则
- 默认不 break 下游:旧名保留做别名转发到新实现,逻辑只留一份
- 旧入口加
@deprecated+ 运行时警告一次(新名字 + 移除版本) - 测试同时覆盖新旧两个入口
- 本次 minor 只废弃不删除,下一个 major 才删旧别名
Quick Reference
| 场景 | 动作 |
|---|---|
| 删旧功能 | 五问 → Advisory/Compulsory 定级 → 替代品 → 公告 → 逐迁 → 零使用后删 |
| 公开改名 | 别名转发 + deprecated 警告 + 双入口测试,major 才删旧名 |
| 注释代码 | 直接删 |
| 无引用内部代码 | 四标准验证后删,一批一次 |
| 兼容 shim | 按版本契约,无下线计划即僵尸,加 TODO 限期 |
| 拿不准 | 标 deprecated 跑一个版本观察,不永久保留 |
Common Rationalizations
| Excuse | Reality |
|---|---|
| "没人用了,直接删" | Hyrum's Law:用户够多时连 bug 都有人依赖;先量化调用方,无替代品不许裸删 |
| "先删了,有问题再加回来" | 删除易恢复难(下游已改);流程是公告→迁移→零使用→删,删是最后一步不是第一步 |
| "留着又不占地方" | 每行都是负债:测试、补丁、理解成本;留必须有下线计划,否则即僵尸 |
| "兼容 shim 才几行" | 几行的 shim 乘以 30 年就是永久税;无 TODO + 下线版本即违规 |
| "breaking 一次改完省事" | 省的是你,成本全转嫁下游;别名 + 迁移期是拥有者的责任(Churn Rule) |
| "注释代码万一有用" | git 即坟墓;注释代码无测试无维护,留着只污染阅读 |
Red Flags — STOP
- 无替代品即宣布废弃
- Compulsory 无 deadline、无迁移工具、无文档三缺一
- 公开改名直接改无别名、无迁移期
- 注释代码/零引用代码以"万一有用"为由保留
- 兼容 shim 无 TODO + 下线版本
- 批量闪迁多调用方(一次迁多个,出事无法定位)
- 删代码与功能改动混同一提交
以上任一出现 → 停手,回迁移四步/删除标准修正后再删。
Verification
- 决策五问已回答,迁移范围已量化
- Advisory/Compulsory 已定级;Compulsory 有 deadline + 工具 + 文档
- 替代品覆盖关键用例且已验证
- 公告已发(CHANGELOG Deprecated 小节 + 迁移指南),旧入口有废弃警告
- 删除前确认零使用(搜索/日志/覆盖三证据)
- 删除 commit 独立、可回滚,全量测试 + 构建通过