Java 介面擁有多重繼承的優勢卻無其缺點,讓客戶端能透過介面插入不同實作。遺憾的是,介面只能表達方法簽名,無法強制實作遵守那些「隱性不變量」。當類別「實作」了介面卻違背預期語義,便產生了本文主角——虛構實作錯誤模式。
看似簡單的 Stack 介面:
public interface Stack {
public Object pop();
public void push(Object top);
public boolean isEmpty();
}
Java 型別檢查器眼中,任何擁有這三個方法的類別都是合法實作。但開發者心中另有隱性不變量:
o 被 push 進堆疊 s,緊接著 pop 應回傳 os.isEmpty() == true,緊接著 pop 應拋出 RuntimeExceptionpush 處理等更多未寫明契約這些不變量難以靜態檢查,文檔易被忽略,實作者一疏忽,依賴契約的客戶端便會失效——錯誤往往潛伏至罕見執行路徑才浮現。
作者強調並非批評 Java。允許在介面表達更多不變量將引入多重繼承的經典難題:若 Stack 與 Popper(pop() 永不拋出例外)同時被實作,同樣的 pop() 簽名卻有衝突語義。為消除歧義,語言複雜度將激增,自動消解更易出錯。將介面規範限於型別簽名,是合理的工程取捨。
症狀:編譯通過,運行時詭異失敗,開發者常先陷入「這不可能發生」的否認階段。
診斷步驟:
解決之道:
虛構實作源於「介面只說做什麼,不說怎麼做」的語言本質。與其怪語言,不如建立「可執行契約」文化:用測試說明預期,用斷言守護不變量,讓介面真正成為可信賴的協作基石。