《實作模式》Podcast 準備稿:演進中的框架
書名: 實作模式 Implementation Patterns 作者: Kent Beck 系列: 設計品味系列(程式碼層次的設計直覺) 公開標題建議: 設計品味系列|Implementation Patterns.最終回:當「簡單至上」翻轉成「演進自由至上」(6/6) 涵蓋範圍: 第 10 章「演進中的框架 Evolving Frameworks」
背景速覽
這是全系列最終回。前面五集講的所有東西——溝通優先、簡單至上、別過度設計、最小化暴露——都建立在同一個假設上:改程式碼的成本,遠低於理解程式碼的成本。這最後一章把這個假設整個掀翻:框架開發違反了它。因為框架作者改不了下游客戶的程式碼。當你不能要求成千上萬的使用者跟著你改,整套價值觀就要重新洗牌。這一章是用 Kent Beck 親手做 JUnit 4 的真實血淚換來的。
一句話重點
寫應用程式,簡單至上;但寫框架,「保留未來演進的自由」凌駕於簡單之上——你會刻意增加複雜度、刻意把東西藏得更死,只為了哪天能改設計而不弄爆所有客戶。
值得討論的重點
重點一:價值觀的大翻轉——複雜度從敵人變成工具
前五集 Kent Beck 一直把複雜度當頭號敵人,把「簡單」奉為最高指令。這一章他親口說:在框架開發中,有時「增加複雜度」反而更划算——只要它換來「框架作者能在不弄壞客戶端的前提下改進框架」的能力。
為什麼?因為主導成本的因素變了:
- 應用程式開發,主導成本是「理解程式碼」→ 所以追求簡單、易讀。
- 框架開發,主導成本是「升級客戶端」→ 所以追求向後相容、演進自由。
一個最具體的例子:寫一般程式碼,Kent Beck 傾向把欄位設成 protected(方便子類別用);但寫框架,他把欄位設成 private。因為 protected 欄位讓客戶更好用 superclass,卻把資料結構鎖死了——private 才能讓框架作者哪天自由改資料結構而不影響任何客戶。「方便客戶用」和「保留演進空間」直接衝突,而框架選後者。
這個翻轉是整章、甚至整本書的高潮。它告訴你一件深刻的事:沒有絕對的「好設計」,只有「在某個成本結構下的好設計」。 換了賽局,規則就變。
重點二:框架是「交集」,不是「聯集」
這是 Kent Beck 給框架設計者最重要的一條心法。他說 JUnit 裡一個很有效的隱喻是:把框架看成「一個領域裡所有有用功能的交集,而不是聯集」。
數學一下:假設每個潛在使用者有 90% 的需求是共通的、10% 是獨特的。如果你想做一個「滿足所有人所有需求」的框架(聯集),它會大得嚇人、複雜到沒人能用。正確的做法是:框架只滿足共同的 90%,然後確保客戶能擴展框架去解決剩下那獨特的 10%。
判準很簡單:如果大多數使用者都得自己加同一個功能,那它屬於框架;如果某個需求是個別情境獨有的,就交給有直接需求的人自己處理。
他還補了一個方法論:從多個具體實例衍生框架,別從通用情境憑空設計。他自己是寫了「半打」自動化測試嘗試之後,才從「反覆寫同樣的程式碼」的經驗裡,看出哪些問題是所有測試共通的、哪些是個別獨有的。這跟前幾集「基於事實而非推測」「先讓它跑再 make it right」是同一條鐵律——好的抽象是「萃取」出來的,不是「發明」出來的。
重點三:不相容升級的減痛工具箱
理想是「新增功能而不破壞任何舊功能」,但這不總是做得到。當你非得做不相容升級,Kent Beck 給了一整套減痛策略,這些對任何維護公開 API / 函式庫 / 內部共用模組的人都極實用:
- Deprecation(棄用):標記為棄用但保持運作一兩個版本,給客戶「該遷移了」的訊號和緩衝時間。本質上是「平行維護新舊兩套架構」。
- Package 版本化:在新 package 用相同的類別名(
org.junit.newandimproved.Assert),客戶只要改 import,不用改邏輯——改 import 比改程式碼風險低太多。 - API 和實作分開改:同一個版本只改一邊,給中間版本讓各方適應方向,趁問題還小時解決。
- 預設參數維持相容:要給已發佈的方法加參數,就保留舊方法、讓它帶預設值呼叫新方法——介面新增了,舊客戶程式碼照樣跑。
- 自動化升級工具:Eclipse 的招——直接提供工具幫客戶把程式碼從 2.x 升到 3.0。
背後的數據很有說服力:JUnit 4 發佈時,Kent Beck 團隊花了將近一半的工程預算,只為了降低客戶端的部署成本——確保新測試能配舊工具、舊測試能配新工具。一半的預算花在「相容性」而不是「新功能」上,這個數字本身就說明了框架開發的成本結構有多不同。
重點四:三種使用風格,以及「該用 interface 還是 superclass」
框架怎麼讓客戶用?Kent Beck 整理出三種風格,複雜度和彈性遞增:
- Instantiation(實例化):最簡單,客戶
new一個物件來用。只處理「資料變化」。 - Configuration(配置):客戶建框架物件、傳入自己的物件(像
TreeSet配自訂Comparator)。能處理「邏輯變化」,但只撐得住一兩個維度,選項一多就爆炸。 - Implementation(實作):客戶 extends 框架類別或 implements 框架介面。最彈性(能處理任意多個獨立變化點),但最可能限制你未來的設計自由——superclass / interface 的每個細節都得永遠保留。
而「該用 interface 還是 superclass」這個經典問題,在框架語境有了明確答案:
- interface 暴露細節最少,客戶不會「意外」依賴超出你本意的東西;但新增方法會炸掉所有實作者。
- superclass 能多給細節,而且新增方法不會破壞既有程式碼(只要給得出預設實作);但客戶只能繼承一個。
Kent Beck 的判準:頻繁變化的抽象,用 superclass(因為能安全加方法);不太變、且需要多重繼承的,用 interface。這跟一般「面向介面編程」的口號剛好有個微妙的反轉——在框架裡,interface 的「脆弱性」是個真實的成本。
注意事項
⚠️ 這一章最容易被誤用的方式,是普通工程師拿框架的標準來要求應用程式碼。Kent Beck 講得很清楚:這套「增加複雜度換演進自由」的價值觀,只在你真的在做框架(你改不了下游)時才成立。如果你在寫一個三人小團隊內部、隨時能全域重構的應用程式,你卻把欄位全設 private、到處加 factory object、搞 versioned interface——那是把框架的稅金,白白付在不需要它的地方。先問:我到底有沒有「改不了客戶端」這個約束? 沒有的話,回去用前五集的簡單至上。
⚠️ 書裡很多具體機制(checked exception 的相容、AWT 的 LayoutManager2 版本化介面、Java package 的 internal 命名約定)是 2007 年 Java 生態的產物。今天有 Java 模組系統(JPMS)、SemVer 規範、套件管理器(Maven/Gradle 的版本解析)、乃至於整個 API 演進的工具鏈,把當年要手刻的東西工程化了。Kent Beck 指出的「問題」全都還在(怎麼演進而不破壞客戶),但「解法」的工具箱已經豐富很多。聽眾要學的是那套思考框架,不是逐字照搬 2007 年的招式。
⚠️ 「框架是交集而非聯集」這條心法極對,但別忘了它有個前提——你得真的有多個具體實例可以萃取共通性。現實中很多「框架」是在只有一個使用者(常常就是作者自己)的時候就過早抽象出來的,結果抽出來的「共同需求」其實只是「我這一個案例的需求」,別人根本用不上。這是 Kent Beck「從半打實例衍生」這句話的反面警告:樣本數不夠就抽框架,你抽到的是幻覺,不是交集。
專家補充
💡 這一章其實是整本書的「元命題」現身:設計沒有銀彈,只有取捨,而取捨取決於成本結構。 前五集你可能以為 Kent Beck 在傳教「簡單至上」,結果最後一章他親手證明——當賽局變了(從應用變框架),連他最珍視的「簡單」都要讓位。這是成熟工程師和初學者最大的差別:初學者背規則,成熟者問「這條規則在我這個成本結構下還成立嗎」。整本書讀到這裡,真正的收穫不是 77 個模式,而是這個「先看賽局再選規則」的後設能力。
💡 「框架是交集不是聯集」這個洞見,可以接到一個更大的軟體史教訓——過度通用化是框架的頭號死因。軟體史上一堆雄心勃勃、想滿足所有人的框架(EJB、早期的各種企業框架)最後都因為太重太複雜而被輕量替代品淘汰(Spring 之於 EJB)。而活下來的框架往往是「先解決一個具體痛點、再小心地擴展」。Kent Beck 的 JUnit 本身就是最佳範例:它一開始極簡,靠「滿足共同需求、把獨特需求留給客戶擴展」活了二十幾年。Gall’s Law 說「能運作的複雜系統,無一例外都是從能運作的簡單系統演化來的」——這一章就是它的程式碼版註腳。
💡 Kent Beck「花一半預算在相容性」這件事,在今天的開源世界有了制度化的對應——語意化版本(SemVer)。major.minor.patch 這套約定,本質上就是把「這次升級會不會弄壞你」這個資訊,編碼進版本號裡,讓上下游能自動協調。Kent Beck 在書裡手動操心的「向前/向後相容」「什麼時候強迫客戶升級」,現在被 SemVer + 套件管理器 + CI 自動化了一大半。但核心的工程倫理沒變:你發佈了一個公開介面,你就對所有依賴它的人負起了責任。 這是「public 方法等於一份維護承諾」(第八集的觀點)在框架尺度的放大。
討論問題
🎙️ 你手上有沒有一個被當成「框架/共用模組」在維護的東西?它真的有「改不了下游客戶」這個約束嗎?還是你其實隨時能全域重構,卻給自己上了框架的枷鎖、付了不必要的複雜度稅?
🎙️ 用「交集 vs 聯集」檢查你維護的共用程式碼:它是不是想滿足每個使用者的每個需求(聯集),結果大到沒人愛用?哪些功能其實只有一兩個使用者要,該被踢出去交給他們自己處理?
🎙️ 你上一次做「破壞性 API 變更」,有沒有給下游緩衝(deprecation、平行版本、自動升級工具)?還是直接一刀切、讓所有人一夜之間都壞掉?Kent Beck 花一半預算在相容性——你願意花多少?
🎙️ 整本書讀到這:如果連 Kent Beck 最珍視的「簡單」都會在框架語境讓位,那你心裡有沒有哪條「我一直當成鐵律」的設計原則,其實只在某個特定成本結構下才成立?
更大範圍關聯
-
接整本書 & 第一集:這集是首尾呼應的關門。第一集立下「價值觀→原則→模式」三層、簡單至上;這集證明價值觀本身會隨賽局翻轉。把這兩集對照聽,才看得到 Kent Beck 真正想教的不是模式,是「依成本結構選規則」的後設判斷力。
-
接《Clean Architecture》(Robert C. Martin):Uncle Bob 那本從另一個尺度談「如何保留變更的自由」——用依賴規則和邊界把易變的細節隔離在外。框架開發的「private 欄位、最小暴露、版本化介面」其實是同一個directive在 API 尺度的具現:把你想保留改變權的東西藏在穩定的介面後面。兩本講的是同一種恐懼(被自己的決定鎖死),不同尺度的解法。
-
接《Domain-Driven Design》& Gall’s Law:「從多個具體實例萃取框架」「框架是交集」呼應 DDD 的「別過早抽象、讓模型從真實領域長出來」,也呼應 Gall’s Law「複雜系統都是從簡單系統演化來的」。這條線可以拉成一個獨立的大主題:好的抽象是萃取的,不是發明的。
-
接 SemVer 與現代開源治理:這集手動操心的相容性問題,今天被語意化版本、套件管理器、CI 自動化制度化了。可以把這集當成「為什麼開源世界要有 SemVer、為什麼 breaking change 這麼被嚴肅對待」的思想源頭之一。
錄製建議
目標約 20 分鐘,單人主講。建議節奏:
- 0–3 分:最終回開場 + 製造反轉的張力。一句話回顧「前五集所有東西都建立在『改程式碼比讀程式碼便宜』這個假設上」,然後丟出鉤子:「這一章,Kent Beck 要親手把這個假設掀翻。」
- 3–9 分:價值觀大翻轉(本集核心)。講透「為什麼框架要犧牲簡單換演進自由」,用
protected → private那個具體例子落地。敲重點:沒有絕對的好設計,只有某個成本結構下的好設計。這是整本書的高潮,要講得有重量。 - 9–13 分:框架是交集不是聯集。用 90%/10% 的數學講清楚,帶「從半打實例萃取、別憑空設計」,接到「過度通用化是框架死因」的軟體史教訓。
- 13–17 分:減痛工具箱 + 風格選擇。快速過 deprecation / package 版本化 / 預設參數,用「JUnit 4 花一半預算在相容性」這個數字製造記憶點。interface vs superclass 的反轉(框架裡 interface 是脆弱的)點到即可。
- 17–20 分:辯證 + 全系列收尾。最重要的辯證:別把框架的稅付在應用程式上——先問你有沒有那個約束。最後拉高:整本書真正教的不是 77 個模式,是「先看賽局再選規則」的後設能力。為整個系列畫下句點。
口氣提醒:這是最終回,情緒要往上走。前半段用「反轉、掀翻假設」製造戲劇性,結尾把六集串成一條線——從「程式碼是寫給人看的」一路走到「連這條都有例外」,讓聽眾感覺走完了一趟完整的思想旅程,而不只是聽完第六個模式清單。