藍圖、邊界與壓力測試:《Clean Code》的結構設計哲學

Robert C. Martin《Clean Code》第二部分讀後整理:物件 vs 資料結構、錯誤處理、系統邊界、類別設計,串起一條主線——讓修改的代價永遠在可控範圍之內。

6 min read 來自《Clean Code》

整潔程式碼的核心不在優雅,而在讓修改的代價永遠在可控範圍之內。

軟體開發者常把程式碼比作建築,但 Robert C. Martin 在《Clean Code》裡用的比喻更精確——是城市。城市需要藍圖,藍圖決定每一塊土地的職責;設計錯了,擴建就是災難。本書第二部分從物件與資料結構的哲學之爭出發,橫跨錯誤處理、系統邊界,一路延伸到類別設計原則,串起的是同一個問題:如何讓程式碼在面對變化時不崩潰?

物件與資料結構:一場設計哲學的反對稱

書中提出一個常被忽略的對立:物件導向設計與結構化設計,在應對變化時的優勢是鏡像翻轉的。

資料結構將資料公開、不隱藏細節,好處是新增操作行為(函式)時完全不需要碰既有資料結構;壞處是一旦資料型態要改,所有相關函式都得跟著動。物件則反過來——封裝隱藏資料,只暴露行為介面,新增資料型態(類別)時不需要修改既有函式,但一旦要新增新行為,所有類別都得同步更新。

這種反對稱性有個直接的設計含義:選擇哪條路,取決於你預期哪個維度的變更更頻繁。 行為邏輯穩定、資料型別需要持續擴充?用物件。資料結構穩定、操作行為需要頻繁迭代?用資料結構。沒有一種設計天生優越,只有對錯誤維度的選擇。

錯誤處理:把混亂隔離在邊界之內

世界是不完美的,資料也是。程式碼終究會遇到錯誤,問題是如何處理錯誤而不讓它污染主要邏輯。

書中論述 try-catch-finally 有點像一種「交易」的概念——無論過程發生什麼,catch 都會讓程式維持在一個可預期的狀態。這個結構強迫工程師先思考邊界行為,是一種讓異常路徑顯式化的設計紀律。

除了結構,丟出例外時還需要附上足夠的 context:錯誤發生的位置、原因、狀態。維護者在事後排查問題時,context 的品質直接決定偵錯效率。書中也建議使用有意義的例外類別來歸類錯誤——例如 socket timeout 與 connection refused,在業務邏輯層面可能都是同一種情境(port device failure),不必讓呼叫端逐一處理每種技術細節。

null 的問題則是另一個層次的混亂來源。書中對 null 的定性很精準:它是一個含糊的回應,有值但沒有任何意義。作者提到,不應該回傳 null,也不應該傳遞 null。如果程式碼中到處都是 null 檢查,那通常是 API 設計出了問題的訊號,而非個別使用者的失誤。整潔的錯誤處理邏輯讓主要邏輯保持乾淨,錯誤本身被統一集中管理。

系統邊界:控制你控制不了的東西

現代軟體開發幾乎沒有人從零打造所有東西。引入第三方函式庫的代價是:它們提供泛用性,但我們的系統要解決的是特定問題——兩者的目標天生存在張力。

書中提出的應對策略之一是**學習測試(learning tests)**的概念:與其花大量時間閱讀文件,不如直接針對你想使用的功能撰寫測試。這些測試只驗證你關注的行為,過濾掉不需要的雜訊;當第三方函式庫發布新版本時,執行這些測試就能立刻得知新版本是否破壞了你的使用方式。

更重要的是封裝的態度。書中論述,不應該把第三方物件直接暴露給整個系統使用,而應該將它包裝在自己的類別中,只開放業務邏輯實際需要的介面。例如,不應該讓 Map 物件在系統中四處流通,而是建立一個 Sensors 類別,把 Map 收進去、只開放必要的操作。

另一個工具是 Adapter 模式:先定義系統內部最理想的介面,再寫一個 Adapter 將它轉換為第三方 API 的實際呼叫。這樣做的好處是核心邏輯只依賴自己的介面;未來需要更換第三方供應商,只需重寫 Adapter,核心邏輯完全不受影響。

邊界是系統最容易發生變化的地帶。好的邊界設計的目標是:讓變化只停留在邊界,不讓它蔓延進核心。

類別設計:短小、凝聚、只有一個修改的理由

讓程式運作,與讓程式整潔,是開發過程中永遠存在的張力。但良好的類別設計是大型系統得以維持可理解性的基石——它讓工程師在修改時只需理解受影響的範圍,而不必把整個系統的複雜度同時裝入腦中。

書中對類別提出幾個核心原則:

短小——過長的類別通常是職責過多的訊號。**單一職責原則(SRP)**要求一個類別只應有一個修改的理由,也就是說,它只服務於一個業務概念。高凝聚性則是指類別內的方法與變數應該緊密相關——若某些變數只被少數幾個方法使用,就是拆分的訊號。書中提出一個具體的拆分時機:當你把一個大函式拆成數個小函式,而這些小函式需要共享某些變數時,應將它們連同共用變數一起抽離為新類別。

讓修改的代價維持可控:OCP 與 DIP

書中最後以兩個設計原則收束整個章節。

開放閉合原則(OCP):理想的系統應由許多小類別組成,新增功能時應透過繼承或新增類別來擴充,而非打開既有程式碼修改原本邏輯。這讓每次需求變更的影響範圍被限制在最小。

相依性反向原則(DIP):類別應依賴抽象介面,而非具體實作細節。以介面與抽象類別隔離具體實作,不僅降低類別間的耦合,也讓單元測試更容易撰寫——因為你可以替換具體實作,而不影響依賴它的邏輯。

這兩個原則指向同一個目標:讓系統能夠在不修改既有程式碼的情況下持續演化。這不是理想,而是工程設計應該追求的基線。

Newsletter

訂閱「恩普拉氏」電子報

撰寫那些關於成長、突破、跨界思考的事。

前往 Substack 訂閱 →