deprecation-and-migration
addyosmani/agent-skills
指导旧系统、API 或功能的弃用流程,以及将用户迁移至替代方案的过程,其中包括决策框架、迁移模式和移除策略。
...展开全部弃用与迁移
概述
代码是一种负担,而非资产。每一行代码都伴随着持续的维护成本——需要修复的缺陷、需要更新的依赖项、需要部署的安全补丁,以及需要培训的新工程师。弃用是一门通过移除不再物有所值的代码来优化系统效率的学科,而迁移则是将用户安全地从旧系统迁移到新系统的过程。
大多数工程团队擅长构建系统,但鲜少有人擅长淘汰系统。这项技能正是为填补这一空白而存在的。
何时使用
- 用新系统、API 或库替换旧系统、API 或库
- 淘汰不再需要的功能
- 整合重复的实现
- 移除无人负责但人人依赖的死代码
- 规划新系统的生命周期(弃用规划从设计阶段就开始)
- 决定是继续维护遗留系统还是投资进行迁移
核心原则
代码是一种负担
每一行代码都会产生持续成本:它需要测试、文档、安全补丁、依赖项更新,以及对任何相关开发人员造成的认知负担。代码的价值在于它所提供的功能,而非代码本身。当相同的功能可以通过更少的代码、更低的复杂度或更好的抽象来实现时——旧代码就该被淘汰。
海勒姆定律使移除变得困难
当用户数量足够多时,任何可观察到的行为都会被依赖——包括 bug、时序怪癖以及未记录的副作用。这就是为什么弃用需要积极的迁移,而不仅仅是发布公告。当用户依赖于替代方案无法复现的行为时,他们无法“直接切换”。
弃用规划始于设计阶段
在构建新系统时,请自问:“三年后我们该如何移除这个功能?”与那些到处泄露实现细节的系统相比,采用简洁接口、功能开关并最大限度减少暴露面的系统更容易被废弃。
弃用决策
在弃用任何功能之前,请回答以下问题:
1. 该系统是否仍提供独特价值?
→ 如果是,则继续维护;如果不是,则继续进行。
2. 有多少用户/消费者依赖它?
→ 量化迁移范围。
3. 是否有替代方案?
→ 若无,先构建替代方案。切勿在没有替代方案的情况下进行弃用。
4. 每个消费者的迁移成本是多少?
→ 若可轻松自动化处理,则执行迁移;若需手动操作且耗时费力,则需权衡其与维护成本。
5. 不弃用该系统的持续维护成本是多少?
→ 安全风险、工程师时间、复杂性带来的机会成本。
强制性弃用与建议性弃用
| 类型 | 何时使用 | 机制 |
|---|---|---|
| 建议性 | 迁移可选,旧系统运行稳定 | 警告、文档说明、提示。用户可按自身时间表进行迁移。 |
| 强制 | 旧系统存在安全隐患、阻碍发展,或维护成本难以承受 | 硬性截止日期。旧系统将在X日期前被移除。需提供迁移工具。 |
默认采用建议模式。仅当维护成本或风险足以证明强制迁移的必要性时,才采用强制模式。强制弃用需提供迁移工具、文档及支持——不能仅宣布截止日期。
迁移流程
步骤 1:构建替代方案
在没有可运行的替代方案之前,切勿宣布弃用。替代系统必须:
- 覆盖旧系统的所有关键用例
- 具备文档和迁移指南
- 已在生产环境中经过验证(而不仅仅是“理论上更优”)
步骤 2:发布公告并记录
## 弃用通知:OldService
**状态:** 自 2025-03-01 起弃用
**替代方案:** NewService(参见下方的迁移指南)
**移除日期:** 建议性通知——目前尚无硬性截止日期
**原因:** OldService 需要手动进行扩展,且缺乏可观测性。
NewService 可自动处理这两方面。
### 迁移指南
1. 将 `import { client } from 'old-service'` 替换为 `import { client } from 'new-service'`
2. 更新配置(参见下文示例)
3. 运行迁移验证脚本:`npx migrate-check`
步骤 3:分阶段迁移
请逐个迁移消费者,不要一次性全部迁移。对于每个消费者:
1. 识别与已弃用系统所有交互点
2. 更新为使用替代方案
3. 验证行为一致性(测试、集成检查)
4. 移除对旧系统的引用
5. 确认无回归问题
“用户流失”原则:如果您拥有即将被弃用的基础设施,则有责任负责迁移您的用户——或者提供无需迁移的向后兼容更新。切勿仅宣布弃用就让用户自行摸索。
步骤 4:移除旧系统
仅在所有用户完成迁移后:
1. 验证无活跃使用情况(指标、日志、依赖关系分析)
2. 移除代码
3. 移除相关的测试、文档和配置
4. 移除弃用通知
5. 庆祝——移除代码是一项成就
迁移模式
“勒颈”模式
让旧系统和新系统并行运行。将流量逐步从旧系统路由到新系统。当旧系统处理的流量降至 0% 时,将其移除。
第一阶段:新系统处理 0%,旧系统处理 100%
第二阶段:新系统处理 10%(金丝雀测试)
第三阶段:新系统处理 50%
第四阶段:新系统处理 100%,旧系统闲置
第五阶段:移除旧系统
适配器模式
创建一个适配器,将来自旧接口的调用转换为新实现。在迁移后端期间,消费者继续使用旧接口。
// 适配器:旧接口,新实现
class LegacyTaskService implements OldTaskAPI {
constructor(private newService: NewTaskService) {}
// 旧方法签名,委托给新实现
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();
}
僵尸代码
僵尸代码是指无人负责但人人都依赖的代码。它未被积极维护,没有明确的负责人,且会积累安全漏洞和兼容性问题。特征包括:
- 超过6个月未提交代码,但仍有活跃的调用方
- 未指定维护者或团队
- 测试失败且无人修复
- 存在已知漏洞的依赖项,且无人更新
- 文档中引用了已不存在的系统
应对措施:要么指派负责人并妥善维护,要么制定具体的迁移计划将其废弃。僵尸代码不能长期处于悬而未决的状态——要么投入资源维护,要么予以移除。
常见的合理化借口
| 借口 | 现实 |
|---|---|
| “它还能用,为什么要移除?” | 无人维护的运行代码会积累安全债务和复杂性。维护成本在悄无声息中不断攀升。 |
| “以后可能会有人用到它” | 如果以后需要,可以重新构建。为了“以防万一”而保留未使用的代码,其成本比重新构建还要高。 |
| “迁移成本太高了” | 将迁移成本与未来2-3年的持续维护成本进行比较。从长远来看,迁移通常更经济。 |
| “等新系统完成后,我们会将其废弃” | 弃用规划应从设计阶段就开始。等到新系统完成时,你们会有新的优先事项了。现在就着手规划吧。 |
| “用户会自行迁移” | 他们不会的。请提供工具、文档和激励措施——或者由你们自己完成迁移(即“流失法则”)。 |
| “我们可以无限期地同时维护这两个系统” | 两个系统做同样的事情,意味着维护、测试、文档编写和用户入职的成本都会翻倍。 |
警示信号
- 已弃用的系统且无替代方案
- 发布弃用公告却未提供迁移工具或文档
- 多年来仅作为建议而未取得实质进展的“软”弃用
- 无人维护且仍有活跃用户的“僵尸代码”
- 在已弃用的系统中添加新功能(应将资源投入替代方案)
- 未评估当前使用情况就宣布弃用
- 在未验证活跃用户为零的情况下移除代码
验证
完成弃用操作后:
- 替代方案已在生产环境中经过验证,并覆盖所有关键用例
- 已提供包含具体步骤和示例的迁移指南
- 所有活跃用户均已完成迁移(通过指标/日志验证)
- 旧代码、测试、文档和配置已完全移除
- 代码库中不再存在对已弃用系统的任何引用
- 已移除弃用通知(其作用已达成)
---
name: deprecation-and-migration
description: Guides the process of deprecating old systems, APIs, or features and migrating users to replacements, including decision frameworks, migration patterns, and removal strategies.
---
# Deprecation and Migration
## Overview
Code is a liability, not an asset. Every line of code has ongoing maintenance cost — bugs to fix, dependencies to update, security patches to apply, and new engineers to onboard. Deprecation is the discipline of removing code that no longer earns its keep, and migration is the process of moving users safely from the old to the new.
Most engineering organizations are good at building things. Few are good at removing them. This skill addresses that gap.
## When to Use
- Replacing an old system, API, or library with a new one
- Sunsetting a feature that's no longer needed
- Consolidating duplicate implementations
- Removing dead code that nobody owns but everybody depends on
- Planning the lifecycle of a new system (deprecation planning starts at design time)
- Deciding whether to maintain a legacy system or invest in migration
## Core Principles
### Code Is a Liability
Every line of code has ongoing cost: it needs tests, documentation, security patches, dependency updates, and mental overhead for anyone working nearby. The value of code is the functionality it provides, not the code itself. When the same functionality can be provided with less code, less complexity, or better abstractions — the old code should go.
### Hyrum's Law Makes Removal Hard
With enough users, every observable behavior becomes depended on — including bugs, timing quirks, and undocumented side effects. This is why deprecation requires active migration, not just announcement. Users can't "just switch" when they depend on behaviors the replacement doesn't replicate.
### Deprecation Planning Starts at Design Time
When building something new, ask: "How would we remove this in 3 years?" Systems designed with clean interfaces, feature flags, and minimal surface area are easier to deprecate than systems that leak implementation details everywhere.
## The Deprecation Decision
Before deprecating anything, answer these questions:
```
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.
```
## Compulsory vs Advisory Deprecation
| Type | When to Use | Mechanism |
|------|-------------|-----------|
| **Advisory** | Migration is optional, old system is stable | Warnings, documentation, nudges. Users migrate on their own timeline. |
| **Compulsory** | Old system has security issues, blocks progress, or maintenance cost is unsustainable | Hard deadline. Old system will be removed by date X. Provide migration tooling. |
**Default to advisory.** Use compulsory only when the maintenance cost or risk justifies forcing migration. Compulsory deprecation requires providing migration tooling, documentation, and support — you can't just announce a deadline.
## The Migration Process
### Step 1: Build the Replacement
Don't deprecate without a working alternative. The replacement must:
- Cover all critical use cases of the old system
- Have documentation and migration guides
- Be proven in production (not just "theoretically better")
### Step 2: Announce and Document
```markdown
## 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`
```
### Step 3: Migrate Incrementally
Migrate consumers one at a time, not all at once. For each consumer:
```
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
```
**The Churn Rule:** If you own the infrastructure being deprecated, you are responsible for migrating your users — or providing backward-compatible updates that require no migration. Don't announce deprecation and leave users to figure it out.
### Step 4: Remove the Old System
Only after all consumers have migrated:
```
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
```
## Migration Patterns
### Strangler Pattern
Run old and new systems in parallel. Route traffic incrementally from old to new. When the old system handles 0% of traffic, remove it.
```
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 Pattern
Create an adapter that translates calls from the old interface to the new implementation. Consumers keep using the old interface while you migrate the backend.
```typescript
// 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);
}
}
```
### Feature Flag Migration
Use feature flags to switch consumers from old to new system one at a time:
```typescript
function getTaskService(userId: string): TaskService {
if (featureFlags.isEnabled('new-task-service', { userId })) {
return new NewTaskService();
}
return new LegacyTaskService();
}
```
## Zombie Code
Zombie code is code that nobody owns but everybody depends on. It's not actively maintained, has no clear owner, and accumulates security vulnerabilities and compatibility issues. Signs:
- No commits in 6+ months but active consumers exist
- No assigned maintainer or team
- Failing tests that nobody fixes
- Dependencies with known vulnerabilities that nobody updates
- Documentation that references systems that no longer exist
**Response:** Either assign an owner and maintain it properly, or deprecate it with a concrete migration plan. Zombie code cannot stay in limbo — it either gets investment or removal.
## Common Rationalizations
| Rationalization | Reality |
|---|---|
| "It still works, why remove it?" | Working code that nobody maintains accumulates security debt and complexity. Maintenance cost grows silently. |
| "Someone might need it later" | If it's needed later, it can be rebuilt. Keeping unused code "just in case" costs more than rebuilding. |
| "The migration is too expensive" | Compare migration cost to ongoing maintenance cost over 2-3 years. Migration is usually cheaper long-term. |
| "We'll deprecate it after we finish the new system" | Deprecation planning starts at design time. By the time the new system is done, you'll have new priorities. Plan now. |
| "Users will migrate on their own" | They won't. Provide tooling, documentation, and incentives — or do the migration yourself (the Churn Rule). |
| "We can maintain both systems indefinitely" | Two systems doing the same thing is double the maintenance, testing, documentation, and onboarding cost. |
## Red Flags
- Deprecated systems with no replacement available
- Deprecation announcements with no migration tooling or documentation
- "Soft" deprecation that's been advisory for years with no progress
- Zombie code with no owner and active consumers
- New features added to a deprecated system (invest in the replacement instead)
- Deprecation without measuring current usage
- Removing code without verifying zero active consumers
## Verification
After completing a deprecation:
- [ ] Replacement is production-proven and covers all critical use cases
- [ ] Migration guide exists with concrete steps and examples
- [ ] All active consumers have been migrated (verified by metrics/logs)
- [ ] Old code, tests, documentation, and configuration are fully removed
- [ ] No references to the deprecated system remain in the codebase
- [ ] Deprecation notices are removed (they served their purpose)





首页
