編寫高質量代碼改善C#程序的157個建議[避免finaly內的無效代碼、避免嵌套異常、避免吃掉異常、注意循環異常處理]


前言  

  本文已同步到http://www.cnblogs.com/aehyok/p/3624579.html。本文主要來學習以下幾點建議

  建議61、避免在finally內撰寫無效代碼

  建議62、避免嵌套異常

  建議63、避免“吃掉”異常

  建議64、為循環增加Tester-Doer模式而不是將try-catch置於循環內

建議61、避免在finally內撰寫無效代碼

先直接來看一下三個簡單的try catch方法

    public class User
    {
        public string Name { get; set; }
    }

    class Program
    {

        static void Main(string[] args)
        {
            Console.WriteLine(Test1());
            Console.WriteLine(Test2());
            Console.WriteLine(Test3().Name);

            Console.ReadLine();
        }

        public static int Test1()
        {
            int i = 0;
            try
            {
                i = 1;
            }
            finally
            {
                i = 2;
                Console.WriteLine("\t 將int結果改為2,finnally執行完畢。");
            }
            return i;
        }

        public static int Test2()
        {
            int i = 0;
            try
            {
                return i = 1;
            }
            finally
            {
                i = 2;
                Console.WriteLine("\t 將int結果改為2,finnally執行完畢。");
            }
        }

看完代碼你心里大概也有了一個答案了吧

這些如果通過IL來解釋,還是比較容易的,在此就不進行贅述了。

  在CLR中,方法的參數以及返回值都是用棧來保存的。在方法內部,會首先將參數依次壓棧,當需要使用這些參數的時候,方法會直接去棧里取用參數值,方法返回時,會將返回值壓入棧頂。如果參數的類型是值類型,壓棧的就是復制的值,如果是引用類型,則在方法內對於參數的修改也會帶到方法外。

建議62、避免嵌套異常  

在建議59中已經強調過,應該允許異常在調用堆棧中往上傳播,不要過多使用catch,然后再throw。果斷使用catch會帶來兩個問題:

1、代碼更多了。這看上去好像你根本不知道該怎么處理異常,所以你總在不停地catch.

2、隱藏了堆棧信息,使你不知道真正發生異常的地方。

來看一下下面的代碼

        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("NoTry\n");
                MethodNoTry();
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.StackTrace);
            }
            try
            {
                Console.WriteLine("WithTry\n");
                MethodWithTry();
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.StackTrace);
            }
            Console.ReadLine();
        }

        public static int Method()
        {
            int i = 0;
            return 10 / i;
        }

        public static void MethodNoTry()
        {
            Method();
        }

        public static void MethodWithTry()
        {
            try
            {
                Method();
            }
            catch (Exception exception)
            {
                throw exception;
            }
        }

執行結果

  可以發現,MethodNoTry的方法可以查看到發生異常錯誤的地方,而MethodWithTry根本不清楚發生錯誤的地方了。調用的堆棧倍重置了。如果這個方法還存在另外的異常,在UI層將永遠不知道真正發生錯誤的地方,給開發者帶來不小的麻煩。 

除了在建議59中提到的需要包裝異常的情況外,無故地嵌套異常是我們要極力避免的。當然,如果真得需要捕獲這個異常來恢復一些狀態,然后重新拋出,代碼來起來應該可以這樣:

            try
            {
                MethodWithTry();
            }
            catch(Exception)
            {
                ///工作代碼
                throw;
            }

或者稍作改動

            try
            {
                MethodWithTry();
            }
            catch
            {
                ///工作代碼
                throw;
            }

盡量避免下面這樣引發異常:

            try
            {
                MethodWithTry();
            }
            catch(Exception exception)
            {
                ///工作代碼
                throw exception;
            }

直接throw exception而不是throw將會重置堆棧消息。

 

建議63、避免“吃掉”異常 

  看了建議62之后,你可能已經明白,嵌套異常是很危險的行為,一不小心就會將異常堆棧信息,也就是真正的Bug出處隱藏起來。但這還不是最嚴重的行為,最嚴重的就是“吃掉”異常,即捕獲然后不向上層throw拋出。

  避免“吃掉”異常,並不是說不應該“吃掉”異常,而是這里面有個重要原則:該異常可悲預見,並且通常情況它不能算是一個Bug。

  想象你正在對上萬份文件進行解密,這些文件來自不同的客戶端,很有可能存在文件倍破壞的現象,你的目標就是要講解密出來的數據插入數據庫。這個時候,你不得不忽略那些解密失敗的問題,讓這個過程進行下去。當然,記錄日志是必要的, 因為后期你可能會倍解密失敗的文件做統一的處理。

  另外一種情況,可能連記錄日志都不需要。在對上千個受控端進行控制的分布式系統中,控制端需要發送心跳數據來判斷受控端的在線情況。通常的做法是維護一個信號量,如果在一個可接受的阻滯時間如(如500ms)心跳數據發送失敗,那么控制端線程將不會收到信號,即可以判斷受控端的斷線狀態。在這種情況下,對每次SocketException進行記錄,通常也是沒有意義的。

  本建議的全部要素是:如果你不知道如何處理某個異常,那么千萬不要“吃掉”異常,如果你一不小“吃掉”了一個本該網上傳遞的異常,那么,這里可能誕生一個BUg,而且,解決它會很費周折。 

建議64、為循環增加Tester-Doer模式而不是將try-catch置於循環內

  如果需要在循環中引發異常,你需要特別注意,因為拋出異常是一個相當影響性能的過程。應該盡量在循環當中對異常發生的一些條件進行判斷,然后根據條件進行處理。可以做一個測試:

        static void Main(string[] args)
        {
            CodeTimer.Initialize();

            CodeTimer.Time("try..catch..", 1, P1);

            CodeTimer.Time("Tester-Doer", 1, P2);

            Console.ReadLine();
        }

        public static void P1()
        {
            int x = 0;
            for (int i = 0; i < 1000; i++)
            {
                try
                {
                    int j = i / x;
                }
                catch
                { 
                    
                }
            }
        }

差距相當明顯。以上代碼中,我們預見了代碼可能會發生DivideByZeroException異常,於是,調整策略,對異常發生的條件進行了特殊處理:Continue,讓效率得到了極大的提升。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM