異常恢複是提高魯棒性最重要的方法。
前言:這篇文章是我自己的筆記,基本上是我翻譯的《Thinking in Java》第12章,翻譯不是目的,目的是想寫一遍印象深刻,因為有些句子翻譯不好,就抄了原文。。。
如果有人覺得文章中夾雜着英文很别扭的話,留言,然後我改。。。
基本概念
異常條件(exceptional conditions)是一個可以阻止程式繼續執行的問題。出現了異常條件,你無法繼續執行,因為在這個上下文中你沒有處理這個問題的足夠資訊,你隻能做的就是跳出目前的上下文環境,并且把問題送出給上一層。這就是抛出異常時所發生的事情。
當抛出一個異常時,會發生這麼幾件事情 : 首先,就像建立其它對象一樣,在堆上用new建立一個異常對象;然後,目前的執行路徑被終止,從目前的環境中彈出異常對象的引用;此時,異常處理機制接管過來,并且開始尋找合适的地方執行程式,而這個合适的地方就是異常處理程式(exception handler),它的工作就是從錯誤狀态中恢複,使程式要麼換一種方式運作,要麼繼續執行。
一個簡單的例子,引用沒有初始化:
if(t == null)
throw new NullPointerException();
1
2

1
2
上面這個程式抛出了一個異常,于是在目前環境上就不必再操心了,它會在别的地方得到處理。
異常機制使得我們可以把每件事當作一個事務來處理,而異常機制可以守護這些事務,“……在分布式計算中我們需要異常機制作為最基本的保障,某項事務如果出了什麼錯誤,可以放棄整個計算”。
我們還可以把異常看作是内建的恢複(undo)系統,因為(在細心地使用下)我們在程式中可以擁有各種不同的恢複點,如果程式的某部分失敗了,異常将“恢複”到程式中某個已知的穩定點上。
異常最重要的一點是,如果程式發生了什麼錯誤的話,異常會阻止程式沿着原來的路徑走下去。這在C和C++中确實是個問題,尤其是C語言中,如果發生了錯誤,沒辦法阻止程式繼續沿着原來的路走下去,是以有可能忽視這個錯誤很長時間進而陷入了完全錯誤的狀态中。
異常使得我們(如果沒有其他手段)強制程式停止運作,并告訴我們出現了什麼問題,或者(理想狀态下)強制程式處理問題,并傳回到穩定的狀态。
異常的參數
建立異常對象有兩種:帶參和不帶參的,不帶參的在上面已經見過了,帶參的看下面這個例子:
throw new new NullPointerException("t == null");
1

1
也可以從作用域(scope)中抛出異常,throw關鍵字抛出異常,簡單的來看就是從函數或作用域(scope)中“傳回”,無論是從哪裡抛出異常,都意味着從這個函數或作用域退出。
另外,你可以抛出任何類型的Throwable,這是異常的基類。通常對于不同類型的錯誤,要抛出相應的異常。錯誤資訊可以儲存在異常對象内部或者用異常類的名稱來暗示。上一層通過這些消息決定如何處理異常。(通常,把資訊隻放在異常的類型中,除此之外不包含任何有意義的内容。)
捕獲一個異常
要了解如何捕獲異常,必須得先了解監控區域(guarded region)的概念,就是可能會産生異常的一段代碼并且緊接着一段處理異常的代碼。
try塊
如果你在方法的内部,并且抛出一個異常(或者你在這個方法中調用了另一個方法,後者抛出異常),你所在的方法會在抛出異常的過程中退出。如果你不想在抛出異常的過程中退出(throw to exit)那個方法,你可以在那個方法中設定一個特殊塊來捕獲那個異常。因為在這個塊裡“嘗試”各種(可能産生異常的)方法調用,是以稱為try塊。如下:
try{
//code that might generate exception
}
1
2
3
1
2
3
異常處理程式 Exception handlers
當然,被抛出的異常必須在某地方得到處理。這個地方就是異常處理程式,并且針對你想捕獲的每個異常,得有一個。異常處理程式緊跟try塊之後,如下:
try{
//code that might generate exception
}catch(Type1 id1){
//處理類型Type1的異常
}catch(Type2 id2){
//……
}
1
2
3
4
5
6
7
1
2
3
4
5
6
7
每個catch子句就像一個接收且僅接收一個特殊類型的參數的方法,可以在處理程式的内部使用辨別符(id1, id2),也可以不使用,因為異常的類型已經給出足夠的資訊了,但是辨別符不可以省略。
異常被抛出後,異常機制會搜尋參數與異常類型相比對的第一個處理程式。然後進入catch子句執行,此時認為異常得到了處理。一旦catch子句結束,則處理程式的查找過程結束。這跟switch不一樣,switch語句需要在每個case後面跟一個break,以避免執行後續的case子句。注意在try塊的内部,許多不同的方法調用可能會産生類型相同的異常,而你隻需要提供一個針對此類型的異常處理程式。
termination vs. resumption
終止與恢複
在異常處理理論中有兩種基本的模型,java支援終止模型(java和c++都支援這種模型),這種模型假定發生的錯誤太嚴重了,已經到了沒辦法恢複的地步。另一種是可采取的辦法是恢複,這意味着異常處理程式想做點什麼來糾正錯誤,然後再嘗試一次剛才發生異常的方法。如果你想恢複,就意味着你在異常處理後再接着執行原來的程式。
在Java中如果想恢複的話,當遇到錯誤時不要抛出異常,而是,調用一個可以修複這個錯誤的方法。或者,把try塊放到while中,一直嘗試直到結果滿意。
從曆史觀點上說,程式設計人員一開始使用恢複的方法,但是卻最終都直接跳過恢複而使用了終止模型。盡管恢複很迷人,但是在實踐上并不好用,主要的原因是恢複程式得知道異常是在哪裡抛出來的,并且得針對抛出地點做處理。這使得程式很難寫同時也很難維護,因為大型程式有很多異常抛出點。