這個做法提到什麼情況適合使用 Exceptions 以及相較之下使用 Error Code 所會帶來的問題點。

處理錯誤的時候常常會使用 Error Code 來取代 Exceptions,但是使用 Error Code 並非強制性的所以出錯時並不會影響到程序運作, 所以當我們把 Error Code 回傳給呼叫方時他們經常不會太注重,更何況做出相對應的處理,或者會用大量的程式碼來檢查這些 Error Code, 也會導致核心的邏輯塞滿了需多檢查邏輯。

反之,如果今天都是使用拋出 Exceptions 那麼會使程序附加額外的開銷,所以我們再寫 library 的時候要在我們的 public 方法進行事先處理, 而不要讓使用者去處理這些錯誤處理,這樣使用者也不用在自己的程式碼寫一堆 try/catch 進行檢查。

與 Error Code 相比通常 Exceptions 的表達的意圖更加明確,因為它是專門來描述某一種具體錯誤的類別,也可以透過繼承建立出更加詳細的 Exceptions。

另外 Exceptions 可以在 Stack 中的呼叫一層一層的像上傳遞,直到碰到了適合 catch 邏輯才停下,使用 Error Code 就必須要呼叫方自行記錄與處理 ,這就很容易造成錯誤訊息丟失。

最後一個好處是使用 Exceptions 如果上層沒有適合的 catch 邏輯,那麼整個程序就會停止運行避免數據受損。

以 File.Exists() 方法為例如果檔案存在則回傳 true 否則回傳 false,在 File.Open() 方法則是檔案不存在就接拋出 Exceptions, 會有這樣的區別是因為 File.Exists() 不管檔案存不存在都不會影響我們跟調用者之間的 contract,也就是說不管如何這個方法都能成功執行, 反之 File.Open() 對於成功的定義是檔案要存在、使用者有權限讀取、執行序能夠正常開啟檔案,這些條件要吻合才能說是成功, 因此這些條件一旦不滿足,這個方法就沒有繼續執行的必要了。

也代表我們在命名的時候應該要更明確,讓使用者可以透過名字知道這個方法是用來測試檢查的,使用者也可以透過檢查方法來控制程式的流程, 也能增加一種保護措施,而不是馬上就拋出 Exceptions。

建議如果方法中的某個操作有可能拋出 Exceptions,那麼可以另外提供檢查方法給使用者判斷,我們可以在檢查方法中先判斷必要的條件 如果不滿足條件直接拋出 Exceptions。

例如下面這種不建議的寫法,就是透過捕捉 Exceptions 的方式來處理錯誤。

void Main()
{
	DoesWorkThatMightFail worker = new DoesWorkThatMightFail();
	try
	{
		worker.DoWork();
	}
	catch (WorkerException e)
	{
		ReportErrorToUser("Test Conditions Failed. Please check widgets");
	}
}

public class DoesWorkThatMightFail
{
	public bool TryDoWork()
	{
		if (!TestConditions())
			return false;
		Work(); // may throw on failures, but unlikely
		return true;
	}
	public void DoWork()
	{
		Work(); // will throw on failures.
	}
	private bool TestConditions()
	{
		// Test conditions here
		return true;
	}
	private void Work()
	{
		// Do the work here
	}
}

可以改成呼叫 TryDoWork 方法這樣呼叫方就能事先檢查是否會報錯,一但錯誤就直接回報這樣就不需要 try/catch 進行額外檢查了。

if (!worker.TryDoWork())
{
	ReportErrorToUser("Test Conditions Failed. Please check widgets");
}

Summary

回到做法建議的內容,只要我們跟使用者對於有共識的合約部分就不用特別在意是否要拋出 Exceptions,共識的部分可以透過檢查方法直接採取措施就好, 應該要注重的是那些共識外的錯誤才有必要拋出 Exceptions。