mcpField
D
deprecation-and-migration

deprecation-and-migration

github.com/addyosmani开发111.5k59.1k 次安装2 周前更新
代码仓库

关于 deprecation-and-migration

为工程团队提供系统弃用与迁移的实践指南,涵盖决策、用户迁移及数据库变更,强调先准备替代方案并分阶段移除旧系统。

主要功能

  1. 01评估弃用必要性与影响
  2. 02区分建议式和强制式弃用
  3. 03规划渐进式用户迁移
  4. 04提供绞杀者、适配器和功能标志模式
  5. 05指导扩展/迁移/收缩式数据库变更

适用场景

  1. 01替换旧系统、API 或库
  2. 02逐步下线不再需要的功能
  3. 03迁移重复实现或无人维护的代码
  4. 04安全调整数据库架构

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/addyosmani/agent-skills --skill deprecation-and-migration

安装后在 Claude Code 中输入/deprecation-and-migration确认已加载。

详细介绍

弃用与迁移

概述

代码是负担,而非资产。每一行代码都有持续的维护成本——要修复 bug、更新依赖、应用安全补丁,还要帮助新工程师熟悉代码。弃用是一门移除不再物有所值的代码的学问,而迁移则是让用户安全地从旧系统转向新系统的过程。

大多数工程组织都擅长构建事物,却很少擅长移除它们。这项技能正是为了弥补这一不足。

适用场景

  • 用新系统、API 或库替换旧系统、API 或库
  • 停用不再需要的功能
  • 整合重复的实现
  • 移除无人负责却人人依赖的死代码
  • 规划新系统的生命周期(弃用规划从设计阶段就应开始)
  • 决定是继续维护旧系统,还是投入资源进行迁移

核心原则

代码是负担

每一行代码都有持续成本:需要测试、文档、安全补丁、依赖更新,还会给在附近工作的任何人增加认知负担。代码的价值在于它提供的功能,而不在于代码本身。如果用更少的代码、更低的复杂度或更好的抽象就能提供相同的功能,就应该移除旧代码。

海勒姆定律让移除变得困难

用户足够多时,每一种可观察到的行为都会有人依赖——包括 bug、时序特性和未记录的副作用。因此,弃用需要积极推动迁移,而不只是发布公告。如果用户依赖替代方案无法复现的行为,他们就无法“直接切换”。

弃用规划从设计阶段就应开始

构建新系统时,先问:“3 年后我们该如何移除它?”采用清晰接口、功能标志且表面积最小的系统,比那些到处泄露实现细节的系统更容易弃用。

弃用决策

弃用任何事物之前,请回答以下问题:

1. Does this system still provide unique value?
   → If yes, maintain it. If no, proceed.

2. How many users/consumers depend on it?
   → Quantify the migration scope.

3. Does a replacement exist?
   → If no, build the replacement first. Don't deprecate without an alternative.

4. What's the migration cost for each consumer?
   → If trivially automated, do it. If manual and high-effort, weigh against maintenance cost.

5. What's the ongoing maintenance cost of NOT deprecating?
   → Security risk, engineer time, opportunity cost of complexity.

强制弃用与建议弃用

类型适用场景机制
建议弃用迁移为可选,旧系统稳定警告、文档和提醒。用户按自己的时间安排迁移。
强制弃用旧系统存在安全问题、阻碍进展或维护成本难以为继严格期限。旧系统将在 X 日期前移除。提供迁移工具。

默认采用建议弃用。 只有在维护成本或风险足以证明强制迁移合理时,才采用强制弃用。强制弃用需要提供迁移工具、文档和支持——不能只发布一个截止日期公告。

迁移流程

第 1 步:构建替代方案

没有可用的替代方案,就不要弃用。替代方案必须:

  • 覆盖旧系统的所有关键用例
  • 提供文档和迁移指南
  • 已在生产环境中验证(而非只是“理论上更好”)
第 2 步:发布公告并编写文档
## Deprecation Notice: OldService

**Status:** Deprecated as of 2025-03-01
**Replacement:** NewService (see migration guide below)
**Removal date:** Advisory — no hard deadline yet
**Reason:** OldService requires manual scaling and lacks observability.
            NewService handles both automatically.

### Migration Guide
1. Replace `import { client } from 'old-service'` with `import { client } from 'new-service'`
2. Update configuration (see examples below)
3. Run the migration verification script: `npx migrate-check`
第 3 步:逐步迁移

一次迁移一个使用方,而不是一次性全部迁移。对于每个使用方:

1. Identify all touchpoints with the deprecated system
2. Update to use the replacement
3. Verify behavior matches (tests, integration checks)
4. Remove references to the old system
5. Confirm no regressions

变更责任规则: 如果你负责维护即将弃用的基础设施,就有责任迁移用户,或提供无需迁移的向后兼容更新。不要发布弃用公告后就让用户自行想办法。

第 4 步:移除旧系统

只有在所有使用方都已迁移之后:

1. Verify zero active usage (metrics, logs, dependency analysis)
2. Remove the code
3. Remove associated tests, documentation, and configuration
4. Remove the deprecation notices
5. Celebrate — removing code is an achievement

迁移模式

绞杀者模式

让新旧系统并行运行。逐步将流量从旧系统切换到新系统。旧系统处理的流量降至 0% 时,将其移除。

Phase 1: New system handles 0%, old handles 100%
Phase 2: New system handles 10% (canary)
Phase 3: New system handles 50%
Phase 4: New system handles 100%, old system idle
Phase 5: Remove old system
适配器模式

创建一个适配器,将旧接口的调用转换为对新实现的调用。迁移后端期间,使用方继续使用旧接口。

// Adapter: old interface, new implementation
class LegacyTaskService implements OldTaskAPI {
  constructor(private newService: NewTaskService) {}

  // Old method signature, delegates to new implementation
  getTask(id: number): OldTask {
    const task = this.newService.findById(String(id));
    return this.toOldFormat(task);
  }
}
功能标志迁移

使用功能标志,逐个将使用方切换到新系统:

function getTaskService(userId: string): TaskService {
  if (featureFlags.isEnabled('new-task-service', { userId })) {
    return new NewTaskService();
  }
  return new LegacyTaskService();
}
数据库架构迁移(扩展/收缩)

架构变更是风险最高的迁移,因为数据是唯一无法通过回滚部署来恢复的东西。故障模式是将架构变更与代码变更耦合在一起:在开始使用新名称的同一个版本中重命名列,在发布期间——新旧代码同时运行时——其中一方查询的列可能并不存在。解决办法是绝不原地更改列。分阶段进行增量迁移,确保每一步中旧代码和新代码都有效。

EXPAND ──────────────→ MIGRATE ──────────────→ CONTRACT
add the new column,    backfill existing rows,  once no code reads the
nullable, alongside    dual-write old+new from  old column, drop it in
the old one            the app                  a later, separate deploy

示例——将 name 重命名为 full_name:

  1. 扩展。 将 full_name 添加为可空列。部署。(旧代码会忽略它;不会造成任何问题。)
  2. 双写。 应用每次插入/更新时同时写入 name 和 full_name。部署。
  3. 回填。 分批将现有行中的 name → full_name,避免锁定表。
  4. 切换读取。 让应用改为读取 full_name,同时继续双写。部署并观察运行情况。
  5. 收缩。 停止写入 name,然后在之后单独进行的另一次部署中删除该列。

每一步都可以独立部署和回滚:如果第 4 步运行异常,回滚代码即可,full_name 仍会继续填充。将每个阶段视为一个精简的垂直切片——请参阅 incremental-implementation 技能。

规则:

  • 先增量添加,最后单独进行破坏性变更。 添加(新可空列、新表、新索引)在任何部署中都是安全的;删除和重命名应在旧架构不再被任何代码引用后,通过单独部署完成。
  • 每次迁移都要有经过测试的回滚路径。 无法反向执行的迁移,就意味着无法回滚的部署。合并前编写并运行 down。
  • 分批回填,避开热路径。 对数百万行执行单次 UPDATE 会锁定表;应分块执行并进行限流。
  • 以不阻塞写入的方式创建大型索引(例如 Postgres 的 CREATE INDEX CONCURRENTLY)。
  • 当切换风险较高时,通过功能标志与代码解耦,具体做法与上文的功能标志迁移模式完全相同。

僵尸代码

僵尸代码是无人负责维护、却人人依赖的代码。它没有得到主动维护,也没有明确负责人,并且会累积安全漏洞和兼容性问题。迹象包括:

  • 6 个月以上没有提交记录,但仍有活跃的使用者
  • 没有指定的维护者或团队
  • 测试失败,却无人修复
  • 依赖项存在已知漏洞,却无人更新
  • 文档引用了已不存在的系统

**应对措施:**要么指定负责人并妥善维护,要么制定具体迁移计划并弃用。僵尸代码不能一直处于无人问津的状态——要么投入资源维护,要么将其移除。

常见借口

借口事实
“它还能运行,为什么要移除?”无人维护但仍在运行的代码会累积安全债务和复杂性。维护成本会悄然增加。
“以后可能有人会用到”如果以后确实需要,可以重新构建。为了“以防万一”而保留未使用的代码,成本高于重新构建。
“迁移成本太高”将迁移成本与未来 2-3 年的持续维护成本进行比较。长期来看,迁移通常更便宜。
“等新系统完成后,我们再弃用它”弃用规划应从设计阶段开始。等新系统完成时,你就会有新的优先事项。现在就规划。
“用户会自行迁移”不会。提供工具、文档和激励措施,或者亲自完成迁移(“流失规则”)。
“我们可以无限期地维护两套系统”两套系统做同一件事,会让维护、测试、文档和新手上手的成本翻倍。
“只要重命名列就行了,就改一行”在发布过程中,新旧代码会同时运行,其中一方会查询已不存在的列。采用扩展/收缩策略,绝不要就地重命名。
“我会在同一个迁移中添加新列并删除旧列”这会将安全的新增操作与破坏性删除操作耦合起来。删除操作应单独发布,并且要等到没有代码再引用旧结构之后。
“需要时我们再写回滚”没有回退路径的迁移,就是无法撤销的发布。合并前编写并运行 down。

危险信号

  • 已弃用的系统没有可用的替代方案
  • 发布弃用公告,却没有迁移工具或文档
  • “软性”弃用多年来一直只有建议,没有任何进展
  • 没有负责人的僵尸代码,却仍有活跃使用者
  • 在已弃用的系统中添加新功能(应投资于替代方案)
  • 未衡量当前使用情况就弃用
  • 未确认活跃使用者为零就移除代码
  • 在同一次发布中上线架构变更及其依赖代码
  • 通过就地修改重命名或删除列,而非采用扩展/收缩策略
  • 合并了未经测试、没有回退路径的迁移,或会锁定表的回填操作

验证

完成弃用后:

  • 替代方案已在生产环境中验证,并覆盖所有关键用例
  • 已提供迁移指南,包含具体步骤和示例
  • 所有活跃使用者均已迁移(已通过指标/日志验证)
  • 旧代码、测试、文档和配置均已彻底移除
  • 代码库中不再有对已弃用系统的引用
  • 已移除弃用通知(它们已完成使命)

完成数据库架构迁移后:

  • 变更分阶段以增量方式发布(扩展 → 回填 → 收缩),而非一次性就地修改
  • 在每个发布步骤中,新旧代码都能与架构兼容
  • 每个迁移都有经过测试的回退路径;回填以限速批次运行
  • 破坏性步骤(删除/重命名)单独发布,并且要等到没有代码再引用旧结构之后

评论

支持 Markdown