異常處理、性能、建議

異常處理的作用

無論編碼技術有多好,程序都必須處理任何可能出現的錯誤。如果應用程序不對異常進行任何處理,直接拋出最終會導致應用程序退出。爲了提高程序的健壯性,必須對任何異常進行必要的處理。

異常執行流程

異常處理的關鍵:

1、  定義什麼錯誤

2、  判斷錯誤

3、  從錯誤中恢復

如果在try塊中拋出一個異常,CLR將搜索捕捉類型與異常類型相同的catch塊,如果沒有找到,CLR會調用棧的更高一層搜索與異常類型匹配的捕捉類型,如果到了棧的頂部還未找到,就會發生一個未處理異常。一旦找到一個匹配的catch塊,所有內層的finally塊會被執行;如果沒有找到,內層的finally塊是不會執行的,這一點要特別注意。接着執行匹配catch塊內容,最後是該catch塊對應的finally塊(如果有的話)。

下面代碼展現了異常處理的關鍵流程。

private void SomeMethod()
        {
            try
            {
                //可能會拋異常的代碼放在這裏
            }
            catch (InvalidOperationException)
            {
                //從InvalidOperationException恢復的代碼放在這裏
            }
            catch (IOException)
            {
                //從IOException恢復的代碼放在這裏
            }
            catch
            {
                //從除上面的異常外的其他異常恢復的代碼放在這裏
                ////捕捉到任何異常,通常要重新拋出異常。
                throw;
            }
            finally
            {
                //這裏的代碼總是執行,對始於try塊的任何操作進行清理
                //這裏的代碼總是執行
            }
            // 如果try塊沒有拋出異常,或異常被捕獲後沒有拋出或沒有重新拋出異
     //常,就執行這裏的代碼。 }

 

  下面代碼展現三種拋出異常方式對性能的影響 

View Code
 internal class Program
    {
        private static void Main(string[] args)
        {

            int times = 3000;
            string test = "test";
            int a = 0;
            Stopwatch stopwatch;

            //TryParse模式
            stopwatch = Stopwatch.StartNew();
            for (int i = 0; i < times; i++)
            {
                Int32.TryParse(test, out a);
            }
            stopwatch.Stop();
            Console.WriteLine("TryParse模式避免異常:" + stopwatch.ElapsedMilliseconds);

            //拋出指定的異常實例
            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < times; i++)
            {
                try
                {
                    if (!int.TryParse(test, out a))
                    {
                        throw new ArgumentException(test);
                    }
                }
                catch
                {

                }
            }
            stopwatch.Stop();
            Console.WriteLine("拋出指定的異常:" + stopwatch.ElapsedMilliseconds);

            //在嵌套層裏面不做任何異常處理直接往頂層拋出
            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < times; i++)
            {
                try
                {
                    MyClass1.Method1();
                }
                catch  
                {
                }
            }
            stopwatch.Stop();
            Console.WriteLine("在嵌套層裏面不做任何異常處理直接往頂層拋出:" + stopwatch.ElapsedMilliseconds);

            // 嵌套層已處理異常,得體恢復返回
            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < times; i++)
            {
                MyClass4.Method4();
            }
            stopwatch.Stop();
            Console.WriteLine("嵌套層已處理異常,得體恢復返回:" + stopwatch.ElapsedMilliseconds);
            Console.ReadLine();

        }
    }

    internal class MyClass1
    {
        public static void Method1()
        {
            MyClass2.Method2();
        }
    }

    internal class MyClass2
    {
        public static void Method2()
        {
            MyClass3.Method3();
        }
    }

    internal class MyClass3
    {
        public static void Method3()
        {
            int.Parse("test");
        }
    }


    internal class MyClass4
    {
        public static void Method4()
        {
            MyClass5.Method5();
        }
    }

    internal class MyClass5
    {
        public static void Method5()
        {
            MyClass6.Method6();
        }
    }

    internal class MyClass6
    {
        public static string Method6()
        {
            string a = "";
            try
            {
                int.Parse("test");
            }
            catch  
            {
                a = "";
            }
            return a;
        }
    }

 

運行結果:

 

 從運行結果來看指定拋出異常性能比直接拋異常好一點,爲什麼會這樣?請拍磚。在嵌套層裏面進行異常處理與直接往頂層拋異常的性能相差不大,

但最重要的地方是對錯誤進行恢復。異常處理的重點是錯誤的恢復、處理。

 

微軟對異常處理方式建議

對於可能在常見方案中引發異常的成員,可以考慮使用 Tester-Doer 模式來避免與異常相關的性能問題。

例如,對於函數的參數問題,就必須創建檢測類檢測輸入是否正確。對於可能在常見方案中引發異常的成員,可以考慮使用 TryParse 模式來避免與異常相關的性能問題。

TryParse模式參考int.TryParse等.就是創建正常運行的條件避免拋出異常.

對於可能在常見方案中引發異常的成員,可以考慮使用 Tester-Doer 模式來避免與異常相關的性能問題。

Tester-doer 模式將分爲了調用,可能會引發異常,分成兩個部分: 一種測試儀和實幹家。 Tester 對可能導致 Doer 引發異常的狀態執行測試。 測試恰好插入在引發異常的代碼之前,從而防範異常發生。

下面的代碼示例演示此模式的 Doer 部分。 該示例包含一個方法,在向該方法傳遞 null(在 Visual Basic 中爲Nothing)值時該方法將引發異常。 如果頻繁地調用該方法,就可能會對性能造成不良影響。

View Code
public class Doer
{
    // Method that can potential throw exceptions often.
    public static void ProcessMessage(string message)
    {
        if (message == null)
        {
            throw new ArgumentNullException("message");
        }
    }
    // Other methods...
}

 

  下面的代碼示例演示此模式的 Tester 部分。 該方法利用一個測試來避免在 Doer 將引發異常時調用 Doer (ProcessMessage)。

View Code
public class Tester
{
    public static void TesterDoer(ICollection<string> messages)
    {
        foreach (string message in messages)
        {
            // Test to ensure that the call
            // won't cause the exception.
            if (message != null)
            {
                Doer.ProcessMessage(message);
            }
        }
    }
}


對於可能在常見方案中引發異常的成員,可以考慮使用 TryParse 模式來避免與異常相關的性能問題。若要實現 TryParse 模式,

需要爲執行可在常見方案中引發異常的操作提供兩種不同的方法。 第一種方法 X, 執行該操作並在適當時引發異常。

第二種方法 TryX, 不引發異常,而是返回一個 Boolean 值以指示成功還是失敗。

由對 TryX 的成功調用所返回的任何數據都通過使用 out(在 Visual Basic 中爲 ByRef)參數予以返回。Parse 和 TryParse 方法就是此模式的示例。

爲每個使用 TryParse 模式的成員提供一個引發異常的成員。只提供 TryX 方法幾乎在任何時候都不是正確的設計,因爲使用該方法需要了解 out 參數。

此外,對於大多數常見方案來說,

異常對性能的影響不會構成問題;因此應在大多數常見方案中提供易於使用的方法。

 結論: 由微軟提供的Doer-Tester模式可知處理異常的方式最好是不要引發異常,在可能拋出異常的地方進行必要的檢查和驗證。例如對實參進行驗證。

總結:實現自己的方法時,如果方法無法完成方法名所指明的任務,就應該拋出異常或對錯誤進行得體恢復。拋出異常時應該詳細說明方法爲什麼無法完成任務。對於未指定處理的異常通常被寫入日誌,開發人員必須修復該異常所在的bug。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章