April 21 01:33~14:12
在〈例外處理壞味道(上)〉與〈例外處理壞味道(下)〉Teddy介紹了以下幾種例外處理壞味道(exception handling bad smells):
- Return Code(回傳碼)
- Ignored Checked Exception(忽略受檢例外)
- Ignored Exception(忽略例外)
- Unprotected Main Program(未被保護的主程式)
- Dummy Handler(虛設的處理者)
- Nested Try Statement(巢狀Try敘述)
- Spare Handler(備胎)
- Careless Cleanup(粗心的資源釋放)
昨天在北科上課review學生的作業,看到另一個很常見的例外處理壞味道:將例外當作控制流程(using exceptions as control flow)。
***
▼下圖是學生所寫的程式碼示意圖,getCompanyName函數首先判斷參數obj是否包含錯誤,如果是則丟出一個Exception並將其內容設為「找不到資料」。在第39行的catch clause中捕捉例外,然後傳回例外物件的getMessage字串。
問題一
有經驗的鄉民一眼就可以看出來上面這段程式出了什麼問題,首先在第29行丟出例外然後在第41行捕捉例外這種寫法就是典型的將例外作為控制流程,和寫go to是類似的效果。
▼程式可以改成當判斷obj物件包含錯誤的時候,直接傳回 “找不到資料”這個字串就可以了。
問題二
原本的程式寫法直接捕捉exception物件,這個物件在Java例外物件的階層中僅次於Throwable,捕捉Throwable或Exception這種寫法叫做blanket catch(請參考〈Java的try、catch、finally(1):Java SE 7之前〉)。在原本的程式碼中27~39行的try clause除了29行以外其他地方也可能會丟出例外,所以就算原本的程式碼不介意使用例外作為控制流程,41~43行的例外處理程式(exception handler)也可能因為其他程式發生例外而被觸發。也就是說,當呼叫getCompanyName函數的人收到"找不到資料"這個字串的時候,在原本的寫法當中有可能因為兩個原因所造成:
- 真的找不到資料。
- 因為發生例外被誤判為找不到資料。
很顯然地原本的程式邏輯是不正確的。
***
在絕大部分的情況下,例外應該單純用在程式異常狀況,而非因為丟出例外可以具有流程控制的特性而將例外使用在流程控制上。對於例外處理設計有興趣的鄉民們可以參考泰迪軟體的「例外處理設計與重構實作班」課程,或是Teddy所寫的《笑談軟體工程:例外處理設計的逆襲》 。
***
友藏內心獨白:程式不是看起來可以動就沒事了。
您好,不好意思,打擾了
回覆刪除想請問一下,如果有去自訂 Exception, 如:ItemException extend Exception
當商品判斷有問題時,我就會 throw new ItemException , 好像也是中了 將 exception 當 流程在使用
不知這樣子 自訂Exception 是不是也一樣不建議當流程來操作呢?
判斷商品有問題丟出例外並不代表將例外當作控制流程,還是需要看到程式碼才可以判斷喔。
刪除瞭解,謝謝您的回覆
刪除作者已經移除這則留言。
回覆刪除請問如果是處理http request, 在做某個operation之前需要先authenticate一下, 當authentication fail的時候應該要直接回個錯誤訊息給http client, 那這時在內部應該丟出exception嗎? code大概像底下這樣
回覆刪除if(user.authenticate(username, password)){
// do other operation
}else{
// throw new Exception ?
}
判斷是否要丟出例外可以先問自己:「這是一個正常狀況還是異常狀況」。以認證為例,如果你認爲認證失敗屬於異常狀況,當然可以丟出一個認證失敗的例外物件 (以上是簡化版的回答,實際狀況要複雜一些,因為判斷是否屬於異常狀況經常和程式所屬的 context 有關,不是那麼絕對可以根據「動作」本身來決定)。
刪除你可以參考這一篇「找不到資料要傳回Null還是丟出Exception?」 http://teddy-chen-tw.blogspot.tw/2013/11/nullexception.html,和你的問題不同但存在類似的思考觀點。