| name | maintain-team-deprecation-migration |
| description | 弃用与迁移——管理代码生命周期。当需要移除、替换或迁移已有功能/API,或提到"弃用""迁移""deprecation""breaking change" |
Deprecation & Migration — 弃用与迁移
入口/出口
- 入口: 需要移除或替换已有功能、API 版本升级、清理废弃代码
- 出口: 迁移计划 + 兼容层(如需要)+ 文档 + 清理
- 指向: 迁移完成后回到正常 build 流程
- 前置加载: CANON.md
- 输出路径: verify-workflow-review
何时不使用
- 只是新增功能,不移除、替换或改变已有行为
- 废弃范围没有使用数据、兼容要求或迁移窗口
- 只是删除本任务中新建且尚未发布的临时代码
核心原则
Code Is a Liability
代码是负债,不是资产。 未使用的代码仍然需要维护、编译、测试、理解。每行代码都有持续成本。删除代码是改善。
Hyrum 法则使删除困难
有足够用户时,每个可观察到的行为都有人依赖。 即使是未文档化的实现细节、错误消息文本、响应字段排序——某处可能有消费者依赖它。
弃用规划从设计时开始
设计 API 时就为未来弃用规划——使用 Feature Flag 或版本化参数,使旧行为可逐步下线。
弃用决策
在宣布弃用之前回答:
- 有替代方案吗? 用户迁移到哪里?替代至少和旧方案一样好。
- 还有多少用户? 多少人依赖这个 API/功能?实际使用量是多少?
- 迁移成本被承担了吗? 谁负责迁移——提供者还是消费者?迁移工具和文档存在吗?
- 时间线合理吗? 如果消费者团队需要 6 个月,不给他们 2 周。
- 紧急回退可能吗? 如果迁移出问题,可以立即恢复弃用功能吗?
强制 vs 必须弃用
| 类型 | 机制 | 适用 |
|---|
| 强制弃用 | 弃用日期后功能移除。消费者必须迁移。 | 安全修复、无法维护的旧系统 |
| 必须弃用 | 功能可用但文档化和告警说明即将移除。消费者有时间迁移。 | 改进但不紧急 |
"必须弃用"不是永久的。 如果消费者不迁移,"建议"变为"强制"带日期。
迁移模式
Strangler Pattern(最安全)
新系统逐步接管旧系统功能:
Phase 1: 新系统 + 旧系统并存(新代码走新路径)
Phase 2: 逐渐迁移旧路径到新系统
Phase 3: 旧系统仅剩 5% 流量
Phase 4: 旧系统完全关闭
不一次性替换。一条条路由/功能逐步迁移。
Adapter Pattern
async function v1CreateTaskHandler(req: V1Request): Promise<V1Response> {
const v2Request = toV2Request(req);
const v2Response = await v2Handler(v2Request);
return toV1Response(v2Response);
}
Feature Flag 迁移
if (await featureFlag.isEnabled('use-new-task-service', userId)) {
return newTaskService.create(req);
} else {
return oldTaskService.create(req);
}
迁移决策流程图
需要弃用?
├── 突发弃用(安全漏洞、合规要求)
│ └── 立即下线 + 紧急通知消费者
└── 渐进弃用
├── 多个消费者?
│ ├── YES → Strangler Pattern(逐个迁移)
│ └── NO → Adapter 或直接替换
├── 需要兼容期?
│ ├── YES → Feature Flag 控制新旧路径
│ └── NO → 直接替换 + 版本号大版本升级
└── 通知 → 设置过期 → 监控使用 → 移除旧代码
反模式修复表
| 反模式 | 问题 | 修复 |
|---|
只在注释里写 @deprecated | 没人看注释,消费者无感知 | 加上 console.warn / 运行时警告 + 使用量监控 |
| 没有度量就删除代码 | 可能还有人在用,删除即事故 | 先加埋点追踪使用量,确认为零后再删 |
| 新代码还在依赖废弃 API | 弃用形同虚设,永远无法清理 | CI 规则禁止新代码引入废弃 API import |
| 没有通知消费者就下线 | 消费者突然崩溃,生产事故 | 最少 2 个版本的弃用公告期 |
| 迁移中途停止(旧新并存) | 两套系统永久并存,复杂度翻倍 | 设死线,到期未迁的由平台强制切换 |
| 废弃了但忘了清理 | 僵尸代码堆积,拖累系统 | 每个废弃有 owner + 过期日期,过期后自动创建清理 PR |
| 替代方案质量低于旧方案 | 消费者拒绝迁移,两套永久并存 | 替代至少和旧方案一样好。不够好就不废弃。 |
| 弃用公告没有迁移指南 | 消费者不知道怎么改,只能拖着 | 每条弃用公告附带迁移示例和文档链接 |
好/坏弃用公告对照
export const oldGetUser = ...
export const oldGetUser = (id: string) => {
console.warn(
'[DEPRECATED] oldGetUser will be removed in v3.0 (2026-06-01). ' +
'Migrate to: userService.getUser(id) — see docs/migration/v2-to-v3.md'
);
trackDeprecatedUsage('oldGetUser');
return userService.getUser(id);
};
好弃用公告三要素:
- 运行时警告 — 每次调用时提醒消费者,不是沉默的注释
- 迁移指引 — 明确告诉消费者改用什么、怎么改、文档在哪
- 截止日期 — 给出具体移除时间,不是"未来某天"
Zombie Code 定义
代码是僵尸代码当它:
- 无人维护,但仍在运行
- 有活跃消费者,但 owner 已经离职/转组
- 文档缺失,但行为有人依赖
- 技术上已弃用,但关闭日期无限期推迟
僵尸代码必须消灭。 标注 owner、迁移消费者、设定关闭日期。
常见说辞
| 说辞 | 现实 | 后果 |
|---|
| "先留着吧,以后可能有用" | 留着 = 维护+测试+编译+理解成本。YAGNI(你不会需要它)。 | 僵尸代码堆积,每行年维护成本 ≥ 1h,团队理解成本随代码量线性增长 |
| "没人用的代码不用管" | 你怎么知道没人用?在关闭前加日志/指标验证。 | 未验证删除导致生产事故,修复时间 ≥ 2h + 影响所有未知消费者 |
| "直接删就行" | Hyrum 法则。某处有东西依赖它。总是用弃用→兼容→清理的三步过程。 | 跳过弃用流程直接删除,依赖方突然崩溃,紧急回滚 ≥ hotfix + 全量回归测试 |
| "必须弃用就够,消费者会自己迁移" | 很少消费者主动迁移。需要明确关闭日期 + 多次通信 + 迁移支持。 | 消费者不迁移导致双系统永久并存,维护成本翻倍 ≥ 2x |
| "没人用那个 API" | 你确定?查监控数据,不猜。 | 猜测代替数据导致误删,生产故障影响 ≥ 所有依赖该 API 的服务 |
| "新 API 还没准备好,先保留旧的" | 那不叫废弃,叫双写。设时间线。 | 无时间线的双写永远并存,技术债务累积 ≥ N 个未关闭的弃用项 |
| "文档更新等删代码时一起做" | 文档先行。消费者需要迁移指南才能迁移。 | 无迁移指南消费者无法行动,弃用周期延长 ≥ 2-3 个版本 |
| "废弃太麻烦了,直接改" | Breaking change 不走废弃流程 = 生产事故。 | 未走流程的 breaking change 导致下游团队生产故障,影响 ≥ M 个消费方 |
| "这个 API 只有我们内部用" | 内部团队也是消费者。内部依赖断裂同样导致生产故障。 | 内部依赖断裂影响 ≥ N 个内部服务,排查时间 ≥ 跨团队协调 1 周 |
| "消费者还没迁移,再延长一下" | 延期一次可以,延期两次说明你的迁移支持不够。主动提供协助。 | 反复延期导致弃用信誉下降,后续弃用更难推进,周期 ≥ 延期 N 次 |
红旗 — STOP
- 弃用公告中没有指定替代方案
- 弃用时间线给消费者不合理的短时间(< 1 个迭代)
- 弃用功能被新功能继续调用("先弃用,然后我们自己也用它")
- Comments-only 弃用("// deprecated" 但没日志、没告警、没文档)
- 旧代码直接删除——没有任何兼容期
验证失败处理
| 失败场景 | 处理方式 |
|---|
| 消费者拒绝迁移 | 评估影响范围。如影响小可强制下线;如影响大需升级到管理层决策。 |
| 迁移引入新 bug | 回滚到旧路径,调查根因,修复后重新迁移。不要在旧路径有 bug 时继续。 |
| 回滚失败(旧代码已删除) | 从 git 历史恢复旧代码作为 hotfix,重新评估迁移策略。保留旧代码直到确认新路径稳定。 |
| 替代方案本身也需要废弃 | 质疑架构方向。暂停迁移,重新评估替代方案。废弃链说明设计有问题。 |
| 依赖链式废弃(A→B→C) | 从叶子节点开始逐个迁移,不要并行。画出依赖图,按拓扑排序执行。 |
| 使用量降为零但仍有调用报错 | 检查监控覆盖是否完整。可能有未接入监控的调用方。加全链路追踪确认。 |
人类伙伴信号
以下话语出现时,说明你的弃用流程有缺口:
- "这个 API 什么时候下线?" — 你没设过期日期。每条弃用公告必须有明确截止时间。
- "还有谁在用这个?" — 你没追踪使用量。废弃前必须加监控,数据驱动决策。
- "迁移指南在哪?" — 你没写迁移文档。文档先行,消费者需要指南才能行动。
- "能再宽限几天吗?" — 你的时间线太紧了。重新评估消费者迁移节奏,调整截止日期。
- "我用了新 API 但行为不一样" — 你的替代方案没有完全覆盖旧 API 的行为。补充测试用例对齐。
- "为什么线上还在调旧接口?" — 你的监控没覆盖所有消费者,或弃用通知没到达。加运行时警告。
全部意味着:STOP。回到弃用决策,补齐缺失环节。
输出模板
弃用与迁移完成后应产出以下结构(记录于 ADR 或项目文档中):
### Deprecation & Migration 记录
**弃用目标**: [API / 功能 / 模块名]
**替代方案**: [新 API / 新功能名 + 迁移路径]
**弃用类型**: [强制 / 必须]
**截止日期**: [YYYY-MM-DD]
**消费者影响评估**:
| 消费者 | 当前调用量 | 迁移状态 | 迁移支持 |
|--------|-----------|----------|----------|
| [团队/服务1] | [N 次/天] | [已迁移 / 未迁移 / 迁移中] | [迁移指南 / 工具 / 无] |
**迁移时间线**:
- Phase 1: [YYYY-MM-DD] — 新旧并存,运行时警告上线
- Phase 2: [YYYY-MM-DD] — 使用量监控确认下降
- Phase 3: [YYYY-MM-DD] — 旧代码关闭/删除
**回退计划**: [hotfix 路径 / feature flag 回退 / git revert 策略]
**已知限制**: [兼容层行为差异 / 监控覆盖缺口 / 未迁移消费者]
验证清单