實戰小技巧 - .NET Exception Message、InnerException 與 ToString()

前篇文章提到 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 讓真相消失在風中。

歡迎推文分享:
Published 12 February 2018 09:06 PM 由 Jeffrey
Filed under: ,
Views: 4,118



意見

# 初出茅廬的工程師 said on 13 February, 2018 12:55 AM

To 黑大:

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

# 小賤健 said on 13 February, 2018 12:17 PM

to 1 樓 初出茅廬的工程師,

請使用 ActionFilter 就好啦。

# Johnny Li said on 13 February, 2018 08:42 PM

To 小賤健

你要說的應該是 Exception filter 吧?

Action filter 不是用來捕捉例外的

# 初出茅廬的工程師 said on 13 February, 2018 10:46 PM

To Johnny Li:

謝謝您的回覆,原來還可以用Filter的方式處理!!第一次聽到Exception filter這個名詞,上網找了一下就得到答案了,十分感謝。

參考資料:

www.huanlintalk.com/.../aspnet-web-api-exception-filter.html

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<February 2018>
SunMonTueWedThuFriSat
28293031123
45678910
11121314151617
18192021222324
25262728123
45678910
 
RSS
創用 CC 授權條款
【廣告】
twMVC
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication