在真實的多層架構開發(Controller → Service → DAO)中,你一定會遇到這樣的困境:
最底層的保險庫管理員(DAO)跟資料庫連線失敗,丟出了一個極度硬核的 SQLException。
身為中間業務大腦(Service)的你,絕對不能直接把這個 SQLException 甩給最前端的櫃台(Controller)甚至使用者。因為 SQL 語法是講給資料庫聽的,使用者根本看不懂;更危險的是,把資料庫欄位名稱直接噴在前端螢幕上,等於把自家系統的骨架攤在陽光下,是嚴重的資安漏洞!
這時候你靈機一動:「那我把底層硬核錯誤攔截下來,換成昨天學的自訂業務例外 OrderProcessingException(訂單處理失敗),再丟出去給櫃台不就好了?」
想法很美好,但如果你換包裝的方式錯了,就會引發工程師的深夜噩夢:「原本最關鍵的死因(Root Cause)被你徹底抹殺,後端工程師想修 Bug 卻找不到第一案發現場!」
換包裝可以,但必須把真兇當成證物一起打包交上去! 這就是——例外鏈(Exception Chaining)!
// 錯誤示範:把原本的關鍵證物丟進垃圾桶
public void payOrder(String orderId) throws OrderProcessingException {
try {
database.updateBalance(orderId); // 假設這裡引爆了底層的 SQLException
} catch (SQLException e) {//抓到的 e,裡面裝著警方第一手偵查卷宗
// 抓到底層錯誤,自作主張換了一顆新炸彈再丟出去,而你只給了一句毫無案發細節的客製化文字,卻把那本卷宗 e 隨手扔了。
throw new OrderProcessingException("訂單扣款失敗,請稍後再試!");
}
}
這段程式碼表面上看起來彬彬有禮,前端也拿到了親切的錯誤訊息。
但是負責維運系統的後端工程師會當場瘋掉!
當系統在半夜當機、噴出滿螢幕紅字時,追蹤後只會顯示:
OrderProcessingException: 訂單扣款失敗,請稍後再試!
這句客製化遺言對維修毫無價值。工程師抓破頭也想不透:底層到底是因為資料庫帳密打錯?連線超時?還是硬碟空間整個被塞爆?
真正的正解是兩個要一起打包,傳文字給前端看卷宗 e 塞給老爸存檔
throw new OrderProcessingException("訂單扣款失敗,請稍後再試!", e);
這樣一來,前端拿到親切的「訂單扣款失敗」,而後端工程師翻開後台日誌時,底下依然清清楚楚附著 Caused by: java.sql.SQLException: Connection timeout(真兇原案卷宗)
Java 早就在老祖宗 Throwable 身上設計好一套「保留犯罪現場」的機制。只要在打造自訂例外時,把抓到的真兇當成證物(cause)塞給老爸,就能兼顧前端的安全與後端的除錯!
步驟 A:打造自訂炸彈留一個暗格收證物
public class OrderProcessingException extends Exception {
// 收遺言(message),外加一份真兇筆錄(cause)
public OrderProcessingException(String message, Throwable cause) {
// 雙手呈遞給老爸 Throwable 妥善保存!
super(message, cause);
}
}
e 與 cause 到底差在哪?
它們本質上是同一份卷宗!在案發現場抓到的現行犯叫 e,送進警察局填表時,老爸規定的欄位名稱叫 cause(成因)。
e(驗屍報告 / 筆錄正本):
第一案發現場最詳盡的卷宗,事發時間、哪一行程式碼炸開。
cause(箱子裡面):
你在自製的包裹裡把卷宗 e 放進去(super(message, cause))。
步驟 B:換上白話包裝,把現行犯 e 塞進肚子裡一起丟出去
//正確示範:換包裝,同時完整保留第一案發現場
public void payOrder(String orderId) throws OrderProcessingException {
try {
database.updateBalance(orderId);
} catch (SQLException e) {
// 抓到現場筆錄 e,塞進 cause,往上呈遞!
throw new OrderProcessingException("訂單扣款失敗,訂單編號:" + orderId, e);//把這個貼著白話說明「訂單扣款失敗」、塞著真兇卷宗的箱子,一口氣整箱扔上去
}
}
這箱炸彈丟出去,到底給誰看?
外包裝(給前端客人看):
貼著親切好懂的標籤 "訂單扣款失敗,訂單編號:123"。客人安心換卡刷,後端資料庫的機密欄位也絕不外洩。
裡面證物(給後端工程師查案):
當半夜出事時,工程師不用盲猜,在日誌裡呼叫 getCause(),裡面那」一五一十記錄案發行數與資料庫代碼的現場筆錄 e 立刻現形,破案就在彈指之間!