同事詢問,有一段程式碼類似以下結構,用try...catch包住函數呼叫,當函數出錯,程式優雅地顯示錯誤訊息,卻漏失了錯在哪一列程式碼等細節,造成偵錯困難。

protected void Page_Load(object sender, EventArgs e)
{
    try
    {
        raisException();
    }
    catch (Exception ex)
    {
        Response.Write("Error:" + ex.Message);
        Response.End();
    }
}
private void raisException()
{
    throw new ApplicationException("Something Wrong!");
}

有幾種方法可以解決這個問題:

    1. 將try ... catch先拆掉。(但測完又要改回去,有點笨)
    2. Response.Write(ex.StackTrace)取得錯誤所在程式列數資訊。(無法中斷在錯誤列上,進行檢查變數值等進一步偵察)
    3. 攔截First Chance Exception,讓偵錯中斷點可以停在throw new Application那一列上。(看來是較好的做法)

對Debugger來說,當程式有例外發生,會接到兩次通知。第一次通知為First Chance Exception,接著如果出錯的程式有被包在try catch中,就會由指定的例外處理(Exception Handler)接手;若程式碼沒有try catch或發生的例外不在Exception Handler要catch的對象清單中,就會再觸發Second Chance Exception,這就是第二次通知。

我們通常將try catch裡發生的例外視為可容忍、預料中的狀況,可能在程式執行中會發生多次(但這會嚴重傷害效能,還是要儘可能降低進行例外處理的頻率,例如: 事先檢查參數是否合理,不合理就不要繼續,以免執行出錯觸發catch裡的邏輯,對效能很傷)。基於這樣的假設,Debugger預設會忽略First Chance Exception的通知,只有Code處理不了時,Debugger才認定程式出錯,進入偵錯模式,讓開發者可以進行Line-By-Line偵錯。

回到上述的例子,由於raiseException()被包在try catch中,故出錯時只會有First Chance Exception通知,接著被catch裡的程式接手處理,並不會觸發偵錯中斷。因此,我們只需小做調整,就可以要求Debugger在First Chance Exception(throw new Application那一列)時就把控制權交給我們。方法是按Ctrl-Alt-E叫出Exceptions設定對話框,勾選CLR Throw選項,就大功告成囉!

開啟後,你可能會發現不少原本被包在try catch裡的Exception都會揭竿而起,讓你的偵錯過程一再被打斷,因此也可以選擇針對某些Exception才抓First Chance(如下圖)。但是,若程式中真有這麼多First Chance Exception,也可視為一種警訊,應該檢討一下是否太依賴try catch,或把Exception Handler當成正常邏輯使用,這不是良好的設計。

[Update: 針對已知可疑位置的問題,可以使用System.Diagnostics.Break()強制進偵錯模式(跟Javascript裡寫debugger;有異曲同工之妙),感謝網友ChrisTorng補充!]


Comments

# by ChrisTorng

我都用 ex.ToString() 會有完整的錯誤訊息加執行堆疊。另外也可以在 catch 中加上 System.Diagnostics.Break(); 就會自動中斷進入偵錯模式。 但以良好的程式觀點來說,不應該 catch 最通用的 Exception,而應該針對特定的 Exception 類別各別寫訊息輸出,也不應該把實際的錯誤訊息傳給使用者。 可以考慮用 #if DEBUG 來區別偵錯版與正式版的錯誤處理。 另外也有看過文章說可以把 MDAs (Managed Debugging Assistants) 給打勾,它可以幫忙抓到一些隱藏的小問題。http://msdn.microsoft.com/en-us/library/d21c150d.aspx

# by 邪惡油頭

看來Java跟.NET還是有些差異啊! 基本上可捕捉的exception問題應該要能在錯誤訊息的堆疊當中看出來才是.不能捕捉的自然就會是error而不是Exception啦! 而且try catch的確是保障你在runtime時期的系統穩定的重要機制嗎?

# by 菜逼巴工程師

想請問一下,如果我使用三層式架構,在Data層中把例外拋出去了,那我前端網頁有「必要」再用try catch接例外嗎?

# by Jeffrey

to 菜逼巴工程師, 依一般設計準則,網頁應該要捕捉例外,將例外細節寫入Log,然後傳回「抱歉系統無法處理您的需求,請洽客服」之類較友善的訊息,避免直接噴出IIS或ASP.NET原始錯誤訊息(Yellow Screen of Death,YSOD)。但這樣有一個問題是,客戶報案時除了時間點沒有其他線索,針對這個我有一個改良做法,將例外細節寫入Log時包含錯誤訊息、Callstack(出錯的程式碼位置)、URL參數、Cookie、使用者身分、IP等資料(愈能還原現場愈好),並為每次錯誤取流水號,在顯示友善錯誤訊息時附上錯誤流水號,接獲使用者回報時方便由流水號調閱事故記錄展開調查。

Post a comment