第 1 章說明技術債,並指出「對設計壞味道與重構缺乏認知」是技術債的主要成因之一。本章正面回答兩個基礎問題:為什麼要關心壞味道?壞味道從哪裡來?
本書的學習路徑:以壞味道反推設計原則#
心理學研究指出,我們從錯誤中學到的,往往比從成功中學到的還多——關鍵在於發現自己錯了的那種「驚訝感」。
本書因此採取與傳統教學相反的路徑:
- 傳統做法:先講設計原則,再用例子套用。
- 本書做法:從壞味道的真實案例出發,討論它為何發生、如何處理,藉此反推被違反的設計原則。
這種「以壞味道為學習錨點」的方式,更貼近實務工程師遇到的場景,也更容易讓設計原則被內化。
2.1 為什麼要關心壞味道?#
技術債的關鍵指標就是軟體品質低落。在帶債的專案裡,常見以下症狀:
- 軟體變得難以理解(理解性 understandability 降低)。
- 同樣的修改與功能擴充花費的時間越來越長(可變更性 changeability、可擴充性 extensibility 降低)。
- 看似可重用的部分,實際上越來越難重用(可重用性 reusability 降低)。
- 客戶越來越不滿意,QA 越來越難寫測試(可靠性 reliability、可測試性 testability 降低)。
由於設計對軟體品質有決定性影響,本書關注以下六個品質屬性。下表中的「設計片段(design fragment)」可指類別、介面、抽象類別,或它們之間的關聯(如繼承階層、相依圖)。
| 品質屬性 | 定義 |
|---|---|
| 理解性(understandability) | 設計片段被理解的容易度 |
| 可變更性(changeability) | 修改既有功能時,可以不引發連鎖效應地調整設計片段的容易度 |
| 可擴充性(extensibility) | 為支援新功能而擴充設計片段、且不引發連鎖效應的容易度 |
| 可重用性(reusability) | 將設計片段用於原始設計脈絡之外的問題的容易度 |
| 可測試性(testability) | 設計片段透過測試被偵錯的容易度 |
| 可靠性(reliability) | 設計片段確保功能正確、防止執行期問題的能力 |
範例:Multifaceted Abstraction 對品質屬性的衝擊#
以 java.util.Calendar 這類同時負責「日期」與「時間」的類別為例(即 3.4 節討論的 Multifaceted Abstraction),它違反單一職責原則(Single Responsibility Principle):
- 理解性:多重責任增加認知負擔;理解每個責任、釐清彼此關係耗時費力。
- 可變更性與可擴充性:難以判斷該動哪些成員;改動可能波及不相關的責任,造成設計層面的連鎖反應。
- 可重用性:即使只需重用一個責任,也得連帶搬走其他不相干的責任;責任彼此糾纏時,行為變得難以預測。
- 可測試性:責任之間緊耦合,難以分離測試。
- 可靠性:操作不同變數的責任放在一起,容易誤存取錯誤變數,導致執行期錯誤。
壞味道的衝擊不僅止於品質屬性。對外暴露 API 的框架/函式庫一旦帶有設計壞味道,修正成本極高——客戶端已經依賴後,幾乎沒有回頭路。這直接影響組織的對外聲譽。
2.2 壞味道從哪裡來?#
設計壞味道的成因與技術債高度重疊。除了第 1 章已提及的「缺乏好的設計者」與「缺乏重構」之外,本節聚焦其他六大成因。

Figure 2.1: 設計壞味道的常見成因
違反設計原則#
設計原則本就是用來引導出高品質設計的指南,違反它們便會以壞味道的形式呈現。
- 範例 1:
java.util.Calendar同時處理日期與時間 → 違反抽象原則(單一職責)→ 即 Multifaceted Abstraction(3.4 節)。 - 範例 2:
java.util.Stack繼承java.util.Vector,但 Stack 與 Vector 在概念上沒有 IS-A 關係(Stack 不能取代 Vector 使用)→ 違反階層原則(替換性 substitutability)→ 即 Broken Hierarchy(6.8 節)。
不當套用設計樣式#
設計樣式(design pattern)必須經過深思才能套用:
- 樣式的類別圖/序列圖只是參考解,每種樣式都有上百種變體,每種變體都有自己的代價。
- 不理解內在權衡的硬套,會產出類別過多、責任稀薄、彼此緊耦合等症狀。
壞味道與設計樣式存在有趣的雙向關係:套用適當的樣式經常是修正壞味道的最佳手段;但錯用、亂用、過度使用樣式同樣會產生壞味道(即所謂的反樣式 antipattern)。
程式語言的限制#
- Java 早期不支援列舉,迫使開發者用類別或介面承載常數,導致 Unnecessary Abstraction(3.5 節)。
- JDK 中的
AbstractQueuedSynchronizer與AbstractQueuedLongSynchronizer都繼承自AbstractOwnableSynchronizer,差別僅在處理int與long兩種原始型別。Java 泛型不支援原始型別 → 無法消除程式碼重複 → 形成 Unfactored Hierarchy(6.3 節)。
OO 語境下的「程序式思維」#
由程序式背景轉入物件導向的開發者,常把類別當作「在做什麼」(doing things)而非「是什麼」(being things)。這種思維會以下列方式出現:
- 以動詞命名類別。
- 對類別進行功能性分解。
- 用顯式型別檢查取代多型。
對應到 3.2 節的 Imperative Abstraction。
黏滯性(viscosity)#
當「正確做法」比「捷徑」更費力,開發者就會傾向用捷徑——這稱為黏滯性。書中區分兩類:
- 軟體黏滯性(software viscosity):套用正確設計時,遭遇的阻力(時間、心力、複雜度)。
- 環境黏滯性(environment viscosity):開發環境本身對好實踐造成的阻力,例如緩慢的開發流程、低效率的工具、組織程序、法規限制等。
黏滯性高的環境會默默地把所有人推向省事的壞路徑。改善工具與流程,往往是改善設計品質最被低估的槓桿。
不遵守最佳實踐與流程#
當組織未確實執行流程或最佳實踐時,常見副作用即為設計壞味道。例如:
- 複合重構(composite refactoring) 必須原子地整體完成;若中途停手,就會留下半成品狀態的壞味道(見 6.9 節的軼事範例)。
2.3 如何處理壞味道?#
處理壞味道的主要手段就是重構(refactoring)。
整體脈絡:
- 第 3–6 章:每個壞味道描述都附上「對應的重構建議」。
- 第 8 章:提供透過重構系統性償還技術債的整體流程。