今日 classitis 最顯眼的範例是 Java 類別庫。

語言本身並未強制要小類別,但 Java 社群似乎把 classitis 寫進了文化。

範例:開檔讀序列化物件#

要從檔案讀出序列化物件,得建立三個不同物件:

FileInputStream fileStream =
    new FileInputStream(fileName);
BufferedInputStream bufferedStream =
    new BufferedInputStream(fileStream);
ObjectInputStream objectStream =
    new ObjectInputStream(bufferedStream);

各物件職責:

  • FileInputStream:只提供基本 I/O(沒緩衝、不能讀寫序列化物件)
  • BufferedInputStream:在 FileInputStream 上加入緩衝
  • ObjectInputStream:加入讀寫序列化物件的能力

fileStreambufferedStream 在開檔之後就再也用不到,後續操作只用 objectStream

為什麼這個設計糟糕#

最讓人困擾的是 buffering 必須由呼叫端主動建立 BufferedInputStream

  • 一旦忘記建立,就沒有緩衝,I/O 會非常慢
  • 容易出錯

可能的辯護#

「不是每個人都想用緩衝,所以不應該內建在基底機制中。應把緩衝獨立出來讓人選擇用不用。」

為何辯護不成立#

介面設計的核心原則:讓「常見情況」盡可能簡單。

  • 幾乎所有檔案 I/O 的使用者都需要緩衝 → 應該作為預設
  • 對於極少數不需要緩衝的情境,提供關閉的機制(例如另一個建構式或顯式停用方法),且機制應與介面其他部分清楚分開
  • 多數開發者甚至不需要知道這個關閉機制存在

Unix 的對比#

Unix 系統呼叫的設計者讓常見情況保持簡單:

  • 認知到「循序 I/O 是最常見的」 → 設成預設行為
  • 隨機存取仍可透過 lseek 達成,但只做循序存取的開發者不需要意識到 lseek 的存在

設計啟示#

介面有很多功能 ≠ 介面複雜。

如果多數開發者只需感知其中一小部分,介面的有效複雜度只等於常用部分的複雜度。