Chapter 5 - Item 45 : Use Exceptions to Report Method Contract Failures

Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得

本書第五章開始討論例外處理,例外處理是一件很容易被開發者忽略的事(尤其是一人專案)。在多人開發或是需要開放 API 給他人呼叫時,例外處理是必須要被好好處理的。

在程式發生未符預期情況時(或未符合條件)擲出例外,能夠讓錯誤早點浮上檯面,避免影響到後續流程與資料正確性;實務上有時會發生開發者為了圖一時方便,直接在 catch 區塊不做任何處置,造成後續追蹤錯誤困難。然而,過猶不及,也並非所有情況都必須強制擲出例外。比如說,File.Exists 當檔案不存在時回傳 false,而非跳出例外,因為檔案不存在這件事是方法指定要檢查的;檔案存在與否都在預期的情況內。但 File.Open 則會在檔案不存在時擲出例外,因為方法名稱已經很清楚的告訴呼叫端,就是要對檔案做讀取,而檔案不存在是不在預設的情境內,故擲出例外。

由此可知,擲出例外與否和方法命名與實際意圖有絕對關係。

一般的準則是:

例外處理並不適用於流程控制(耗費效能),只有在系統無法復歸正常狀態時擲出例外。

下面來看一個例子。

var worker = new DoesWorkThatMightFail();
try
{	        
	worker.DoWork();
}
catch (WorkerException ex)
{
	Debug.WriteLine("WorkerException occurs");
}

此寫法表示預期 DoWork 有可能擲出例外,然而某些情況能夠允許此錯誤發生(流程上可以接受復歸)。當有這個情況時,利用擲出例外控制流程就不是一個好選擇。

改寫程式碼:

public class DoesWorkThatMightFail
{
	public bool TryDoWork()
	{
		if(!TestConditions())
			return false;
		
		Work(); // 出錯時會擲出例外
		return true;
	}
	
	private bool TestConditions()
	{
		// 省略程式碼
		// 檢查是否符合 Work 條件
		return true;
	}
	
	public void DoWork()
	{
		Work(); // 執行指定工作
	}

	private void Work()
	{
		// 省略
		// 出錯時擲出例外
	}
}

客戶端程式碼:

if(!worker.TryDoWork())
    Debug.WriteLine("WorkerException occurs");

DoesWorkThatMightFail 提供了 TryDoWork 公有方法,並在類別內提供 TestConditions 私有方法。早期的檢查方法可用性並回傳布林值,作為流程控制的依據。(註:Work 方法自身也有可能擲出例外,此時布林值涵蓋範圍要看實際情況與設計而定。)

結論:
1. 利用例外處理讓 api 更明確表達意圖,早期發現錯誤早期提示。
2. 例外處理不要用來做流程控制,利用回傳布林值檢查程式狀態並另做處理。