l

2013年11月14日 星期四

Java的try、catch、finally(3):我的例外被finally block蓋台了

Nov. 07 09:52~11:00

螢幕快照 2013-11-07 上午10.41.45

 

前兩集簡介了Java SE 7之前與之後的例外處理機制,有了這些基本觀念之後,今天終於可以來談一個比較有趣的問題:finally block丟出的例外會覆蓋掉try block與catch block所丟出的例外。

***

消失的例外

先看上面這一段程式範例,假設在try block中使用MyOutputStream會丟出一個IOException,該例外將直接往外傳,因此沒有相對應的catch block去捕捉它。但由於MyOutputStream是一個IO物件,離開try statement之前要釋放它所使用的資源,因此在finally block中呼叫它的close() method。但是close()方法也可能發生錯誤而丟出一個IOException,如果這個IOException也往外丟(第201行),則呼叫finallyBlockOverrideExceptionThrownByTryBlock()的人將只會收到finally block所丟出的IOException,而原本由try block所丟出的那一個IOException則被「蓋台」而消失不見了。

接下來在main()呼叫上面這段式:

螢幕快照 2013-11-07 上午10.43.38

 

stack trace告訴我們,這個例外是由198行程式碼所丟出,也就是finally block裡面的mos.close()這一行所丟出的IOException蓋掉了原本try block所丟出的IOException。

螢幕快照 2013-11-07 上午10.44.34

***

語意不同

看到這邊鄉民們可能會覺得這有什麼了不起的,被蓋掉就被蓋掉啊,反正眼不見為淨挑眉質疑。雖然最後丟出的都是IOException,但從例外處理的角度來看,這兩個IOException所代表的意義不同:

  • try block所丟出的IOException:表示該method無法執行它被賦予的「正常功能」,也就是說呼叫該method的結果是失敗的(failure)。
  • finally block所丟出的IOException:表示cleanup失敗,系統可能會有資源釋放不乾淨的問題。

換句話說,這兩個IOException的語意不同,應該用不同的例外處理策略來應付:

  • Function failure:造成failure的原因很可能是component fault,常見的例外處理方式有retry,重新執行一次原本的method,或是考慮使用替代方案,呼叫另一個method來取代原本失敗的method。如果是design fault,則需要修改程式。
  • Cleanup failure:如果是資源釋放失敗,則需進一步研究其原因。這種情況很有可能是由於程式撰寫錯誤所造成的design fault,需要修改程式內容。如果沒有被呼叫元件的原始碼,可能需要改用其他的元件來替代。

了解了這兩個IOException的語意之後,再問一個問題:當兩者同時發生的時候,哪一個例外比較重要,應該被保留下來?

理想上兩者都應該被保留,但因為只有一個例外可以往外傳遞,所以這時候就要從呼叫者的角度來思考那一個例外應該被保留。如果保留cleanup failure,那麼呼叫者會以為執行該method的正常功能成功,只是cleanup失敗。但實際是卻是兩者都失敗,所以cleanup failure的語意就模糊不清。

但反過來,如果保留function failure,那麼呼叫者也無法分辨是否有cleanup失敗的情況。兩害相權取其輕,實務上,cleanup失敗的機率比較低,而且對呼叫者而言,最主要關心的議題是呼叫某個method的結果是否成功,因為cleanup就算是失敗程式通常還是可以再跑一陣子,只要能夠把這個問題記錄下來即可,並不一定立刻需要在程式執行的當下去修復它。但是如果呼叫的method失敗,就要立刻處理,不能假裝沒看到,否則整個系統的狀態可能都會錯誤。

***

怎麼辦

這個問題,不只在Java語言會發生,在C#語言也有。這兩個主流的語言都建議:在finally block裡面不要丟出例外。所以在finally block如果會產生例外請把這個例外記錄到日誌檔案中。

螢幕快照 2013-11-07 上午10.37.52

 

事情如果這麼簡單就不好玩了,還記得Java SE 7之後多了try-with-resources的功能,下面這段使用try-with-resources所重新改寫過的程式碼和上面的程式碼執行相同的功能。但是現在問題來了,因為執行cleanup的程式碼現在改由JVM來呼叫,不需要鄉民們自己寫在finally block裡面。那如果cleanup失敗,哪怎麼辦?現在連把cleanup failure寫在日誌檔的機會都沒有了啊。

螢幕快照 2013-11-07 上午10.53.24

這個問題比較複雜一點,下集繼續。

***

友藏內心獨白:終於快講到重點了。

沒有留言:

張貼留言