研发协作
在移除旧系统、API、功能或迁移数据库 schema 时…
当需要下线旧系统/API/功能、合并重复实现、清理僵尸代码,或在生产环境无停机地重命名/删除数据库列时,用这套弃用与迁移流程安全推进。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在移除旧系统、API、功能或迁移数据库 通用
解决思路
- 先做弃用决策:依次回答五个问题——该系统是否仍提供独特价值(是则维护,否则继续);有多少用户/消费者依赖它(量化迁移范围);是否已有替代品(没有则先构建替代品,不要在没有替代方案时弃用);每个消费者的迁移成本是多少(可轻松自动化就做,手动且高成本则与维护成本权衡);不弃用的持续维护成本是多少(安全风险、工程师时间、复杂度带来的机会成本)。
- 选择弃用类型:默认使用建议性弃用(advisory),通过警告、文档、提示让用户按自己的节奏迁移;仅当旧系统有安全问题、阻碍进展或维护成本不可持续时使用强制性弃用(compulsory),给出硬性截止日期并提供迁移工具。
- 构建替代品:替代品必须覆盖旧系统的所有关键用例、有文档和迁移指南、并已在生产环境验证(而非只是理论上更好)。
- 发布并记录弃用通知:写明状态(弃用日期)、替代品、移除日期(建议性则无硬性截止)、原因,并附上具体迁移指南(如替换 import 语句、更新配置、运行迁移校验脚本 `npx migrate-check`)。
- 增量迁移消费者:逐个而非一次性迁移,每个消费者依次执行——识别与弃用系统的所有接触点、改用替代品、验证行为一致(测试、集成检查)、移除对旧系统的引用、确认无回归。
- 遵守 Churn Rule:如果你拥有被弃用的基础设施,你负责迁移你的用户,或提供无需迁移的向后兼容更新;不要只宣布弃用就让用户自己想办法。
- 移除旧系统:仅在所有消费者迁移完成后执行——验证零活跃使用(指标、日志、依赖分析)、删除代码、删除相关测试/文档/配置、删除弃用通知、庆祝(删除代码是成就)。
- 使用绞杀者模式(Strangler Pattern)并行运行新旧系统并逐步切换流量:阶段1新系统0%旧系统100%;阶段2新系统10%(金丝雀);阶段3新系统50%;阶段4新系统100%旧系统空闲;阶段5移除旧系统。
- 使用适配器模式(Adapter Pattern):创建适配器把旧接口调用翻译到新实现,消费者继续使用旧接口,同时你迁移后端(如 `class LegacyTaskService implements OldTaskAPI` 内部委托给 `NewTaskService`)。
- 使用功能开关迁移(Feature Flag Migration):用功能开关逐个把消费者从旧系统切到新系统(如 `featureFlags.isEnabled('new-task-service', { userId })` 决定返回 `NewTaskService` 还是 `LegacyTaskService`)。
- 数据库 schema 迁移使用扩展/收缩(Expand/Contract),绝不在原地修改列:EXPAND 阶段添加新列(可空)与旧列并存;MIGRATE 阶段回填现有行并由应用双写新旧列;CONTRACT 阶段在无代码读取旧列后于后续独立部署中删除旧列。
- 以重命名 `name` 为 `full_name` 为例按五步执行:1. Expand——添加可空的 `full_name` 并部署(旧代码忽略它,不会出错);2. Dual-write——应用在每次插入/更新时同时写 `name` 和 `full_name` 并部署;3. Backfill——分批把 `name` 复制到 `full_name`,避免锁表;4. Switch reads——应用改读 `full_name`,仍双写,部署并观察;5. Contract——停止写 `name`,然后在单独的、更晚的部署中删除该列。每一步都可独立部署和回滚。
- 数据库迁移规则:先加后删,破坏性操作最后且单独部署(新增可空列、新表、新索引在任何部署中都安全;删除和重命名要在无代码引用旧结构后单独部署);每个迁移都要有经过测试的 down 路径(无法回滚的迁移就是无法回滚的部署,合并前先编写并运行 `down`);回填要分批并避开热路径(单条 `UPDATE` 覆盖数百万行会锁表,要分块并限流);构建大索引时不阻塞写入(如 Postgres 的 `CREATE INDEX CONCURRENTLY`);当切换有风险时用功能开关与代码解耦。
- 识别僵尸代码(无人拥有但人人依赖的代码):6 个月以上无提交但仍有活跃消费者、无指定维护者或团队、无人修复的失败测试、无人更新的已知漏洞依赖、引用已不存在系统的文档。应对方式:要么指定负责人并妥善维护,要么用具体迁移计划弃用;僵尸代码不能停留在中间状态。
- 反驳常见合理化借口:能工作不代表不用删(会积累安全债和复杂度);以后可能需要可以重建(保留未用代码成本更高);迁移太贵要对比 2-3 年维护成本(长期通常迁移更便宜);等新系统完成再弃用(弃用规划从设计时就开始);用户会自行迁移(不会,要提供工具、文档、激励或自己迁移);可以无限期维护两套系统(成本翻倍);只重命名一列(发布期间新旧代码并存会查询不存在的列,必须 expand/contract);同一次迁移中加列又删旧列(把安全的加和破坏性的删耦合了,删要单独部署);需要时再写回滚(没有 down 路径的迁移无法回滚,合并前先写并运行 `down`)。
- 警惕红旗信号:弃用系统没有可用替代品;弃用公告没有迁移工具或文档;多年建议性弃用无进展;无主但有活跃消费者的僵尸代码;向弃用系统添加新功能(应投资替代品);未测量当前使用量就弃用;未验证零活跃消费者就删除代码;schema 变更与依赖它的代码在同一次部署发布;原地重命名或删除列而非 expand/contract;合并的迁移没有经过测试的 down 路径,或回填会锁表。
- 完成后验证:替代品已生产验证并覆盖所有关键用例;存在含具体步骤和示例的迁移指南;所有活跃消费者已迁移(由指标/日志验证);旧代码、测试、文档、配置已完全移除;代码库中无对弃用系统的引用;弃用通知已移除。数据库 schema 迁移后额外验证:变更以增量阶段发布(expand → backfill → contract)而非单次原地编辑;每个部署步骤新旧代码对 schema 都有效;每个迁移有经过测试的 down 路径;回填以限流批次运行;破坏性步骤(删除/重命名)在无代码引用旧结构后单独部署。
适用边界 · 注意事项
- 没有可用替代品时不要弃用——必须先构建并生产验证替代品。
- 不要只宣布弃用而不提供迁移工具、文档和支持,也不要指望用户自行迁移。
- 不要在同一个部署中同时发布 schema 变更和依赖它的代码,也不要原地重命名或删除列。