c#
throw exception
正確重拋例外 (Exception) 的方式
2018/10/17 15:04:33
0
2665
正確重拋例外 (Exception) 的方式
簡介 |
本文說明如何正確重拋例外 (Exception),避免重要資訊被錯誤的重拋方式給隱藏。 |
作者 |
陳志澤 |
前言
在 Review 專案程式碼時,常會發現有把 Try-Catch 捕捉到之 Exception 直接拋出的狀況 (如左下圖所示),其實這樣是非常不恰當的做法;較好的方式是使用 throw 重拋例外就好了 (如右下圖所示)。這兩者到底由何差異,以下將就簡單實例來進行探討。
在 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 來拋出例外,執行看看。

呼叫端測試主程式如下,捕捉到例外錯誤後印出 StackTrace 資訊於畫面上。

使用 throw ex 拋出
如下圖所示,先使用 throw ex 來拋出例外,執行看看。

當我們使用 throw ex 時,會重置呼叫堆疊,造成確切例外發生位置的遺失。如下圖所示,錯誤發生點明明就在 Doo.DoSomethingInDoo() 中,而 StackTrace 提供的資料卻只有到 Boo.DoSomethingInBoo(),因此單就此訊息無法得知實際錯誤到底是在哪個類別方法中發生的,實在是會造成很大的困擾。

使用 throw 拋出
接著使用 throw 來拋出例外,執行看看。

使用 throw 拋出錯誤時,並不會重置呼叫堆疊,保有完整 StackTrace 資訊,方便獲得例外發生點來進行除錯。如下圖所示,明確地指出錯誤發生點就在 Doo.DoSomethingInDoo() 中,獲得完整呼叫堆疊資訊。

測試代碼
以下是完整的測試代碼,有興趣的朋友可以自行測試
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 } } |