Effective C# 爲應用程序創建特定的異常類

異常是一種的報告錯誤的機制,它可以在遠離錯誤發生的地方進行處理錯誤。所有關於錯誤發生的的信息必須包含在異常對象中。在錯誤發生的過程中,你可能想把底層的錯誤轉化成詳細的應用程序錯誤,而且不丟失關於錯誤的任何信息。你須要仔細考慮關於如何在C#應用程序中創建特殊的異常類。第一步就是要理解什麼時候以及爲什麼要創建新的異常類,以及如何構造繼承的異常信息。當開發者使用你的庫來寫catch語句時,他們是基於特殊的進行時異常在區別爲同的行爲的。每一個不同的異常類可以有不同的處理要完成:
 
try {
Foo( );
Bar( );
} catch( MyFirstApplicationException e1 )
{
FixProblem( e1 );
} catch( AnotherApplicationException e2 )
{
ReportErrorAndContinue( e2 );
} catch( YetAnotherApplicationException e3 )
{
ReportErrorAndShutdown( e3 );
} catch( Exception e )
{
ReportGenericError( e );
}
finally
{
CleanupResources( );
}


不同的catch語句可以因爲不同的運行時異常而存在。你,做爲庫的作者,當異常的catch語句要處理不同的事情時,必須創建或者使用不同的異常類。如果不這樣,你的用戶就只有唯一一個無聊的選擇。在任何一個異常拋出時,你可以掛起或者中止應用程序。這當然是最少的工作,但樣是不可能從用戶那裏贏得聲望的。或者,他們 可以取得異常,然後試着斷定這個錯誤是否可以修正:
 
try {
Foo( );
Bar( );
} catch( Exception e )
{
switch( e.TargetSite.Name )
{
    case "Foo":
      FixProblem( e );
      break;
    case "Bar":
      ReportErrorAndContinue( e );
      break;
    // some routine called by Foo or Bar:
    default:
      ReportErrorAndShutdown( e );
      break;
}
} finally
{
CleanupResources( );
}


這遠不及使用多個catch語句有吸引力,這是很脆弱的代碼:如果只是常規的修改了名字,它就被破壞了。如果你移動了造成錯誤的函數調用,放到了一個共享的工具函數中,它也被破壞了。在更深一層的堆棧上發生異常,就會使這樣的結構變得更脆弱。
 
在深入討論這一話題前,讓我先附帶說明兩個不能做承諾的事情。首先,異常並不能處理你所遇到的所有異常。這並不是一個穩固指導方法,但我喜歡爲錯誤條件拋出異常,這些錯誤條件如果不立即處理或者報告,可能會在後期產生更嚴重的問題。例如,數據庫裏的數據完整性的錯誤,就應該生產一個異常。這個問題如果忽略就只會越發嚴重。而像在寫入用戶的窗口位置失敗時,不太像是在後來會產生一系列的問題。返回一個錯誤代碼來指示失敗就足夠了。

其次,寫一個拋出(throw)語句並不意味會在這個時間創建一個新的異常類。我推薦創建更多的異常,而不是隻有少數幾個常規的自然異常:很從人好像在拋出異常時只對System.Exception情有獨鍾。可惜只只能提供最小的幫助信息來處理調用代碼。相反,考慮創建一些必須的異常類,可以讓調用代碼明白是什麼情況,而且提供了最好的機會來恢復它。

再說一遍:實際上要創建不同的異常類的原則,而且唯一原因是讓你的用戶在寫catch語句來處理錯誤時更簡單。查看分析這些錯誤條件,看哪些可以放一類裏,成爲一個可以恢復錯誤的行爲,然後創建指定的異常類來處理這些行爲。你的應用程序可以從一個文件或者目錄丟失的錯誤中恢復過來嗎?它還可以從安全權限不足的情況下恢復嗎?網絡資源丟失又會怎樣呢?對於這種遇到不同的錯誤,可能要採取不同的恢復機制時,你應該爲不同的行爲創建新的異常類。

因此,現在你應該創建你自己的異常類了。當你創建一個異常類時,你有很多責任要完成。你應該總是從System.ApplicationException類派生你的異常類,而不是System.Exception類。對於這個基類你不用添加太多的功能。對於不同的異常類,它已經具有可以在不同的catch語句中處理的能力了。

但也不要從異常類中刪除任何東西。ApplicationException 類有四個不同的構造函數:
 
// Default constructor
public ApplicationException( );

// Create with a message.
public ApplicationException( string );

// Create with a message and an inner exception.
public ApplicationException( string, Exception );

// Create from an input stream.
protected ApplicationException(
SerializationInfo, StreamingContext );


當你創建一個新的異常類時,你應該創建這個四構造函數。不同的情況調用不同的構造方法來構造異常。你可以委託這個工作給基類來實現:
 
public class MyAssemblyException :
ApplicationException
{
public MyAssemblyException( ) :
    base( )
{
}

public MyAssemblyException( string s ) :
    base( s )
{
}

public MyAssemblyException( string s,
    Exception e) :
    base( s, e )
{
}

protected MyAssemblyException(
    SerializationInfo info, StreamingContext cxt ) :
    base( info, cxt )
{
}
}
 
構造函數須要的這個異常參數值得討論一下。有時候,你所使用的類庫之一會發生異常。調用你的庫的代碼可能會取得最小的關於可能修正行爲的信息,當你簡單從你使用的異常上傳參數時:
 
public double DoSomeWork( )
{
// This might throw an exception defined
// in the third party library:
return ThirdPartyLibrary.ImportantRoutine( );
}


當你創建異常時,你應該提供你自己的庫信息。拋出你自己詳細的異常,以及包含源異常做爲它的內部異常屬性。你可以提供你所能提供的最多的額外信息:'
 
public double DoSomeWork( )
{
try {
    // This might throw an exception defined
    // in the third party library:
    return ThirdPartyLibrary.ImportantRoutine( );
} catch( Exception e )
    {
      string msg =
        string.Format("Problem with {0} using library",
          this.ToString( ));
      throw new DoingSomeWorkException( msg, e );
    }
}
}


這個新的版本會在問題發生的地方創建更多的信息。當你已經創建了一個恰當的ToString()方法時(參見原則5),你就已經創建了一個可以完整描述問題發生的異常對象。更多的,一個內聯異常顯示了產生問題的根源:也就是你所使用的第三方庫裏的一些信息。

這一技術叫做異常轉化,轉化一個底的層異常到更高級的異常,這樣可以提供更多的關於錯誤的內容。你越是創建多的關於錯誤的額外的信息,就越是容易讓它用於診斷,以及可能修正錯誤。通過創建你自己的異常類,你可能轉化底層的問題到詳細的異常,該異常包含所詳細的應用程序信息,這可以幫助你診斷程序以及儘可能的修正問題。

希望你的應用程序不是經常拋出異常,但它會發生。如果你不做任何詳細的處理,你的應用程序可能會產生默認的.Net框架異常,而不管是什麼錯誤在你調用的方法裏發生。提供更詳細的信息將會讓你以及你的用戶,在實際應用中診斷程序以及可能的修正錯誤大有幫助。當且僅當對於錯誤有不同的行爲要處理時,你才應該創建不同的異常類。你可以通過提供所有基類支持的構造函數,來創建全功能的異常類。你還可以使用InnerException屬性來承載底層錯誤條件的所有錯誤信息。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章