這個做法提出建立你自己客製的 Exception 能夠比直接用預設類別帶來更多詳細的錯誤資訊。

在做法 45 有提到 Exceptions 相關的基礎概念,理解到可以把錯誤相關的資訊都納入到 Exception 類別裡,並且又能透過拋出功能 讓我們在遠離錯誤的地點進行處理與做出應對。

那什麼情況需要客製化 Exception 呢? 首先客製化 Exception 的關鍵目的就是為了要對特定的錯誤進行對應的處理, 例如下面這個例子就是透過 catch 捕捉各種客製的 Exception 然後對它們進行專門的處理。

try
{
	Foo();
	Bar();
}
catch (MyFirstApplicationException e1)
{
	FixProblem(e1);
}
catch (AnotherApplicationException e2)
{
	ReportErrorAndContinue(e2);
}
catch (YetAnotherApplicationException e3)
{
	ReportErrorAndShutdown(e3);
}
catch (Exception e)
{
	ReportGenericError(e);
	throw;
}
finally
{
	CleanupResources();
}

也就是說需要專門處理的錯誤才有需要客製 Exception,否則有兩個錯誤擁有相同處理的流程那為甚麼不把它們合併成一個 Exception 就好?

另外也可以透過捕捉最底層的 Exception 類別再透過 switch 的方式來切換處理流程,但是這種流程會違反 OCP 原則, 也就是不管要新增錯誤或者修改錯誤名稱都要調整 switch 裡面的程式碼。

private static void SampleTwo()
{
	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);
				throw;
		}
	}
	finally
	{
		CleanupResources();
	}
}

一但決定要建立客製化的 Exception 那首先要確定最底層的類別一定要是 System.Exception,也可以從繼承 System.Exception 的類別 再衍生出自己的新類別,如果是從 System.Exception 衍生出來的類別要注意需要建立下面四個建構函式並對應到底層建構函式。

public class MyAssemblyException : Exception
{
	public MyAssemblyException() :
		base()
	{
	}
	public MyAssemblyException(string s) :
		base(s)
	{
	}
	public MyAssemblyException(string s, Exception e) :
		base(s, e)
	{
	}
	// May not be supported on all platforms in .NET Core
	protected MyAssemblyException(SerializationInfo info, StreamingContext cxt) :
		base(info, cxt)
	{
	}
}

對於 MyAssemblyException(string s, Exception e) 這個建構函式可以補充說明一下,它的用途是把捕捉到的 Exception 轉換成 InnerException ,最後在包裝成自己的 MyAssemblyException 進行拋出。

這個流程常出現在呼叫第三方 library 的時候,假設此第三方 library 拋出了一個客製化的 Exception,對它們來說是有意義的操作但對我們來說 並不知道怎麼處理這個 Exception,所以我們就可以在建立一個客製化的 Exception,把第三方的包起來變成 InnerException,並進行補充說明。

public double DoSomeWork()
{
	try
	{
		return ThirdPartyLibrary.ImportantRoutine();
	}
	catch (ThirdPartyException e)
	{
		var msg = $"Problem with{ToString()}using library";
		throw new DoingSomeWorkException(msg, e);
	}
}

這個技巧也稱為 exception translation 來將底層的錯誤轉換成更有意義的高層級錯誤,這種情況就很適合建立客製化的 Exception,因為我們能夠 進行額外補充說明來讓呼叫者處理的時候有更價明確的判斷依據。


Summary

這個做法建議除非有要對捕捉到的錯誤進行專門的事後處理,否則就應該用相同的 Exception 類別即可,並且建立衍生類別的時候要記得建立四個建構函式 並對應到底層的建構函式,還有 exception translation 能夠把錯誤包裝成 InnerException 並提供更有價值的訊息。