談談我處理異常的一般方法
我們在編寫程序的時候會遇到各種各樣的意外情況,如除數為0,數組越界,非法轉型,棧溢出等等。因而我們需要有一種機制來處理這些情況,異常處理就是其中的一種機制。當然,還有其他的機制,在MFC中,由于標準的不統一,就存在著各種錯誤報告方法,如有通過函數返回特殊值的方式,有通過執行某一語句后查詢特殊語句獲取錯誤的碼的方式,等等。
在C#中,只有一種報告方式,即異常。這樣可以讓開發人員從大量的文檔中解脫出來,不必為一些非邏輯的問題而花費大量時間。
C#中的異常不同于C++,所有的異常類型都是繼承自System.Exception的,因此我們定義自己的異常類型時,都要繼承自System.Exception或者該類的子類。
常見的異常語句如下:
try{ … }catch( InvalidOperationException ex){ .. }catch( Exception ex){ .. throw ex; } }finally{ .. }
我們可以通過捕獲不同類型的異常來捕獲我們需要的信息,而最后一個捕獲Exception的語句將會捕獲未被該語句之前的語句捕獲的信息。Throw語句可以將捕獲的異常拋出,繼續引發該異常,使得外層的捕獲語句捕獲到該異常,當然,最原始的異常也是通過該語句引發的。
以上都是一些基本的異常知識。我希望根據自己的經驗,闡述一下自己開發過程中是如何使用異常的。
首先要解決的問題是何時使用異常。我們知道,引發一個異常,需要一個比較大的開銷(http://www.cnblogs.com/aoaoblogs/archive/2009/12/08/1619827.html)。我認為,對于在處理問題主要邏輯范圍以內的問題,不要使用異常。主要邏輯范圍,指的是在完成一件任務的主要路徑。
比如我現在要進行一個查詢工作,查詢一下用戶在不在,如果該用戶在,那么我應該返回true,不在則應該返回false,我在用這個方法的時候,我對他的直接期望是得到一個返回值。然而,在查詢過程中會發生一些其他錯誤,如數據庫無法連接,數據庫并發連接數過大。這些問題,就應當是異常處理所要面對的問題。又比如我希望上傳一個文件,我用的時候就直接調用該方法,該語句在設計之初,就應當是直接能上傳的。
還有就是什么樣的問題需要拋出。
我認為,在設計過程中,在一些不可恢復的情況下,才有拋出異常的必要。應當在文檔中列出較少的可能拋出的異常。我們繼續以查詢用戶為例子,在查詢數據庫的過程中,我們發現數據庫無法連接,如果該查詢方法對時間要求不是很嚴格,我們應當使用一些策略來使得該方法能完成,如再次嘗試連接。在嘗試連接一定次數之后,我們才需要引發異常。
還有我認為,我們在設計方法的時候,要捕獲所有的方法內語句產生的異常,并將異常包裝后拋出,而不是直接拋出。并且拋出的異常最好是與邏輯相關的。繼續用查詢用戶做例子,數據庫鏈接出錯、網絡不可用之類的問題,我認為為了方便開發者使用該類(其實有時候開發者往往就是自己),我們應當將這些異常都放到一個異常類型中,如EnvironmentException,然后定義一個枚舉,將該枚舉作為EnvironmentException的屬性。我們在捕獲異常的時候,只要捕獲該類型的異常就好了,如果希望了解異常細節,我們則可以使用InnerException獲取引起該異常的異常。
下面討論怎樣定義異常的問題。
在.net中有許多內置的異常,如InvalidOperationException、InvalidCastException等,這些異常都繼承自System.SystemException。我在定義異常的時候,通常直接繼承自System.Exception。我個人認為,一個方法不應該拋出太復雜的異常,而使得開發者困惑。我拋出一個異常,只是把一個方法執行過程中所存在的問題拋出了。我想,得到一個與這個方法邏輯相關的異常、看到名字就大概知道哪里出錯的異常,總比一個InvalidCastException這樣的異常好多了。
比如以下是一段語句。
try{ var user=userManager.GetUser(id); user.Login(); user.Logout(); var article=articleManager.getArticle(articleId); article.user=user; article.save(); }catch{ … }
如果Login()會拋出一個InvalidOperationException,logout()會拋出一個InvalidOperationException,我們可能不得不進一步分析這些異常,然后才能對這些操作作出適當的應對措施。
對于一個規模不大的類,我習慣于定義包含一個枚舉類型的類的異常。例如,上面這個例子,我會定義UserOperationException, UserManagerOperationException, ArticleOperationExcetpion,ArticleOperation這些異常,以UserOperationException為例子
public class UserOperationException:Exception{ //僅列出主要部分,構造函數等就略了.. public enum UserOperationStatus{get;set;} } public enum UserOperationStatus{ Login, Logout, Save, }
這樣在異常捕獲的時候,我們可以捕獲特定的異常來確定哪一塊出現了問題,也可以捕獲Exception來捕獲所有的異常。
根據我前面說過的設計原則,所有的異常都是經過包裝的已知異常,而且這些異常是基于我們的業務邏輯的,這樣代碼看起來就會比較清晰了。
如果Login方法還有一些其他的異常信息,我一般這樣實現。
public enum LoginOperationException:UserOperationException{ public LoginOperationException(string message,ExceptioninnerException) :base(message,innerException,UserOperationStatus.Login) { .. } }
使用這樣的繼承方式,我們就可以更加靈活的來進行異常處理。
現在討論一下如何進行異常捕獲。
我認為在寫程序的時候,可以首先考慮程序的邏輯問題,非邏輯問題,即異常捕獲可以不進行處理。繼續以用戶登錄為例子:
public function void Login(string username,string password){ //_UserManager是類的一個私有字段 var user=_UserManager.GetUserByName(username); if(user.password==password) { user.Login(); Console.WriteLine(“Success”); user.Logout(); Console.WriteLine(“LogoutSuccess”); }else{ Console.WriteLine(“Invalidpassword”); } }
可能我們第一次考慮的時候是這樣的,這是一個最直觀的邏輯,在編寫代碼的過程中,并沒有過多的來關注各個方法拋出的異常。當我們檢查完畢整個邏輯沒有問題之后,就可以對異常進行捕獲了。
public function void Login(string username,string password){ try{ //_UserManager是類的一個私有字段 var user=_UserManager.GetUserByName(username); if(user.password==password) { user.Login(); Console.WriteLine(“Success”); user.Logout(); Console.WriteLine(“Logout Success”); }else{ Console.WriteLine(“Invalid password”); } }catch(UserManagerOperationException){ Console.WriteLine(“User doesn’t exist”); }catch(UserLoginOperationException ex){ switch(ex.LoginStatus){ case LoginStatus.DB: Console.WriteLine(“DB errors”); break; case LoginStatus.Network: Console.WriteLine(“Network errors”); break; }catch(UserOperationExceptionex){ Console.WriteLine(“Other exception {0}”,ex); } }
當然了,這樣的編碼方式還是要在編碼過程中來關注這些異常。在我所了解的范圍內,微軟提供了一個開源的企業庫Microsoft Enterprise Library(http://entlib.codeplex.com/),該庫提供了一個ExceptionHandling Application Block,我們可以用更加靈活方式來處理這些異常,比如全部在配置文件里面做。
當然,還有其他的方式來解決異常處理的問題,比如AOP技術。最近一直對AOP比較感興趣,但并沒有相關的實踐,因此也沒有什么經驗可談。
如果您完整的看完了我的文章,希望您可以根據您的經驗指出文章中存在的問題。這篇文章只是一個我的經驗總結。