THE ELEMENTS OF C# STYLE

|前言程序員

  程序員其實藝術家,靈動的雙手如行雲流水般在鍵盤上創造着生命的奇蹟,我認爲代碼是有靈魂的。同一個模塊,在每一個程序員手中所締造出來的是不相同的。算法

   最終,這個模塊或者實現了最初的業務,可是回過頭看看你的做品,你會認爲她是你的藝術品,仍是她就是一坨Code?編程

   好吧,爲了普及C#是最美的編程語言這種思想,我決定寫這篇【THE ELEMENTS OF C# STYLE】,歡迎吐槽!設計模式

|通常原則 緩存

1. 堅持最小驚奇原則
   簡單性(Simplicity)
   用簡單的類和簡單的方法知足用戶指望
   清晰性(Clarity)
   確保每一個類、接口、方法、變量和對象都有清晰的目的,並闡明什麼時候、何處、如何使用它們
   完整性(Completeness)
   提供任一可能的用戶指望找到和使用的最小功能,建立完整的文檔,描述全部特性和功能
   一致性(Consistency)
   類似實體的外觀和行爲應該相同,不一樣實體的外觀和行爲應該不一樣,應該儘量制定和遵照相關標準
   健壯性(Robustness)
   對軟件中可能出現的錯誤和異常作出預測,並將解決方法記入文檔,不要隱藏錯誤,也不要等着用戶去發現錯誤
2. 第一次就作對
3. 記錄全部規範的行爲併發

|格式    編程語言

1. 第一次就作對工具

1.1. 使用快捷鍵 Ctrl + K + D 來格式化 .CS 代碼
1.2. 在流程控制語句中使用語句塊花括號
但若控制體中爲如下單句語句,能夠不使用花括號
return, break, continue, throw, yield return, gotopost

      if(a<2) return;

2. 類的組織測試

2.1.使用 .CS 文件右鍵菜單中頂部的Organize Usings-> Remove and Sort 來整理 using 指令
2.2.使用 #region 將源代碼組織到不一樣區域中

   推薦使用ReSharper的 File Structure 窗口方便的管理代碼結構(快捷鍵Ctrl + Alt + F)

   通常能夠考慮按如下類型和訪問級別組織類成員:
      #region [Fields]
      #region [Embedded Types]
          … 這裏放public 成員
      #region [Protected]
      #region [Private Methods]
    區域儘可能不要嵌套多層
2.3.單獨聲明每一個變量和特性(每行只放一個)
不建議的書寫方式:

      int a;int b;int c;

            

|命名  

1. 通常原則   

1.1. 使用有意義的名稱
      應該使用對讀代碼的人來講有意義的名稱,避免使用單個字符或太通常性的名稱(特別是在方法內部實現中)
1.2. 根據含意而非類型來命名
      類型信息通常可從其用法和智能感知中看出來,如:使用Customer 而不是CustomerClass對於UI控件的命名能夠加上控件的類型

      如:CustomerNameLabel、 FeedBackButton


1.3. 使用熟悉的名稱(目標領域通用的術語)
1.4. 不要用大小寫來區分名稱
      雖然編譯器區分大小寫,但這樣會影響代碼可讀性,如:XMLStream和XmlStream
1.5. 避免使用過長的名稱
      對象的名稱應當足以描述其目的,若是名稱過長,可能說明該實體企圖實現的功能太多,此時應當考慮是否須要重構
1.6. 加上元音 – 使用完整的單詞
      不要經過去除元音來縮短名稱,這樣會下降代碼可讀性,有時還可能產生歧義
      如:不要使用 public Msg AppendSig(),而應該使用 public Message AppendSignature()
1.7. 不要出現拼寫錯誤,第一次就拼對!

2.縮略形式    

2.一、除非全稱太長,不然不建議使用縮略形式
     不要使用沒必要要的縮略詞,這樣會影響可讀性,如:使用Attachment 而不是Atta
     若是必須使用縮略詞,請使用廣爲使用和接受的縮略詞,如:Api、App、Zip
2.2. 像普通詞同樣書寫縮略詞
     不要全都大寫,如:XmlString 而不是 XMLString

3.類型和常量

3.1. 建議使用Pascal寫法給命名空間、類、結構、屬性、枚舉、成員常量及方法命名
     每一個單詞首字母都大寫,如:DataManipulator
3.2. 建議使用名詞命名類、結構和屬性
     如: NetworkManager、 public string NetworkType { set; get; }
3.3. 建議用複數形式書寫集合名稱
     集合對象的名稱應該有能反映集合中對象類型的複數形式,
     差:List<Shape> shapeList = new …
     好:List<Shape> shapes = new …
3.4. 建議給抽象基類加上Base 後綴
     如:public abstract class AccountBase
     public class PersonalAccount: AccountBase
3.五、使用單個大寫字母命名泛型參數
    單個泛型參數使用<T>,多個泛型參數將T做爲前綴,如:<TKey, TValue>
3.六、給實現了一種設計模式的類添加模式名稱
    如:public class MessageFactory

4.枚舉

4.1. 用單數形式爲(通常)枚舉命名
    如:public enum EnhancementMode
4.2. 用複數形式爲位域枚舉命名
    如,定義時:    

      public enum Languages
      {
         English = 1,
         ChineseSimplify = 1 << 1,
         ChineseTraditional = 1 << 2,
         …
      }

          使用時(一般與位或操做符一塊兒使用): 

    var languages = Languages.English | Languages.ChineseSimplified | Languages.ChineseTraditional

 5.接口 

5.1. 用大寫字母 I 做爲接口名稱的前綴(IInterface)名
5.2. 使用名詞或形容詞給接口命名
       若接口聲明瞭對象提供的服務,可用名詞命名,如:public interface IMessageListener
       若接口描述了對象的能力,可以使用帶-able或-ible後綴的形容詞命名
       如:public interface IReversible

6.屬性 

6.1. 根據Getter和Setter的值來爲屬性命名
      如對於一個取得過時日期的屬性,將其命名爲ExpirationDate
6.2. 避免冗長的屬性名稱
      如:使用Icollection Customers,而不用Icollection CustomerCollection
6.3.用能體現布爾值特性的名稱給bool類型的屬性命名
      視含意添加Is、Has等前綴,如:bool IsOpen, bool HasCompleted

7.方法 

7.1. 使用Pascal寫法爲方法命名
    每一個單詞首字母都大寫,如:ParseSecondsFrom1970
7.2. 避免冗長的方法名
    操做類名對象的類的成員方法名中不須要再帶有該類對象名
    如:在Book 類中,使用方法Open(),而不是OpenBook()

8.變量和參數 

8.1. 使用Camel寫法爲變量和參數命名
    第一個單詞的首字母小寫,後面每一個單詞的首字母大寫,如:lastName
8.2. 用名詞命名變量和參數
8.3. 給字段(Fields)名稱加上下劃線前綴_,使之與其餘變量區分開來
    如:_lastName
8.4. 用一系列標準名稱爲「一次性」變量和參數命名
   •循環變量int i, j, k
   •object o, obj
   •string s, str
   •Exception e, ex
   •EventArgs args

9.特性 

9.1. 給自定義特性加上Attribute後綴
    定義時:
    public class MyCustomAttribute: Attribute { … }
    使用時:
    [MyCustom]
    public class CertainClass{ … }

10.事件、異常 

10.1. 事件的名稱應當包括對象及動做的描述
     如:public event EventHandler<MessageReceivedEventArgs> MessageReceived
10.2. 事件對應的參數類型應當添加EventArgs後綴,並繼承自EventArgs類
     public class MessageReceivedEventArgs: EventArgs
    {
       //…
     }
10.3. 事件所在的類中應當有一個名爲OnEventName()的方法用於觸發事件
10.4. 給自定義異常類型添加Exception 後綴
     public MyCustomException: Exception
    {
      //…
    }

|文檔 

1.通常原則

1.1. 爲使用接口的人編寫軟件接口文檔
    編寫代碼中公共接口的文檔,可讓別人正確、高效地理解和使用接口
    編寫文檔註釋的首要目的是在服務的提供者(supplier)和客戶(client)之間定義一種編程契約(programming contract)
    與方法相關的文檔應該描述與該方法的調用者有依賴的行爲的諸多方面,而不該試圖描述其實現細節
1.2.爲維護者編寫代碼實現文檔
    老是假定會有徹底不熟悉你的代碼的人要閱讀和理解你的代碼
1.3.保持註釋和代碼同步
    修改代碼時,也要確保更新相關注釋
    代碼和文檔一塊兒構成軟件產品,應對其同等重視
   「When the code and the comments disagree, both are probably wrong.」–Norm Schryer, Bell Labs
1.4.儘早編寫軟件元素的文檔
在實現以前或實現過程當中編寫軟件元素的文檔,勿拖延至軟件將近完成才編寫,由於對代碼太過熟悉或太過厭煩,在項目結尾作的文檔每每缺乏細節
若是在實現以前編寫軟件參考文檔,則可使用該文檔爲受託實現軟件的開發人員定義需求
1.5.考慮全世界的讀者

2.API 

1.儘可能使用C#內建的文檔機制
   使用<summary>等標籤編寫文檔,以後VS可自動生成API文檔
   使用NDoc等工具可自動生成可知足各類需求各類格式的文檔
   經常使用標籤:

   

   Ctrl + Shift + F1

  

3.內部代碼

3.1. 只在須要幫助別人理解代碼的時候才添加內部註釋
3.2. 解釋代碼爲何要這樣作
3.3. 避免使用C風格的註釋塊 /* … */
3.4. 使用單行註釋 // 描述實現細節
      特定變量或表達式的目的
      實現層面的設計決定
      複雜算法的來源資料
      缺陷的修正或變通方法
      之後可能作優化或加工的代碼
     任何所知的問題、侷限或缺陷
3.6. 使用關鍵詞標出待完成工做、未解決問題、錯誤和缺陷修正

 

|編程

1.類型

1.1.使用內建的C#類型別名
    別名更加簡潔易讀,並且具備關鍵字語法着色
    例如:使用int,而不是System.Int32;string,而不是String;object,而不是Object
1.2. 避免使用內聯字面值
   不要:if (size > 45) 
   而是:const int limit = 45; if (size > limit) 
   這樣不但改善了可讀性,並且便於在一個位置修改
1.3.避免沒必要要的值類型裝箱拆箱

 

1.4. 使用標準形式書寫浮點字面值
   差:const double foo = 0.000042;
   好:const double foo = 4.2e-5;
1.5. 將「值」語義定義爲struct(而不是class)
   選用struct表示其實例在堆棧上建立
   注意struct不能繼承和派生(是密封的),並且沒有默認的構造器
1.6. 避免使用代價較高的隱藏式字符串分配
   代碼if (str1.ToUpperCase() == str2.ToUpperCase()) 將產生2次字符串分配
   能夠優化爲if (string.Compare(str1, str2, true))
1.7. 採用更高效的空字符串檢測方法
   差:if (str == 「」)
   好:string.IsNullOrEmpty(str) / string.IsNullOrWhiteSpace(str)
1.8. 只在必要時使用可空值類型
   由於須要裝箱拆箱,因此請確認只在有必要的狀況下使用可空值類型

2.語句和表達式 

2.1. 在複雜表達式中使用括號,而不要依賴操做符優先級
   差:varj = 10 * 2 << 1 + 20;
   好:varj = (10 * (2 << 1)) + 20;
2.2. 不要將布爾值與true 或false 進行相等比較
   if (popup.IsOpen == true) 這沒有必要!應當老是直接判斷布爾值if (popup.IsOpen)
2.3. 使用靜態方法object.Equals() 測試引用類型等同
   由於某個類型的實例方法Equals() 和操做符 == 可能已被重寫
   if (object.Equals(str1, str2))

3.類

 

3.1. 定義小類和小方法
   較小的類和方法易於設計、編碼、測試、編寫文檔、閱讀、理解和使用,較小的類一般擁有更少的方法,能表達更簡單的概念
   儘可能將每一個類的接口(指對外暴露的功能)限制在提供必要功能所需的最少法以內
   儘可能將較大的方法拆分出一些較小的私有方法,即便這些小方法只被調用一次,由於切割開的代碼更容易閱讀和重用
   另外,CLR能夠對小方法進行更好的代碼優化
3.2. 聲明全部成員的訪問級別
   不要假設別人會記得默認的訪問級別,應當所有寫出,並按原則上public, protected, private 的順序排列
3.3. 合理避免使用internal 聲明
   internal成員一般標誌着很差的設計,由於它繞過了訪問限制,並且隱藏了類和成員之間的關係
   你能說出protected internal 的訪問級別嗎?
   僅在如下狀況下考慮使用internal:
   •某工具類僅提供給同程序集中的其餘類使用,不須要暴露給程序集的使用者
   •須要防止派生類得到特定父類方法,但又同時容許特定輔助類等訪問這些方法

4.字段、屬性、方法

4.1. 聲明全部字段爲private,使用屬性提供訪問
將實現細節看做私有信息,下降在實現改動時對依賴類的影響,而且讓數據持續有效
4.2. 只爲簡單、低成本、順序無關的訪問使用屬性
即不該有反作用、開銷大、與訪問順序有關
4.3. 方法中避免傳遞過多參數
若是某個方法須要大量參數,就應該考慮是否要從新設計了,參數太多標誌着方法作了太多的事,或者數個相關參數能夠被抽象到另外一個類裏

5.異常

5.1. 使用返回碼(return code)報告預期的狀態改變
5.2. 使用異常強迫得到編程契約
    •前置條件異常(pre-conditional exception):參數無效或調用方法時相關對象處於無效狀態
    •後置條件異常(post-conditional exception):方法產出結果無效或在返回前相關對象處於無效狀態
    與方法調用者有關時才拋出異常,若是是內部邏輯錯誤,則應使用斷言(Debug.Assert)
5.3. 不要靜默地接受或忽略非預期的運行時錯誤
    不要使用空的catch 塊消除異常
5.4. 只捕獲能處理的異常
    合理避免使用通用的catch (Exception ex) { … }
5.5.使用try…finally… 代碼塊或using 語句管理資源
5.6.儘量拋出最具體的異常
    NullReferenceException, ArgumentOutOfRangeException, FileNotFoundException
5.7.按照異常類型的特殊性級別排列catch 塊
    先捕獲更特殊的異常
5.8.不要在finally 塊中拋出異常
    在finally 塊中拋出異常會致使同時激活多個異常,很難處理

6.效率 

6.1.使用懶惰求值和懶惰初始化(lazy evaluation & lazy initialization)
   原則:
   在須要結果以前,不要進行復雜的計算
   老是在最靠近嵌套邊緣的地方執行計算
   若是可能,緩存結果
   在須要對象以前,不構造對象(可能須要加鎖來防止併發初始化)
6.2.重用對象以免再次分配
   緩存並重用頻繁建立且生命週期有限的對象
   使用訪問器而不是構造器來從新初始化對象
   使用工廠模式來封裝緩存和重用對象機制
6.3.避免建立沒必要要的對象
   特別是新對象生命週期較短,或者構造後歷來不引用時,尤爲須要注意
   在知道本身須要什麼以前,避免建立對象

 

6.4.讓CRL處理垃圾回收
   通常而言,避免調用GC.Collect() 方法,讓垃圾回收器自行動做
   在多數狀況下,垃圾回收器的優化引擎比你更善於判斷執行回收的最佳時機
6.5.到最後再進行優化
   優化的第一原則:
   不要優化
   優化的第二原則(只針對專家):
   仍是不要優化
   在確認須要優化以前,不要花時間作優化
   確實要作優化時,應採用80-20原則:平均而言,系統中20%的代碼使用80%的資源,確保從這20%的代碼開始優化

|推薦

神器Resharper很是好用,推薦安裝,讓你愛上Coding!

相關文章
相關標籤/搜索