當一個抽象留在設計中卻沒被任何人用,就出現 Unutilized Abstraction 壞味道。

核心定義#

兩種具體形態:

  • 未被引用的抽象(unreferenced abstractions):具體類別存在但沒人用。
  • 孤兒抽象(orphan abstractions):抽象類別/介面沒有任何衍生抽象

設計應服務於真實需求,而非想像或推測中的需求。本壞味道直接違反 YAGNI (You Aren’t Gonna Need It)原則。

常見成因#

  • 預測式設計(speculative design):「未來可能會用到」的心態。
  • 需求變更:原本支援某需求的抽象在需求轉向後仍留在程式碼中。
  • 維護期遺留垃圾:重構或改版時忘了刪舊類別。
  • 不敢刪:在大型程式碼基底中無法確認某類別是否仍被使用,就索性留著。

範例#

範例 1:sun.misc.Service 的歷史遺跡#

JDK 中 sun.misc 套件早年是內部支援工具,後來這些功能多被公開 API 取代。例如 sun.misc.Service(JDK 1.3 引入)後來被 java.util.ServiceLoader(JDK 1.6)取代——但舊類別繼續留著,成了 Unutilized Abstraction。

範例 2:自製並行工具被 java.util.concurrent 取代#

某 Java 專案用 JDK 1.3 開發,自訂了並行版 HashMap、阻塞 FIFO buffer、copy-on-write array list。JDK 1.5 引入 java.util.concurrent 後,新程式改用標準工具,但舊類別未被刪除

範例 3:報表類別的孤兒階層#

一個 IDE 整合的視覺化工具開發者考慮「未來可能想要 PDF/HTML 報表」,預先建立三個抽象類別作為占位。但工具的核心始終是視覺化,報表功能從未實作——這三個抽象類別變成孤兒。

Figure 3.12: 未被使用的報表生成類別(範例 3)

注意:這看起來很像 6.2 節的 Unnecessary Hierarchy,但Unnecessary Hierarchy 中的型別仍被使用,差別就在這裡。

建議重構#

最簡單的重構就是刪除

  • 範例 1:把 sun.misc.Service 的呼叫換成 java.util.ServiceLoader。JDK 9 已正式移除 sun.misc.Service
  • 範例 2:將自製並行工具替換為對應的標準工具。注意:並行替換可能引入難以重現的 bug,需周全的測試與審查
  • 範例 3:直接移除整個未使用的抽象階層。

公開 API 中的抽象不能任意刪除——可標註 obsoletedeprecated,明確告訴新客戶端不要使用。

軼事:被忘記的舊實作#

JDK 一份 bug 報告提到:ObjectFactorySecuritySupport 類別曾散見於多個套件,後來被統一搬到 internal/utils。但在某次匯入 jaxp 1.4.5 source bundle 時,舊套件版本被覆蓋回來——舊版類別於是再次出現於程式碼基底中,無人使用

教訓:重構與維護時,務必清掉被取代的程式碼,否則程式碼會無聲地膨脹。

對品質屬性的衝擊#

  • 理解性:未使用的抽象汙染設計空間,提高認知負擔。
  • 可靠性:未使用的程式碼若不慎被觸發,可能導致詭異 bug。

別名#

  • Unused classes:沒有任何引用的類別。
  • Speculative generality:「將來可能會用到」而新增的類別。

實務考量:API 中的擴充點#

函式庫/框架常以抽象類別或介面提供「擴充點」。它們在函式庫內部看起來沒有實作者,但實際的使用者是外部客戶端——不算 Unutilized Abstraction。