當設計中用一團原始資料或編碼字串取代了一個本應存在的類別/介面時,就出現 Missing Abstraction 壞味道。
核心定義#
抽象的使能技巧之一是「提供清晰的概念邊界與唯一身份」。當這個身份消失,被原始型別(primitive types)或字串取代時,就違反了抽象原則。
連帶副作用:
- 違反封裝:原本應隱藏在抽象內的實作細節被外露。
- 違反模組化:相關的資料與行為被散落到多個抽象中,造成緊耦合與脆弱不可重用的程式碼。
常見成因#
- 設計分析不足:在期限壓力下「先讓程式跑起來」,順手用原始型別或字串敷衍。
- 缺乏重構:早期用字串/原始型別表示的概念,後來成長為複雜邏輯卻沒有及時重構。
- 過度迷戀微幅效能:誤以為陣列存取比物件成員存取快,因而避開應有的抽象。多數情境下,這點效能差距遠不足以彌補設計品質的損失。
範例#
範例 1:ISBN#
在圖書館管理系統中:
- ISBN 有 10 碼與 13 碼兩種形式,可互轉。
- 每個位元都有意義(前綴、註冊群、註冊者、出版品、檢核碼)。
- 檢核碼依特定演算法計算,可驗證號碼正確性。
- 可轉換為條碼。
若以字串或長整數儲存 ISBN,驗證/轉換/比對等邏輯就會散落並重複——這是典型的 Missing Abstraction。
範例 2:資料叢集(data clumps)#
繪圖程式中的「矩形選取區」常被表示為:
(x1, y1)與(x2, y2)兩組double,或(x1, y1)加上(width, height)。
這些值總是一起出現、一起傳遞,是典型的 data clumps——同樣是 Missing Abstraction。
範例 3:JDK 的 stack trace#
Java 1.0 的 Throwable.printStackTrace() 把整個 stack trace 編碼為字串輸出。客戶端為了取得行號或判斷重複錯誤,必須去剖析字串格式。一旦客戶端依賴此格式:
- 設計者就不能變更字串格式。
- 設計被字串「鎖死」。
建議重構#
為原始資料或字串建立真正的抽象,把建構、驗證、複製等相關行為移入。
- 範例 1:建立
ISBN抽象類別/介面,並衍生ISBN10與ISBN13子類別。

Figure 3.4: ISBN 範例的建議重構
- 範例 2:建立
Rectangle或SelectedRegion類別,把欄位與相關方法移入。 - 範例 3:JDK 1.4 補上
StackTraceElement類別,提供程式化存取;既有printStackTrace()為了相容仍保留。
public final class StackTraceElement {
public String getFileName();
public int getLineNumber();
public String getClassName();
public String getMethodName();
public boolean isNativeMethod();
}對品質屬性的衝擊#
- 理解性:核心概念散落在程式碼基底中,難以追蹤。
- 可變更性與可擴充性:找出該改的地方都困難,更別說避免連鎖效應。
- 可重用性與可測試性:對應領域實體的邏輯沒有單一收斂點,難以重用、難以測試。
- 可靠性:失去抽象作為「資料完整性守門員」的角色,正確性容易被破壞。
別名#
- Primitive obsession:以原始型別表示日期、貨幣等。
- Data clumps:總是一起出現的資料叢集。
實務考量#
並非每個資料都需要抽象化——有些只是「純資料元素」,沒有額外行為。
判斷是否該建立抽象的快速檢核:
- 是否需要透過建構式做預設初始化?
- 是否需要驗證?
- 是否需要美觀輸出(pretty printing)?
- 是否需要釋放資源?
任何一項為「是」,就應該認真考慮建立抽象,而非過度追求精簡。