談談我處理異常的一般方法

作者: Loning  來源: 博客園  發布時間: 2010-10-14 07:13  閱讀: 1450 次  推薦: 0   原文鏈接   [收藏]  
摘要:我們在編寫程序的時候會遇到各種各樣的意外情況,我們需要有一種機制來處理這些情況,異常處理就是其中的一種機制。

  我們在編寫程序的時候會遇到各種各樣的意外情況,如除數為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比較感興趣,但并沒有相關的實踐,因此也沒有什么經驗可談。

  如果您完整的看完了我的文章,希望您可以根據您的經驗指出文章中存在的問題。這篇文章只是一個我的經驗總結。

0
0
 
標簽:C# 異常處理
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()