deprecation-and-migration
addyosmani/agent-skills
引導淘汰舊系統、API 或功能,並將使用者遷移至替代方案的流程,內容包含決策框架、遷移模式及移除策略。
...展開全部廢止與遷移
概述
程式碼是一種負擔,而非資產。每一行程式碼都伴隨著持續的維護成本——需要修復的錯誤、需要更新的依賴項、需要套用的安全性修補程式,以及需要培訓的新進工程師。廢棄是一門移除不再物有所值程式碼的學問,而遷移則是將使用者安全地從舊系統移轉至新系統的過程。
大多數工程組織擅長打造產品,但鮮少擅長移除產品。這項技能正是為彌補此差距而存在。
何時使用
- 以新系統、API 或函式庫取代舊系統、API 或函式庫
- 淘汰不再需要的功能
- 整合重複的實作
- 移除無人負責但眾人皆依賴的死碼
- 規劃新系統的生命週期(停用規劃應從設計階段開始)
- 決定是否繼續維護舊系統,或投資進行遷移
核心原則
程式碼是一種負擔
每一行程式碼都伴隨著持續的成本:它需要測試、文件、安全性修補程式、依賴項更新,以及任何與之相關工作人員的腦力負擔。程式碼的價值在於它所提供的功能,而非程式碼本身。當相同的功能能透過更少的程式碼、更低的複雜度或更好的抽象化來實現時——舊程式碼就該被淘汰。
海勒姆定律使移除變得困難
當使用者數量足夠多時,任何可觀察到的行為都會成為依賴對象——包括錯誤、時序上的怪異現象,以及未記錄的副作用。這就是為什麼廢止功能需要積極的遷移,而不僅僅是發布公告。當使用者依賴某些行為,而替代方案無法複製這些行為時,他們就無法「直接切換」。
廢棄規劃始於設計階段
在建構新系統時,請自問:「三年後我們該如何移除這個功能?」採用乾淨介面、功能標誌(feature flags)並將暴露的介面範圍最小化的系統,相較於處處洩漏實作細節的系統,更容易進行廢棄處理。
廢止的決策
在將任何功能標記為過時之前,請先回答以下問題:
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)





首頁
