今日 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:加入讀寫序列化物件的能力
fileStream與bufferedStream在開檔之後就再也用不到,後續操作只用objectStream。
為什麼這個設計糟糕#
最讓人困擾的是 buffering 必須由呼叫端主動建立 BufferedInputStream:
- 一旦忘記建立,就沒有緩衝,I/O 會非常慢
- 容易出錯
可能的辯護#
「不是每個人都想用緩衝,所以不應該內建在基底機制中。應把緩衝獨立出來讓人選擇用不用。」
為何辯護不成立#
介面設計的核心原則:讓「常見情況」盡可能簡單。
- 幾乎所有檔案 I/O 的使用者都需要緩衝 → 應該作為預設
- 對於極少數不需要緩衝的情境,再提供關閉的機制(例如另一個建構式或顯式停用方法),且機制應與介面其他部分清楚分開
- 多數開發者甚至不需要知道這個關閉機制存在
Unix 的對比#
Unix 系統呼叫的設計者讓常見情況保持簡單:
- 認知到「循序 I/O 是最常見的」 → 設成預設行為
- 隨機存取仍可透過
lseek達成,但只做循序存取的開發者不需要意識到lseek的存在
設計啟示#
介面有很多功能 ≠ 介面複雜。
如果多數開發者只需感知其中一小部分,介面的有效複雜度只等於常用部分的複雜度。