[Tips][C#] 正確重拋例外 (Exception) 的方式

正確重拋例外 (Exception) 的方式

前言

 

最近在Review專案程式碼時,發現有把Try-Catch捕捉到之Exception直接拋出的狀況(如左下圖所示),其實這樣是不恰當的做法;較好的方式是使用throw重拋例外就好了(如右下圖所示)。這兩者到底由何差異,以下將就簡單實例來進行探討。

 

image   image

 

 

差異比較

 

大家都知道在捕捉到例外時,可以從Exception.StackTrace屬性中來獲得呼叫堆疊資訊,進而了解錯誤發生的確切位置,以方便除錯作業的進行。以下將就該資訊的完整度探討兩者之差異。

 

首先,先建立一個呼叫的推疊類別做測試。當呼叫Aoo.DoSomethingInBoo()方法時,會依序向Boo, Coo, Doo的DoSomethingInXoo()方法執行,且最後在Doo.DoSomethingInDoo()中故意造成錯誤,再交由Boo.DoSomethingInBoo()捕捉此例外錯誤,並以 throw ex 或 throw 二次拋出例外至呼叫端中來比較差異。

 

image

 

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

 

image

 

 

throw ex

 

首先使用throw ex來拋出例外,執行看看

 

image

 

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

 

image

 

 

throw

 

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

 

image

 

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

 

image

 

以下是完整的測試代碼,有興趣的朋友可以自行測試


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 
        }
    }
}

希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !