每個軟體專案最終都會遇到已棄用的程式碼、被標記為過時、不建議使用或計劃移除的元件。 @deprecated Java 中的註解 DeprecationWarning 在 Python 中,IDE 會自動補全功能中的刪除線,這些都是程式碼庫發出的訊號,表示你正在使用或維護的某些內容已被取代。忽略這些訊號會悄悄累積風險,直到某個依賴項被移除、安全性修補程式忽略了過時的 API,或是框架升級破壞了所有仍依賴三個主要版本前已棄用功能的程式碼。
理解「已棄用」的含義、其重要性以及如何系統地處理已棄用依賴項,是開發團隊可以掌握的最實用的維護技能之一。本指南涵蓋了所有相關內容:清晰的定義、與類似術語的比較、跨語言的棄用警告,以及在已棄用依賴項演變為生產環境問題之前對其進行結構化管理的方法。
什麼是已棄用代碼?
已棄用程式碼指的是那些功能仍然可用,但官方已明確不建議使用的函數、方法、API、函式庫或整個元件。這些程式碼仍然可以運行、編譯、執行並產生結果,但其維護者已表明,它們將在未來的版本中被移除,或被更好的替代方案取代,或乾脆停止維護和更新。使用已棄用程式碼意味著依賴那些其維護者已經不再關心的內容。
棄用是一種溝通機制,而非技術狀態。當庫維護者將某個函數標記為已棄用時,他們的意思是:「這個函數目前仍然有效,但我們打算將其移除,你應該在此之前遷移到其他庫。」從發布棄用通知到實際移除之間的時間長短不一,可能是一個主要版本,也可能是五年,但方向始終相同。 「已棄用」意味著最終會被移除。
Deprecated 與 Depreciated:拼字混淆
這兩個詞經常被混淆,拼字檢查器也無濟於事,因為它們都是真正的英語單詞,但含義不同。
(軟體領域中的)已棄用:標記為過時、不建議使用、計劃移除。這是軟體領域中正確的術語。
折舊(會計術語):指價值隨時間推移而減少。例如,「伺服器硬體在三年內折舊完畢」。
如果你在技術文件中看到“depreciated code”,它幾乎總是指“deprecated code”,作者誤用了會計術語,而本意是指軟體術語。這個錯誤很常見,甚至出現在本文的 Google Search Console 資料中。在軟體領域,請務必使用“deprecated”。
已棄用代碼、過時代碼、遺留代碼和失效代碼
這些術語雖然相關,但描述的是代碼的不同狀態。將它們混淆會導致溝通不良和優先判斷錯誤。
| 術語 | 這是什麼意思 | 它被移除了嗎? | 它有維護嗎? | 風險等級 |
|---|---|---|---|---|
| 已過時 | 官方不鼓勵,已標記為待移除 | 不,仍然存在 | 不,維護工作已經停止了。 | 中等規模,隨著時間而成長 |
| 過時的 | 不再相關或適用;已被取代。 | 有時 | 沒有 | 中等偏上 |
| 遺產 | 仍然有效且可能仍在生產環境中運行的舊程式碼 | 不,仍然活躍 | 很少 | 變量,取決於變化率 |
| 死程式碼 | 執行期間從未接到電話或聯繫。 | 不,仍在源頭 | 不適用,從不運行 | 低至中等遷移/審計風險 |
| 過時的程式碼 | 長期未修改但未被正式棄用的程式碼 | 沒有 | 不清楚 | Medium可能會隱藏一些假設。 |
已棄用 vs. 已過時「已棄用」是一個正式的標記,表示有人明確地用「已棄用」一詞對其進行了標記。 @deprecated 或發布了棄用通知。 「過時」是一個比較廣泛的描述,程式碼可能仍然可以運行,但鑑於現代替代方案的存在,已不再具有合理的用途。所有被棄用的程式碼最終都會過時,但並非所有過時的程式碼都已被正式棄用。
已棄用與已移除:已棄用的程式碼仍存在於程式碼庫中。已移除的程式碼則已完全消失。棄用期是指介於這兩種狀態之間的過渡階段,也就是在程式碼失效前進行遷移的時間。
已棄用程式碼與遺留程式碼:遺留程式碼是指早期技術時代編寫的、仍在積極使用和維護的舊生產程式碼。已棄用代碼則明確標記為需要移除。處理日常事務的遺留 COBOL 程序並非已棄用,它們屬於遺留代碼,但仍在積極維護。庫供應商標記為過時的 COBOL API 函數則屬於已棄用程式碼。
棄用與停用:棄用是程式碼庫或庫中的技術訊號。停用則是營運決策,包括關閉服務、移除基礎設施或終止對產品的支援。棄用的 API 可能還會繼續運作數年;而停用的 API 則會在特定日期被徹底關閉。
已棄用項的顯示方式:跨語言的警告
棄用警告的形式會因程式語言和工具的不同而有所差異。能夠第一時間辨識出這些警告是解決問題的第一步。
Python:棄用警告
蟒蛇
import warnings
# Marking a function as deprecated
def old_function():
warnings.warn(
"old_function is deprecated, use new_function instead",
DeprecationWarning,
stacklevel=2
)
# original implementation
def new_function():
# improved implementation
pass
Python 會在運作時顯示棄用警告。常見的編譯器訊息如下:
DeprecationWarning: old_function is deprecated, use new_function instead
或對於第三方軟體包:
DeprecationWarning: pkg_resources is deprecated as an API.
Use importlib.resources or importlib.metadata instead.
Java:已棄用的註解
Java的
public class LegacyProcessor {
@Deprecated
public void processData(String input) {
// old implementation
}
// Replacement method
public void processDataV2(String input, ProcessOptions options) {
// new implementation
}
}
Java編譯器產生:
Note: SomeFile.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
JavaScript/TypeScript:JSDoc @已棄用
JavaScript的
/**
* @deprecated Use fetchUserById() instead.
* This function will be removed in version 4.0.
*/
function getUser(id) {
// old implementation
}
// Modern replacement
async function fetchUserById(id) {
// new implementation
}
打字稿
class ApiClient {
/** @deprecated Use post() with typed options instead */
sendRequest(url: string): Promise<any> {
// deprecated implementation
}
}
IDE 顯示 getUser 凡是提到它的地方都加上刪除線,以及 TypeScript 的 @typescript-eslint/no-deprecated 規則會在 CI 中標記它。
C++:[[已棄用]] 屬性
CPP
// C++14 and later
[[deprecated("Use processV2() instead")]]
void process(int value) {
// old implementation
}
void processV2(int value, ProcessFlags flags = ProcessFlags::Default) {
// new implementation
}
編譯器產生:
warning: 'process' is deprecated: Use processV2() instead [-Wdeprecated-declarations]
Swift:@available 已棄用
迅速
@available(*, deprecated, renamed: "fetchUser(withID:)")
func getUser(id: String) -> User {
// old implementation
}
func fetchUser(withID id: String) -> User {
// replacement
}
Kotlin/Java:已棄用 @Deprecated,請使用 ReplaceWith
科特林
@Deprecated(
message = "Use processItems() instead",
replaceWith = ReplaceWith("processItems(items)"),
level = DeprecationLevel.WARNING
)
fun handleItems(items: List<Item>) {
// deprecated
}
fun processItems(items: List<Item>) {
// replacement
}
為什麼棄用程式碼會造成實際問題
已棄用的程式碼不僅僅是維護問題。它會在四個方面造成實際且不斷累積的風險:
安全漏洞。已棄用的 API 和程式庫不再接收安全性修補程式。如果某個已棄用的庫存在未修復的 CVE 漏洞,則表示該漏洞永久存在,維護者已停止修復,因為他們希望所有使用者都遷移到其他平台。運行已棄用元件的組織實際上是在選擇運行已知存在漏洞的程式碼。
升級時依賴關係中斷。之所以會發出棄用通知,正是因為該 API 即將被移除。當主版本升級發布並移除已棄用的 API 時,所有仍依賴該 API 的系統都會同時崩潰,而且是在最糟糕的時刻——原本應該是例行升級的過程中。
維護複雜性增加。已棄用的程式碼要求開發人員同時掌握兩種思維模式:舊程式碼的功能和等效新程式碼的功能。每個新團隊成員都必須學習應該避免使用哪些程式碼部分以及原因。這種雙軌並行的複雜性會隨著已棄用組件數量的增加而不斷累積。
技術債不斷累積。每個已棄用的依賴項都是一個技術債單位。與其他債務不同,已棄用代碼債務有期限,一旦已棄用的組件被實際移除,它就會從「警告」狀態變為「已損壞」狀態。
如何處理軟體專案中已棄用的依賴項
第一步:清點所有折舊
與其逐一尋找已棄用的組件,不如進行系統性的掃描。大多數工具都提供了顯示完整清單的方法:
打壞
# Python: find all DeprecationWarning instances
python -W error::DeprecationWarning -m pytest
# JavaScript/Node.js: run with deprecation tracing
node --trace-deprecation app.js
# Java: compile with full deprecation details
javac -Xlint:deprecation *.java
# npm: find deprecated packages
npm outdated
npm audit
步驟二:依風險分類
並非所有棄用都需要立即採取行動。請對每項棄用進行分類:
| 優先 | 標準 | 操作選項 |
|---|---|---|
| 危急 | 已棄用的安全關鍵型庫;已知 CVE 漏洞;將在下一個主要版本中移除 | 立即遷移 |
| 高 | 目前主要版本已棄用;CI 中存在活躍警告。 | 當前迭代或下一個迭代的日程安排 |
| 媒材 | 已棄用,但仍支援兩個以上主要版本;無安全風險 | 新增到待辦事項清單並附上時間表 |
| 低 | 內部程式碼中已棄用的註解,變更率較低 | 跟踪,並在相關重構期間處理 |
步驟 3:遷移前找出所有使用情況
在更改已棄用的組件之前,請先確定它的所有使用位置。如果沒有完整的使用映射,貿然變更可能會導致遺漏一些使用位置,從而造成系統靜默崩潰:
蟒蛇
# Using grep for basic search
grep -r "old_function" src/
# Using ast-grep for code-aware search (TypeScript/JS)
ast-grep --pattern 'getUser($ID)' --lang ts
# Using ripgrep with file type filtering
rg "deprecated_method" --type java
對於大型程式碼庫,自動化靜態分析工具可以產生比手動 grep 更準確的完整交叉引用映射,特別是對於透過動態分發或繼承的間接使用。
第四步:系統性遷移
逐一替換已棄用的用法,並在進行下一個之前驗證每個用法:
蟒蛇
# Before: deprecated
import imp
module = imp.load_source('mymodule', '/path/to/mymodule.py')
# After: replacement
import importlib.util
spec = importlib.util.spec_from_file_location('mymodule', '/path/to/mymodule.py')
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
JavaScript的
// Before: deprecated event property
document.addEventListener('keydown', (event) => {
const key = event.keyCode; // deprecated
});
// After: modern replacement
document.addEventListener('keydown', (event) => {
const key = event.key; // current standard
});
步驟 5:在 CI/CD 上新增棄用門控
防止清理後新的已棄用用法進入程式碼庫:
雅姆
# .github/workflows/deprecation-check.yml
- name: Check for deprecated API usage (Java)
run: javac -Xlint:deprecation -Werror src/**/*.java
- name: Check for deprecated packages (Node)
run: npm audit --audit-level=moderate
- name: ESLint no-deprecated rule (TypeScript)
run: npx eslint --rule '{"@typescript-eslint/no-deprecated": "error"}' src/
制定棄用政策
妥善處理棄用問題的組織會將其視為一項政策問題,而不僅僅是技術問題。棄用政策定義了:
誰有權利棄用某個組件?單一開發者不應在未經團隊審核的情況下單方面棄用廣泛使用的內部 API。棄用決策應由使用該組件的所有者共同參與。
棄用期持續多久?一個合理的預設值是:移除前一個主版本週期的通知期。對於公共 API,兩個主版本週期。對於內部 API,一個版本週期。
如何傳達棄用訊息?程式碼註解、變更日誌條目以及直接通知已知使用者。僅存在於程式碼註解中的棄用通知很容易被忽略。
「移除」究竟指什麼?是代碼被刪除?移至單獨的可選包?還是隱藏在功能開關之後?請明確定義最終狀態。
遷移路徑的記錄方式。 每個棄用註釋都應該包含對替代方案的引用。 @deprecated Use fetchUserById() instead 比 @deprecated.
已棄用的程式碼還能運作嗎?
是的,直到它不再運行為止。已棄用的程式碼會一直正常運行,直到它被正式移除的版本發佈為止。這正是已棄用程式碼最危險的特性:它會給人一種虛假的安全感。多年來一直使用已棄用 API 的系統可能看起來很穩定,但隨著每個版本更新,突然崩潰的風險也會增加。
對於「運行已棄用程式碼是否安全?」這個問題,答案取決於棄用日期臨近的程度以及該組件的安全狀況。對於一個仍在積極維護且沒有已知 CVE 漏洞的函式庫,如果某個函數在次要版本中被棄用,則其直接風險較低。而對於一個存在未修復漏洞且已公佈生命週期結束日期的已棄用身份驗證庫,則其直接風險較高。
SMART TS XL 識別企業系統中已棄用的代碼
在單一語言專案中,尋找已棄用的程式碼只需執行正確的編譯器標誌或程式碼檢查規則即可。但在涵蓋 COBOL、JCL、Java、Python 和現代服務的企業環境中,需要同時尋找每種語言的已棄用元件,而且它們之間的關係與棄用本身同樣重要。
SMART TS XL“ 靜態程式碼分析 它會掃描環境中的每種語言,並同時在整個程式碼庫中找出已棄用的註解、過時的 API 用法和死程式碼模式。當一個 COBOL 副本被標記為過時時, SMART TS XL 識別所有包含該 API 的程式。當 Java API 方法被棄用時,它會追蹤整個服務組合中所有呼叫該方法的記錄。
影響分析功能更進一步:在移除任何已棄用的元件之前,它會產生移除操作將影響的完整範圍,包括哪些程式、哪些作業流程、哪些下游服務,以及所有程式語言。這把原本充滿風險的「哪些功能會出問題?」的問題,轉化為一個結構化的枚舉列表,列出了移除操作之前需要驗證的所有內容。
企業級搜尋功能讓清單可查詢:只需幾秒鐘,即可在數百萬行多種語言的程式碼中,找到特定已棄用函數的每一次使用、對已棄用副本的每一次引用、對過時 API 的每一次呼叫。對於以已棄用元件清單作為確定遷移範圍起點的傳統系統現代化項目而言,此搜尋功能可將數週的手動審核工作簡化為一次精準的查詢。