前篇文章提到 try catch 時若只保留 Exception.Message,可能遺失 InnerException 及 StackTrace 錯失破案重要線索。文章迴響顯示這是個值得介紹的實戰技巧,故再補充一篇。

在某些應用情境我們會選擇使用 try … catch 達成特定目的,例如:(註:Exception 的官方翻譯為例外狀況,這裡容我用較口語化的「錯誤」取代)

  1. 捕捉可預期錯誤,進行補救並繼續執行程式
    例如:發現作業失敗時,Rollback 交易、寫 Log、通知管理員、退回前一步驟請使用者再試一次... 比程式直接 Crash 來得好。
  2. 捕捉可預期錯誤,改顯示較易懂的錯誤訊息
    例如: 補捉 KeyNotFoundException 傳回錯誤訊息「系統資料未包含您指定的選項,請連絡客服人員」,會比「指定的索引鍵不在字典中」更容易理解。
  3. 捕捉錯誤後改抛回自訂錯誤型別
    優點是上層呼叫端可以使用 catch (MyCustomException mce) 針對自訂錯誤執行特定邏輯。
    而這裡有個小技巧,Exception 有個屬性 InnerException,補捉錯誤並拋出自訂錯誤時要記得將原始 Exception 放入自訂錯誤的 InnerException(稍後將有範例),以便呼叫端追查真實錯誤原因。

關於使用 try … catch 的正確姿勢,微軟文件庫有份文件:例外狀況的最佳作法 - Microsoft Docs 可以參考。

下面的程式示範如何捕捉錯誤並改抛回自訂 MyCustException。建構 MyCustException 時要將捕捉到的 ArgumentException 當成 InnerException 包進物件一併傳到上層。而 Main() 在 try catch 時犯了一個錯,它只顯示 MyCustException.Message 就交差:

    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Test();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.Read();
        }
 
        static void Test()
        {
            try
            {
                InnerCall();
            }
            catch (Exception ex)
            {
                throw new MyCustException(
                    "InnerCall出錯", 
                    ex);               
            }
        }
 
        static void InnerCall()
        {
            throw new 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 讓真相消失在風中。


Comments

# by 初出茅廬的工程師

To 黑大: 謝謝您的分享,此外,微軟提供的官方說明也寫得蠻清楚的(覺得意外)。在此想請教您一個問題,我是寫MVC的工程師,同事建議在每個Action裡面都先建立try catch後,才在裡面寫需要的邏輯,可以在出錯的時候能被捕捉,我想請教如果是您會怎麼在Controller運用try catch呢?

# by 小賤健

to 1 樓 初出茅廬的工程師, 請使用 ActionFilter 就好啦。

# by Johnny Li

To 小賤健 你要說的應該是 Exception filter 吧? Action filter 不是用來捕捉例外的

# by 初出茅廬的工程師

To Johnny Li: 謝謝您的回覆,原來還可以用Filter的方式處理!!第一次聽到Exception filter這個名詞,上網找了一下就得到答案了,十分感謝。 參考資料: http://www.huanlintalk.com/2013/01/aspnet-web-api-exception-filter.html

Post a comment