正確重拋例外 (Exception) 的方式
前言
最近在Review專案程式碼時,發現有把Try-Catch捕捉到之Exception直接拋出的狀況(如左下圖所示),其實這樣是不恰當的做法;較好的方式是使用throw重拋例外就好了(如右下圖所示)。這兩者到底由何差異,以下將就簡單實例來進行探討。
差異比較
大家都知道在捕捉到例外時,可以從Exception.StackTrace屬性中來獲得呼叫堆疊資訊,進而了解錯誤發生的確切位置,以方便除錯作業的進行。以下將就該資訊的完整度探討兩者之差異。
首先,先建立一個呼叫的推疊類別做測試。當呼叫Aoo.DoSomethingInBoo()方法時,會依序向Boo, Coo, Doo的DoSomethingInXoo()方法執行,且最後在Doo.DoSomethingInDoo()中故意造成錯誤,再交由Boo.DoSomethingInBoo()捕捉此例外錯誤,並以 throw ex 或 throw 二次拋出例外至呼叫端中來比較差異。
呼叫端測試主程式如下,捕捉到例外錯誤後印出StackTrace資訊於畫面上
throw ex
首先使用throw ex來拋出例外,執行看看
當我們使用 throw ex 時,會重置呼叫堆疊,造成確切例外發生位置的遺失。如下圖所示,錯誤發生點明明就在Doo.DoSomethingInDoo()中,而StackTrace提供的資料卻只有到Boo.DoSomethingInBoo(),因此單就此訊息無法得知實際錯誤到底是在哪個類別方法中發生的,實在是會造成很大的困擾。
throw
接著使用throw來拋出例外,執行看看
使用throw拋出錯誤時,並不會重置呼叫堆疊,保有完整StackTrace資訊,方便獲得例外發生點來進行除錯。如下圖所示,明確地指出錯誤發生點就在Doo.DoSomethingInDoo()中,獲得完整呼叫堆疊資訊。
以下是完整的測試代碼,有興趣的朋友可以自行測試
namespace RethrowExceptionConsole
{
class Program
{
static void Main(string[] args)
{
try
{
Aoo aoo = new Aoo();
aoo.DoSomethingInAoo();
}
catch (Exception ex)
{
// display stack trace info
Console.WriteLine( "----- stack info -----" );
Console.WriteLine( ex.StackTrace.ToString() );
Console.WriteLine( "----------------------" );
}
Console.Read();
}
}
// level 1
public class Aoo
{
public Boo boo = new Boo();
public void DoSomethingInAoo()
{ boo.DoSomethingInBoo(); }
}
// level 2
public class Boo
{
public Coo coo = new Coo();
public void DoSomethingInBoo()
{
try
{
coo.DoSomethingInCoo();
}
catch (Exception ex)
{
// log here
// ...
// ## Rethrow exception ##
// destroys the strack trace info!
//throw ex;
// ## Rethrow exception ##
// preserves the stack trace
throw;
}
}
}
// level 3
public class Coo
{
public Doo doo = new Doo();
public void DoSomethingInCoo()
{ doo.DoSomethingInDoo(); }
}
// level 4
public class Doo
{
public void DoSomethingInDoo()
{
int i = 0;
i = 1 / i; // cause exception
}
}
}
希望此篇文章可以幫助到需要的人
若內容有誤或有其他建議請不吝留言給筆者喔 !