異常處理
成功的異常處理應該實現如下4個目標:
(1)使程式代碼混亂最小化。
(2)捕獲並保留診斷信息。
(3)通知合適的人員。
(4)採用合適的方式結束異常活動。
針對某一段程式可能拋出多種異常的情況,可以採用如下兩種不同的方式對異常進行捕捉和處理:
(1)直接捕捉Exception類型異常因為程式拋出的可捕捉的異常都是Exception類的子類,因此都會被捕捉到。
(2)使用多個catch子句,針對具體的異常進行捕捉和處理。
第一種方式雖然可以實現捕捉異常的目的,但是不利於異常的分類和處理操作,通常情況下是針對異常的具體類型進行特定的操作,由於直接對Exception類型進行了捕捉,因此還需要進一步確認捕捉的異常的具體類型,以便決定採用什麼方式來處理,顯然採用第二種方式對多種異常進行捕捉時非常合理和現實的。
避免過度使用異常
作為一個合格的程式設計師需要知道一些基本的知識,例如做任何一件事都不能過度,這也算是每件事的共同的特點,同樣的Java中的異常處理也是這樣。雖然異常處理可以使代碼提前地規避一些異常錯誤,但是物極必反,過多地使用異常處理也是不可取的,它或許會造成一些其他的有些後果。
過度使用異常主要有兩個方面。
(1)把異常和普通的錯誤混淆在一起,不再編寫任何錯誤處理代碼,而是以簡單地拋出異常來代替所有的錯誤處理。
(2)使用異常處理來代替流程控制。
熟悉了異常使用方法後,程式設計師可能不再願意編寫繁瑣的錯誤處理代碼,而是簡單地拋出異常。實際上這樣做是不對的,對於完全已知的錯誤,應該編寫這種錯誤的代碼,增加程式的健壯性;對於普通的錯誤,應該編寫這種錯誤的代碼,增加程式的健壯性。只有對外部的,不能確定和預知的運行時錯誤才使用異常。
在五子棋遊戲中,處理用戶輸入坐標點已有棋子的兩種方式。
(1)編寫錯誤代碼
if(!gb.board[-xPos-1][yPos一1].equals(“+”))
{
System.out.println(“您輸入的坐標點已有棋子了,請重新輸入”);
continue;
}
這種處理方式檢測到用戶試圖下棋的坐標點已經有棋子了,立即列印一條提示語句,並重新打開下一次循環。這種處理方式簡潔明了,邏輯清晰。程式的運行效率也很好,程式進入if塊後,即結束了本次循環。
如果將上面的處理機制改為如下方式:
(2)程式拋出異常
if(!gb.board[xPos-1][yPos一1].equals(“+”))
{
throw new Exception(”您試圖下棋的坐標點已經有棋子了”);
}
在(2)中沒有提供有效的錯誤代碼,當程式檢測到用戶試圖下棋的坐標點已經有棋子時,並沒有提供相應的處理,而是簡單地拋出了一個異常。這種處理方式雖然簡單,但是Java運行時接收到這個異常後,還需要進入相應的catch塊來捕獲該異常,所以運行效率要差一些。而且用戶下棋重複這個錯誤完全是可預料的,所以程式完全可以針對錯誤提供相應的處理,而不是拋出異常。
必須指出:異常處理機制的初衷是將不可預期異常處理代碼和正常的業務邏輯處理代碼分離,因此不要使用異常處理來代替正常的業務邏輯判斷。
另外,異常機制的效率比正常的流程控制效率差,所以不要使用異常處理來代替正確的程式流程控制。
注意:異常只應該用於處理非正常的情況,不要使用異常處理來代替正確的流程控制,對於一些完全可預知,而且處理方式清晰的邏輯,程式應該提供相應的錯誤處理代碼,而不是將其籠統地稱為異常。
避免使用龐大的try塊
很多初學者喜歡在try塊里放置大量的代碼,在一個try塊里放置大量的代碼看上去“很簡單”,但這種“簡單”只是一種假象,只是在編寫程式時看上去比較簡單,但因為try塊里的代碼過於龐大,業務過於複雜,就會造成try塊中出現異常的可能性大大增加,從而導致分析異常原因的難度也大大增加。而且當try塊過於龐大時,需要分析它們之間的邏輯關係,反而增加了程式複雜度。
正確的做法是,把大塊的try塊分割成多個可能出現異常的程式段落,並把它們放在單獨的try塊中,從而分別捕獲並處理異常。
對異常分門別類進行處理
在異常處理巾要避免使用catch all語句,所謂的catch all語句指的是一種異常捕獲模組,它可以處理程式發生的所有可能異常。例如:如下代碼片段:
try
{
//可能引發Checked異常的代碼
}
catch(Throwable t)
{
//進行異常處理
t.printStackTrace();
)
不可否認,每個程式設計師都曾經用過這種異常處理方式:但在編寫關鍵程式時就應避免使用這種異常處理方式。這種處理方法有如下兩點不足之處。
(1)所有的異常都採用相同的處理方式,這將導致無法對不同的異常分情況處理,如果要分情況處理,則需要在catch塊中使用分支語句進行控制,這不是一個好的做法。
(2)這種捕獲方式可能將程式中的錯誤,運行時異常等可能導致程式終止的情況全部捕獲到,從而“壓制”了異常。如果出現了一些“關鍵”異常,那么此異常也會被“靜悄悄”地忽略。
實際上,catch all語句不過是一種通過避免錯誤處理而加快編程進度的機制,應儘量避免在實際套用中使用這種語句。
不要忽略異常
既然已捕獲到異常,那catch塊理應做些有用的事情——處理並修復這個錯誤。catch塊整個為空,或者僅僅列印出錯信息都是不妥的。
cateh塊為空就是假裝不知道甚至瞞天過海,這是最可怕的事情。程式處理到了錯誤,但所有的人都看不到任何異常,那么整個套用可能已經徹底壞了。僅在catch塊里列印錯誤跟蹤棧信息稍微好一點,但也僅僅比空白多了幾行異常信息。異常處理時,建議對異常採取適當措施,比如:
(1)處理異常。對異常進行合適的修復,然後繞過異常發生的地方繼續執行;或者用別的數據進行計算,以代替期望的方法返回值;或者提示用戶重新操作等等。總之,對於checked異常,程式應該儘量修復。
(2)重新拋出新異常。把當前運行環境下能做的事情儘量做完,然後進行異常轉移,把異常包裝成當前層的異常,重新拋出給上層調用者。
(3)在合適的層處理異常。如果當前層不清楚如何處理異常,就不要在當前層使用catch語句來捕獲該異常。直接使用throws聲明拋出該異常,讓上層調用者來負責處理該異常。