TIPS-如何偵錯被try catch包住的例外
同事詢問,有一段程式碼類似以下結構,用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!");
}
有幾種方法可以解決這個問題:
- 將try ... catch先拆掉。(但測完又要改回去,有點笨)
- Response.Write(ex.StackTrace)取得錯誤所在程式列數資訊。(無法中斷在錯誤列上,進行檢查變數值等進一步偵察)
- 攔截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補充!]