前篇文章提到 try catch 時若只保留 Exception.Message,可能遺失 InnerException 及 StackTrace 錯失破案重要線索。文章迴響顯示這是個值得介紹的實戰技巧,故再補充一篇。
在某些應用情境我們會選擇使用 try … catch 達成特定目的,例如:(註:Exception 的官方翻譯為例外狀況,這裡容我以用較口語化的「錯誤」取代)
- 捕捉可預期錯誤,進行補救並繼續執行程式
例如:發現作業失敗時,Rollback 交易、寫 Log、通知管理員、退回前一步驟請使用者再試一次... 比程式直接 Crash 來得好。 - 捕捉可預期錯誤,改顯示較易懂的錯誤訊息
例如: 補捉 KeyNotFoundException 傳回錯誤訊息「系統資料未包含您指定的選項,請連絡客服人員」,會比「指定的索引鍵不在字典中」更容易理解。 - 捕捉錯誤後改抛回自訂錯誤型別
優點是上層呼叫端可以使用 catch (MyCustomException mce) 針對自訂錯誤執行特定邏輯。
而這裡有個小技巧,Exception 有個屬性 InnerException,補捉錯誤並拋出自訂錯誤時要記得將原始 Exception 放入自訂錯誤的 InnerException(稍後將有範例),以便呼叫端追查真實錯誤原因。
關於使用 try … catch 的正確姿勢,微軟文件庫有份文件:例外狀況的最佳作法 - Microsoft Docs可以參考。
下面的程式示範如何捕捉錯誤並改抛回自訂 MyCustException。建構 MyCustException 時要將捕捉到的 ArgumentException 當成 InnerException 包進物件一併傳到上層。而 Main() 在 try catch 時犯了一個錯,它只顯示 MyCustException.Message 就交差:
class Program
{
staticvoid Main(string[] args)
{
try
{
Test();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.Read();
}
staticvoid Test()
{
try
{
InnerCall();
}
catch (Exception ex)
{
thrownew MyCustException(
"InnerCall出錯",
ex);
}
}
staticvoid InnerCall()
{
thrownew ArgumentException("明知故犯");
}
}
class MyCustException : ApplicationException
{
public MyCustException(string message,
Exception innerException) :
base(message, innerException)
{
}
}
如下圖所示,執行時只會看到:
至於為什麼 InnerCall 出錯,錯在哪一段程式,鬼才知道?
要挖出錯誤根源,應檢查 ex.InnerException 是否不為 null,則有內部錯誤資訊,再由 ex.InnerException.Message 取出底層錯誤訊息,但要留意 ex.InnerException 可能還會有 InnerException,真正的錯誤訊息藏在 ex.InnerException.InnerException.Message。換句話說,得寫段遞迴一路剝洋葱才能 100% 保證挖出真正出錯原因。另外,想像 ASP.NET 出錯畫面(YSOD,Yellow Screen of Death)顯示程式碼錯在哪一行,則要透過 Exception.StackTrace取得。
聽起來很麻煩,但有條捷徑,將 ex.Message 改成 ex.ToString() 就好了!
如上圖所示,ToString() 會包含 InnerException (黃字部分),以及 StackTrace (方法名稱與程式行數),該有的資訊都有。
【結論】try catch 時要保存或顯示完整錯誤資訊,建議改用 ToString(),別只用 Message 讓真相消失在風中。