Deprecation#
作者:Hyrum Wright |編輯:Tom Manshreck
I love deadlines. I like the whooshing sound they make as they fly by. —Douglas Adams
所有系統都會老化。即使軟體是數位資產、位元本身不會腐壞,但新技術、函式庫、語言和環境的變遷會讓既有系統變得過時(obsolete)。老舊系統與周遭生態系統漸行漸遠,需要持續的營運資源與冷門的專業知識來維護。因此,投入心力有序地淘汰過時系統,往往比讓它們無限期地苟延殘喘更為划算。Google 將這種有計畫地遷移並最終移除過時系統的過程稱為 deprecation。
Deprecation 屬於軟體工程(而非單純的程式設計)的範疇,因為它要求我們思考如何在時間尺度上管理系統。對長期運行的軟體生態系而言,正確規劃與執行 deprecation 能降低資源成本、減少冗餘與複雜度,進而提升開發速度。反之,草率的 deprecation 可能比什麼都不做還糟。Deprecation 的影響範圍從單一函式呼叫到整個軟體堆疊都有可能。
本章不討論面向終端使用者的產品下架,而是聚焦在技術系統的 deprecation——系統擁有者對其使用情況具有可見性的場景。Google 仍在持續摸索最佳的 deprecation 實踐方式,本章描述的是從大型且被廣泛使用的內部系統的淘汰中學到的經驗教訓。
為何需要 Deprecation?#
本章的核心前提是:程式碼是負債,不是資產。程式碼本身不帶來價值,帶來價值的是它提供的功能(functionality)。如果同樣的功能能用一行可維護、可理解的程式碼達成,而非一萬行義大利麵式的複雜程式碼,我們當然偏好前者。
與其關注我們能產出多少程式碼或程式碼庫有多大,不如關注每單位程式碼所能交付的功能,並嘗試最大化這個指標。達成這個目標最簡單的方式之一,不是寫更多程式碼,而是移除不再需要的多餘程式碼和系統。Deprecation 的政策和程序使這成為可能。
Deprecation 最適合用在以下情境:
- 系統已明確過時,且存在提供相當功能的替代方案
- 新系統在資源效率、安全性、可維護性或正確性上有顯著優勢
- 同時維護兩套功能重疊的系統,長期成本會持續攀升
單純年代久遠並不構成 deprecate 的理由。像 LaTeX 排版系統這樣經過數十年持續改良的軟體,雖然古老但並不過時。
當兩個系統並存時,它們可能需要相互介接(interoperate),產生複雜的轉換邏輯;彼此演化時也可能形成相互依賴,使最終移除變得更加困難。更重要的是,舊系統的存在會拖累新系統的演化,因為新系統仍被期望維持與舊系統的相容性。花費心力移除舊系統,能讓替代系統更快速地演進。
Google 發現組織能同時進行的 deprecation 工作量是有限的。就像市政府不會同時封閉所有道路來重鋪柏油一樣,選擇 deprecation 專案時應謹慎,一旦啟動就要承諾完成。
為何 Deprecation 如此困難?#
Hyrum 定律的作用#
使用者越多,就越可能有人以出乎意料的方式使用系統——他們的用法「恰好能運作」(happens to work)而非「保證能運作」(guaranteed to work)。移除一個系統可以視為終極變更(the ultimate change):不只是改變行為,而是完全消除行為,這會震出大量意想不到的依賴者。
功能差異#
在舊系統被 deprecate 之前,通常需要有一個更好的替代系統。但「更好」也意味著「不同」——如果新系統與舊系統完全相同,對遷移的使用者而言就不會有任何好處(雖然對營運團隊可能有益)。這種功能差異意味著舊系統與新系統之間很少能一對一對應,每一處對舊系統的使用都必須在新系統的脈絡下重新評估。
情感依附#
工程師對自己花數年心血打造的系統會產生情感依附(change aversion)。Google 內部在系統性移除舊程式碼時,偶爾會遭遇「我喜歡這段程式碼!」的阻力。這是可以理解的反應,但最終是自我挫敗的:如果系統已經過時,它對組織是淨成本,應該被移除。
應對方式之一是確保原始碼倉庫不僅可搜尋目前版本,也能搜尋歷史版本——被移除的程式碼仍然找得到(參見第十七章)。
Google 內部有個老笑話:做事永遠只有兩種方式——已被 deprecated 的,和還沒準備好的。這反映出在快速變遷的技術環境中,新舊系統交替期的現實。好的文件、清楚的路標,以及協助遷移的專家團隊,能讓人更容易判斷該用舊系統還是新系統。
政治與資金#
為 deprecation 努力取得資金和執行力在政治上可能很困難。為一個團隊配備人力並花時間移除過時系統,需要真金白銀的投入;而什麼都不做、讓系統無人看管地繼續運行的成本卻不容易被觀察到。要說服相關利害關係人 deprecation 工作是值得的可能很困難,特別是當它會對新功能開發產生負面影響時。研究技術(如第七章討論的方法)可以提供具體證據來證明 deprecation 的價值。
漸進式替代#
鑑於 deprecation 和移除過時軟體系統的困難,使用者往往更容易選擇就地演化(evolve in situ)一個系統,而非完全替換它。漸進式做法不能完全避免 deprecation 流程,但它將流程拆解為更小、更可管理的區塊,在每個階段都能產生漸進式效益。
Google 觀察到,完全遷移到全新系統的成本極高且經常被低估。透過 in-place refactoring 進行漸進式 deprecation,可以在既有系統持續運作的同時,更容易地向使用者交付價值。
在設計階段考慮 Deprecation#
如同許多工程活動一樣,軟體系統的 deprecation 可以在系統初建時就納入規劃。程式語言的選擇、軟體架構、團隊組成,甚至公司政策與文化,都會影響系統在其使用壽命結束後能多容易地被移除。
在軟體工程中,「為最終淘汰而設計」的概念可能顯得激進,但在其他工程領域卻很常見。以核電廠為例——作為極其複雜的工程系統,其設計時就必須考慮經過數十年生產服務後的除役(decommissioning),甚至預先撥款。許多核電廠的設計決策都受到最終需要除役這個事實的影響。
然而,軟體系統很少如此深思熟慮地設計。許多軟體工程師受到建構和發布新系統的吸引,而非維護既有系統。許多公司(包括 Google)的企業文化強調快速建構和發布新產品,這往往不利於從一開始就考慮 deprecation。儘管軟體工程師被認為是數據驅動的,要規劃自己辛苦建造之物的最終消亡,在心理上仍然是困難的。
Google 鼓勵工程團隊在設計階段思考以下問題:
- 使用者從我的產品遷移到潛在替代方案,有多容易?
- 我能否漸進地替換系統的各個部分?
這些問題大多與系統如何提供與消費依賴(dependencies)有關(詳見第十六章)。
最後,值得指出的是,決定是否長期支持一個專案,是在組織最初決定建立該專案時做出的。一旦軟體系統存在,僅剩的選項就是支持它、謹慎地 deprecate 它、或者在某個外部事件導致它崩壞時放任它停止運作。
當組織決定建立一個專案時,就等於做出了是否長期支持它的決定。專案建立後僅剩三個選項:持續支持、謹慎 deprecate,或放任它在外部事件中崩壞。這三者都是合理的選項,取捨取決於組織的具體情境。但請不要啟動組織沒有承諾在其預期壽命內支持的專案。
Deprecation 的類型#
Deprecation 並非單一流程,而是一個連續光譜,從「希望有一天能關掉」到「明天就要關掉,使用者最好準備好了」。大致可分為兩類:advisory(建議性)和 compulsory(強制性)。
Advisory Deprecation(建議性淘汰)#
Advisory deprecation 是指沒有截止日期、不是組織的高優先事項,組織也不打算為其投入專門資源的淘汰。也可以稱之為「aspirational deprecation」——團隊知道系統已被替代,希望客戶最終能遷移到新系統,但並沒有迫切的計畫來提供遷移協助或刪除舊系統。
這類 deprecation 的特性:
- 往往缺乏強制力:我們希望客戶遷移,但無法強迫他們
- 適合用來宣傳新系統的存在,鼓勵早期採用者嘗試
- 新系統應已達生產可用狀態,能支援生產環境的使用量和負載,並準備好無限期地支援新使用者,而非處於 beta 階段。當然,任何新系統都會經歷成長的陣痛,但在舊系統以任何方式被 deprecated 之後,新系統就會成為組織基礎設施的關鍵組件
- 當新系統提供變革性(transformative,而非僅僅漸進性)的好處時效果最佳。使用者不會為了邊際利益自行遷移,即使有巨大改進的新系統,僅靠建議性淘汰也不會獲得完全採用
Google 在 advisory deprecation 方面觀察到一個有趣的場景:當新系統提供令人信服的好處時,僅僅通知使用者新系統的存在並提供自助遷移工具,往往就能鼓勵採用。然而,這些好處不能僅僅是漸進性的——它們必須是變革性的。
儘管如此,Google 的經驗表明,僅靠建議性淘汰很少能讓團隊主動遷離舊系統。舊系統的大量既有使用會產生一種「概念引力」(conceptual pull)——無論我們多麼強調「請使用新系統」,舊系統的龐大使用基數總會吸引大量新的使用者。舊系統將持續需要維護和其他資源,除非其使用者被更積極地鼓勵遷移。
單純貼上 deprecation 警告然後放手不管,通常只能略微減少新的使用,但很難促成既有使用者的遷移。正如 SRE 同事們經常說的:“Hope is not a strategy.”
Compulsory Deprecation(強制性淘汰)#
更積極的鼓勵來自強制性淘汰。這類 deprecation 通常伴隨一個移除截止日期:如果使用者在該日期之後仍依賴舊系統,他們自己的系統將停止運作。
強制性淘汰的關鍵實踐:
- 集中專業知識:將遷移專業知識集中在一個專責團隊,通常就是負責移除舊系統的團隊。該團隊有動機協助他人遷移,並能開發可跨組織重複使用的工具(參見第二十二章)
- 執行機制(enforcement mechanism):時程表必須有強制力。這不意味著時程不能調整,而是授權 deprecation 團隊在充分警告後中斷不合規的使用者。沒有這個權力,客戶團隊很容易為了功能開發等更緊迫的工作而忽視 deprecation
- 配備人力:沒有配備人力的強制性淘汰會被客戶團隊視為未獲資助的任務指令(unfunded mandate),產生「原地跑步」(running to stay in place)的感覺,造成基礎設施維護者與客戶之間的摩擦
值得注意的是,即使有政策的支持,強制性淘汰仍可能面臨政治阻礙。想像一下:舊系統的最後一個使用者是整個組織都依賴的關鍵基礎設施。你願意為了遵守一個任意的截止日期而中斷那個基礎設施——以及間接中斷所有依賴它的人嗎?如果那個團隊可以否決 deprecation 的進程,就很難相信這個 deprecation 真的是「強制性」的。
Google 的單一程式碼倉庫(monolithic repository)和依賴圖提供了對系統使用方式的深入洞察,但某些團隊可能甚至不知道自己依賴了一個過時系統。為了發現這些隱藏的依賴,Google 會在系統正式移除前的數週到數月內,安排逐步延長的計畫性停機,類似災難復原演練(DiRT exercises)。這些事件能發現未知的執行期依賴關係,讓相關團隊有時間發現並準備。Google 也會偶爾更改僅供內部實作使用的符號名稱,以發現哪些使用者在不知情的情況下依賴它們。
對於靜態程式碼依賴,靜態分析工具所提供的語義資訊通常足以偵測過時系統的所有依賴。
Deprecation 警告#
無論是建議性或強制性淘汰,以程式化方式標記系統為 deprecated 以警告使用者都很有用。但要記住,只是標記為 deprecated 然後期望使用自然消失是不夠的。Deprecation 警告能幫助防止新的使用,但很少能促成既有系統的遷移。
實務上,這些警告會隨時間累積。如果在傳遞性的脈絡中使用(例如 library A 依賴 B,B 依賴 C,C 發出警告,而警告在建構 A 時出現),這些警告很快就會淹沒使用者,使他們完全忽視。在醫療領域,這個現象稱為警報疲勞(alert fatigue)。
任何發送給使用者的 deprecation 警告都必須具備兩個屬性:
- 可行動性(Actionability):警告必須告訴使用者具體該怎麼做——例如將某個函式呼叫替換為其更新版本、或列出將資料從舊系統遷移到新系統的步驟。而且這些步驟必須是以一般工程師的專業水平就能實際執行的
- 相關性(Relevance):警告必須在使用者實際執行相關操作時出現。在工程師正在撰寫使用 deprecated 函式的程式碼時發出警告,遠比程式碼已提交到倉庫數週後才通知有效。同樣地,資料遷移的郵件應在舊系統移除前數月發送,而非移除前一個週末才匆忙通知
要抵抗「對所有東西都貼上 deprecation 警告」的衝動。天真的工具往往產出大量警告訊息,導致工程師疲勞(alert fatigue),最終完全忽略這些警告。
Google 的做法是:
- 使用 ErrorProne 和 clang-tidy 等工具,將警告精準地呈現在新變更的程式碼行上(如第二十章所述),避免一次性淹沒使用者
- 更具侵入性的警告(如依賴圖中的 deprecated targets)僅在強制性淘汰時才啟用,且團隊正在積極遷移使用者
- 在有限的情況下,工具還會建議一鍵修復(push-button fix)以遷移到建議的替代方案
工具在適當的時間向適當的人呈現適當的資訊方面扮演著關鍵角色,讓更多警告得以加入而不會造成使用者疲勞。
管理 Deprecation 流程#
雖然 deprecation 專案感覺上與建構新系統不同(因為我們在解構而非建構),但在管理方式上其實是相似的。以下是幾個值得注意的差異。
流程負責人(Process Owners)#
沒有明確的負責人,deprecation 流程不太可能取得實質進展,無論系統產生多少警告和告警。Google 的經驗表明,設置專職管理和執行 deprecation 流程的專案負責人看似浪費資源,但替代方案更糟:
- 永遠不淘汰任何東西 → 承諾無限期維護所有舊系統
- 把淘汰工作委託給使用者 → 實質上退化為 advisory deprecation,永遠不會自然完成
被遺棄的專案(仍在使用但無人明確擁有或維護)在建立負責人和對齊激勵機制時特別棘手。這些專案有時進入這種狀態正是因為它們被 deprecate 了:原始擁有者已轉移到後繼專案,留下過時的專案在地下室繼續運轉,仍然是某個關鍵專案的依賴,期望它最終自動消失。
這類專案不太可能自行消失。儘管我們抱持最好的期望,這些專案仍需要 deprecation 專家來移除,以防止它們在不當的時機故障。這些團隊應以移除為首要目標,而非其他工作的附帶專案。這類重要但不緊急的清理工作,是 20% 時間的絕佳用途,也能讓工程師接觸程式碼庫的其他部分。
里程碑(Milestones)#
建構新系統時,專案里程碑通常很明確:「在下一季推出 frobnazzer 功能。」遵循漸進式開發實踐,團隊逐步向使用者交付功能,使用者每使用一個新功能就是一次勝利。最終目標可能是推出整個系統,但漸進式里程碑幫助團隊感受到進展,並確保他們不需要等到流程結束才為組織創造價值。
相比之下,deprecation 專案容易讓人覺得唯一的里程碑就是完全移除舊系統。團隊可能覺得在關燈離開之前沒有取得任何進展。但諷刺的是,如果團隊做得正確,這一步往往是外部最不會注意到的——因為到那個時候,舊系統已經沒有任何使用者了。
Deprecation 專案管理者應避免將完全移除作為唯一可衡量的里程碑,尤其考慮到某些 deprecation 專案可能根本不會走到那一步。與建構新系統類似,管理 deprecation 團隊應設定具體的漸進式里程碑,這些里程碑是可衡量的且能向使用者交付價值。用於評估 deprecation 進度的指標會與建構新系統時不同,但在 deprecation 流程中認可適當的漸進式成就同樣重要:
- 刪除一個關鍵子元件
- 將特定客群遷移完畢
- 清理特定模組的依賴
如同建造新系統一樣,認可和慶祝漸進式成就有助於維持團隊士氣。
Deprecation 工具#
用於管理 deprecation 流程的工具大多在本書其他章節中有詳細討論,例如大規模變更(LSC)流程(第二十二章)和程式碼審查工具(第十九章)。這些工具可分為三類:
發現(Discovery):在 deprecation 流程的早期階段乃至整個過程中,了解過時系統被誰使用、以何種方式使用都是至關重要的。初期的大量工作是確定誰在使用舊系統,以及以哪些未預期的方式使用。根據使用的類型,可能需要在獲得新資訊後重新審視 deprecation 的決定。Google 使用 Code Search(第十七章)和 Kythe(第二十三章)靜態確定哪些客戶使用了特定函式庫,並抽樣檢視既有使用以了解客戶出乎意料地依賴哪些行為。由於執行期依賴通常需要某些靜態函式庫或精簡客戶端的使用,這項技術能產出啟動和執行 deprecation 流程所需的大部分資訊。生產環境的日誌與執行時期取樣則幫助發現動態依賴的問題。最後,全域測試套件作為「神諭」(oracle),用於判斷所有對舊符號的參考是否已被移除——如第十一章所述,測試是防止系統在生態系演化時產生非預期行為變更的機制,客戶有責任維護充分的測試,以確保過時系統的移除不會傷害自己
遷移(Migration):利用大規模變更(LSC)流程與程式碼生成和審查工具,實際更新程式碼庫以指向新的函式庫或執行時期服務
防止回退(Preventing Backsliding):這是一個經常被忽視但至關重要的基礎設施。即使是建議性淘汰,警告使用者在撰寫新程式碼時避開 deprecated 系統也是有用的。沒有回退防護,deprecation 會變成一場打地鼠遊戲(whack-a-mole)——使用者不斷加入他們熟悉的舊系統呼叫,deprecation 團隊不斷遷移這些新使用。在微觀層面,Google 使用 Tricorder 靜態分析框架在程式碼審查時通知使用者正在加入 deprecated 系統的呼叫。deprecated 系統的擁有者可以添加編譯器註解(如 Java 的
@deprecatedannotation),Tricorder 會在審查時呈現這些符號的新使用,讓 deprecated 系統的團隊控制訊息內容,同時自動提醒變更作者。在有限的情況下,工具也會建議一鍵修復(push-button fix)以遷移到替代方案。在巨觀層面,Google 利用建構系統的可見性白名單(visibility whitelists)確保不會引入對 deprecated 系統的新依賴。自動化工具會定期檢查並修剪這些白名單,隨著依賴系統逐漸遷離過時系統
結論#
Deprecation 就像馬戲遊行過後清掃街道的髒活,但這些努力透過降低維護負擔和工程師的認知負荷,改善了整體軟體生態系。
可擴展地維護複雜軟體系統,不僅是建構和運行軟體,也必須能夠移除過時或不再使用的系統。
完整的 deprecation 流程需要透過政策與工具成功管理社會面和技術面的挑戰。以有組織、良好管理的方式進行 deprecation,雖然常被忽視為組織效益的來源,但對組織的長期永續發展至關重要。
TL;DRs#
- 軟體系統有持續的維護成本,應與移除它們的成本進行權衡
- 移除系統往往比建構它更困難,因為既有使用者常以超出原始設計的方式使用系統
- 考量關閉成本後,就地演化(evolving in place)通常比替換為全新系統更經濟
- 誠實評估 deprecation 的成本很困難:除了維護舊系統的直接成本,還有生態系成本——多個功能相似的系統需要共存和互通,舊系統可能隱性地拖累新系統的功能開發。這些生態系成本分散且難以衡量,deprecation 與移除的成本同樣如此