deprecation-and-migration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDeprecation 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
- 用新系统、API或库替换旧版本
- 淘汰不再需要的功能
- 合并重复的实现逻辑
- 移除无人维护但所有人都依赖的死代码
- 规划新系统的生命周期(弃用规划从设计阶段就应该启动)
- 决策是维护遗留系统还是投入资源做迁移
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
海勒姆定律(Hyrum's Law)让代码移除变得困难
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.
当用户量足够大时,系统的每一个可观测行为都会被用户依赖——包括bug、时序特性以及未文档化的副作用。这就是为什么弃用需要主动推进迁移,而不只是发布公告。如果用户依赖的行为在新系统中没有被复现,他们不可能直接切换到新系统。
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.
搭建新系统时就要问自己:「3年后我们要怎么淘汰这个系统?」设计了清晰接口、支持功能开关、对外暴露范围最小的系统,比到处泄露实现细节的系统更容易被弃用。
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.在弃用任何内容之前,先回答以下问题:
1. 该系统是否仍提供独一无二的价值?
→ 是则继续维护,否则进入下一步。
2. 有多少用户/调用方依赖该系统?
→ 量化迁移的范围。
3. 是否有可用的替代方案?
→ 没有就先搭建替代方案,没有替代品的情况下不要启动弃用。
4. 每个调用方的迁移成本是多少?
→ 如果可以轻松自动化就自动迁移,如果是高成本的手动迁移,就和维护成本做权衡。
5. 不弃用的持续维护成本是多少?
→ 包括安全风险、工程师时间消耗、系统复杂度带来的机会成本。Compulsory vs Advisory Deprecation
强制弃用 vs 建议弃用
| 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.
| 类型 | 适用场景 | 实现方式 |
|---|---|---|
| 建议弃用 | 迁移是可选的,旧系统仍然稳定 | 告警提示、文档说明、引导提示。用户可以按自己的节奏完成迁移。 |
| 强制弃用 | 旧系统存在安全问题、阻碍开发进度,或者维护成本已经不可持续 | 设置明确的截止日期,旧系统会在指定日期被移除,同时提供迁移工具支持。 |
优先选择建议弃用。只有当维护成本或风险高到足以 justify 强制迁移时,再使用强制弃用。强制弃用要求你必须提供迁移工具、文档和支持——不能只发一个截止日期公告就不管了。
The Migration Process
迁移流程
Step 1: Build the Replacement
步骤1:搭建替代方案
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
步骤2:发布公告和文档
markdown
undefinedmarkdown
undefinedDeprecation Notice: OldService
弃用通知: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.
状态: 自2025-03-01起正式弃用
替代方案: NewService(参考下方迁移指南)
移除日期: 建议弃用——暂无明确截止时间
弃用原因: OldService需要手动扩缩容,缺乏可观测性,NewService可以自动实现这两点。
Migration Guide
迁移指南
- Replace with
import { client } from 'old-service'import { client } from 'new-service' - Update configuration (see examples below)
- Run the migration verification script:
npx migrate-check
undefined- 将 替换为
import { client } from 'old-service'import { client } from 'new-service' - 更新配置(参考下方示例)
- 运行迁移校验脚本:
npx migrate-check
undefinedStep 3: Migrate Incrementally
步骤3:增量迁移
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 regressionsThe 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.
逐个迁移调用方,不要一次性全部切换。每个调用方的迁移流程:
1. 识别所有和待弃用系统的交互点
2. 替换为使用新的替代系统
3. 校验行为一致性(测试、集成校验)
4. 移除所有对旧系统的引用
5. 确认没有出现回归问题** churn规则:** 如果你是待弃用基础设施的所有者,你有责任完成用户的迁移——或者提供无需迁移的向后兼容更新。不能发布弃用公告后就让用户自己解决所有问题。
Step 4: Remove the Old System
步骤4:移除旧系统
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只有在所有调用方都完成迁移后才能执行:
1. 确认没有活跃使用(通过指标、日志、依赖分析校验)
2. 移除旧代码
3. 移除关联的测试、文档和配置
4. 移除弃用公告
5. 庆祝——移除代码也是一项成就Migration Patterns
迁移模式
Strangler Pattern
绞杀模式(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并行运行新旧系统,逐步把流量从旧系统切到新系统,当旧系统流量占比为0%时,就可以移除旧系统。
阶段1:新系统处理0%流量,旧系统处理100%
阶段2:新系统处理10%流量(金丝雀验证)
阶段3:新系统处理50%流量
阶段4:新系统处理100%流量,旧系统闲置
阶段5:移除旧系统Adapter Pattern
适配器模式(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);
}
}创建适配器,把旧接口的调用转换为新实现的调用。调用方可以继续使用旧接口,你在后台完成后端迁移。
typescript
// 适配器:旧接口,新实现
class LegacyTaskService implements OldTaskAPI {
constructor(private newService: NewTaskService) {}
// 旧方法签名,代理到新实现
getTask(id: number): OldTask {
const task = this.newService.findById(String(id));
return this.toOldFormat(task);
}
}Feature Flag Migration
功能开关迁移(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();
}使用功能开关,逐个把调用方从旧系统切到新系统:
typescript
function getTaskService(userId: string): TaskService {
if (featureFlags.isEnabled('new-task-service', { userId })) {
return new NewTaskService();
}
return new LegacyTaskService();
}Zombie Code
僵尸代码(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.
僵尸代码是指无人所有但所有人都依赖的代码,它没有被主动维护,没有明确的负责人,会累积安全漏洞和兼容性问题。识别特征:
- 超过6个月没有提交记录,但仍有活跃调用方
- 没有分配的维护者或负责团队
- 测试用例失败但没有人修复
- 依赖存在已知安全漏洞但没有人更新
- 文档引用的系统已经不存在
应对方案: 要么分配负责人妥善维护,要么制定明确的迁移计划将其弃用。僵尸代码不能一直处于不确定状态——要么投入资源维护,要么直接移除。
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. |
| 借口 | 现实情况 |
|---|---|
| 「它还能用,为什么要移除?」 | 没有人维护的可用代码会累积安全债务和复杂度,维护成本会悄无声息地增长。 |
| 「以后可能有人会用到」 | 如果以后真的需要,可以重新构建。「以防万一」保留无用代码的成本比重新构建更高。 |
| 「迁移成本太高了」 | 把迁移成本和未来2-3年的持续维护成本做对比,长期来看迁移通常成本更低。 |
| 「我们做完新系统之后再做弃用」 | 弃用规划从设计阶段就应该启动,等新系统做完的时候,你已经有新的优先级了,现在就做规划。 |
| 「用户会自己完成迁移的」 | 他们不会的。你需要提供工具、文档和激励——或者自己完成迁移(churn规则)。 |
| 「我们可以无限期同时维护两个系统」 | 两个实现相同功能的系统意味着双倍的维护、测试、文档和新人上手成本。 |
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)
完成弃用后校验以下项:
- 替代方案已经过生产验证,覆盖所有核心使用场景
- 存在包含具体步骤和示例的迁移指南
- 所有活跃调用方都已完成迁移(通过指标/日志验证)
- 旧代码、测试、文档和配置已被完全移除
- 代码库中没有残留对已弃用系统的引用
- 弃用公告已被移除(已经完成了它的使命)