1. 程式人生 > >這套C#編碼規範寫不錯

這套C#編碼規範寫不錯

命名約定

我們在命名識別符號時(包括引數,常量,變數),應使用單詞的首字母大小寫來區分一個識別符號中的多個單詞,如UserName.

  • PascalCasing

PascalCasing包含一到多個單詞,每一個單詞第一個字母大寫,其餘字母均小寫。例如:HelloWorld、SetName等。

除了引數、變數、常量外,所有名稱空間名稱、類、函式、介面、屬性、事件、列舉等名稱的命名,使用 Pascal 風格。

  • camelCasing

camelCasing包含一到多個單詞,第一個單詞首字母小寫,其餘單詞首字母大寫。例如:name、productId等。

引數與變數的命名使用camelCasing.

  • SCREAMING_CAPS

SCREAMING_CAPS包含一到多個單詞,每個單詞的所有字母都大寫,單詞與單詞之間用"_"連線,該風格目前在c#中只用於const常量。

如:public const string DEFAULT_PAGE = "default.aspx";

  • 私有變數的命名

Private 的私有變數使用下劃線"_"+camelCasing的大小寫規則,以便快速確認該變數的作用域。

如: private int _userId;

  • 首字母縮寫詞的大小寫

首字母縮寫詞是由一個短語的首字母組成的,如Xml(ExtensibleMarkuLaguage),IO(Input and Output)。它和單詞縮寫是有區別的,單詞縮寫僅僅是把一個單詞的長度變短。

  1. 把兩個字母的首字母縮寫詞全部大寫,除非它是camelCasing的第一個單詞。

    using System.IO;

    public void StartIO(Stream ioStream)

  2. 由三個或以上的字母組成的首字母縮寫詞,只有第一個字母大寫,如Xml,Html.除非首字母是camelCasing識別符號的第一個單詞。

    using System.Xml;

    public void ProcessXmlNode(XmlNode xmlNode)

  • 複合詞的大小寫

不要把複合詞中的首字母大寫。複合詞要當成一個單詞來處理。

如endpoint, callback,metadata,namespace等都是正確的寫法

  • 在帶單位的值的變數後加上"_camelCasing單位"

將單位加入識別符號命名中,可以使使用者快速準確的知道傳人資料的單位,減少錯誤的發生。

public void CreateCache(int cacheSize)

傳入的資料是bytes, KB, MB 還是GB?

改成public void CreateCache(int cacheSize_mb)

一目瞭然,並且會減少呼叫者傳入錯誤資料的可能。

其他一些沒有單位的函式引數以及帶單位的版本。

  • 不要使用匈牙利命名法

匈牙利命名法是指用小寫形式的資料型別縮寫來作為變數名的字首。如:strName,intCount。

這種命名法在C和C++時代很流行,可以幫助程式設計師記住自己的型別。

但在C#中需要禁用,除非你有足夠的理由,因為:

  1. C#都是強型別的,現在的IDE(如Visual Studio)可以自動的檢測出當前變數的型別以及型別錯誤

  2. 開發初期經常需要修改變數的型別,使用匈牙利命名法維護很困難。
  • 使用英語語序命名識別符號

人在閱讀程式碼時,能更快的理解符合其閱讀習慣的命名。

如VerticalAlignment比AlignmentVertical能讓人更快的知道該變數的含意。

簡單的講,看到一個識別符號一定要可以見名知意。

  • 名字一定要能夠表達出識別符號的含意

識別符號名字必須要表達出該識別符號的意義,絕對不可以使用無意義的v1,v2…vn之類的命名。

public static void CloneChars(char[] cl1, char[] cl2)

{

for (var i = 0; i < cl1.Count(); i++)

{

cl2[i] = cl1[i];

}

}

程式碼的呼叫者不看這函式是無法知道cl1還是cl2是要拷貝的char陣列,他必須進到這個函式去看完整個邏輯才可以呼叫。而且在看的過程中cl2[i] = cl1[i]; 也需要他花幾秒鐘來思考是做什麼的。

如果改成有意義的名字: source 和target那麼這個方法呼叫者一看名字就知道使用方法了。

public static void CloneChars(char[] source, char[] target)

{

for (var i = 0; i < source.Count(); i++)

{

target[i] = source[i];

}

}

  • 選擇意義單一明確的名字

在命名時要使用專業的單詞,避免使用"空洞"的單詞

如: class BinaryTree

{

public int Size()

看到這行程式碼你想到Size會返回什麼,樹的高度,節點數還是樹在記憶體中的空間?

我們可以使用更單一明確的詞來告訴讀者這個方法的具體含義,如Height,NodesNum,Memory_Bytes

  • 使用不會產生歧義的名字

在給識別符號命名時,一定不能產生歧義,程式碼中的很多錯誤都是由於命名時的歧義造成的。例如:

public const int CART_TOO_BIG_LIMIT = 10;

if (ShoppingCart.Count() >= CART_TOO_BIG_LIMIT)

{

LogError("Too many items in cart.");

}

這段程式碼有個很經典的"大小差一缺陷"。在判斷購物車物品上限時,我是應該使用 ">"還是應該使用">=",我是無法從程式碼中判斷出來的,所以這個地方很容易出現bug.如果我們換成MAX_ITEMS_IN_CART, 那我馬上就可以判定出這裡要使用">"。

  • 命名要與使用者的期望相匹配

有些名字之所以會讓人誤解是因為帶嗎閱讀者對它們有先入為主的印象,就算你本意並非如此。這種情況下,你最好是選用一個與使用者期望所匹配的名字。

如很多程式設計師都習慣了把Get開始的方法當作"輕量級訪問器",他只是簡單的返回成員變數。

大家看到以下的程式碼

class BinaryTree

{

public int GetNodesCount()

會以為只是返回內部private int _nodesCount; 私有變數的訪問器。

但如果實際你的程式碼可能是一個非常耗時的程式碼,內部實現是廣度優先遍歷所有的樹節點,還要去資料庫查詢父節點和子節點的關係,然後累加。

那麼這麼一個耗時的方法可能由於你的命名,導致了被呼叫者反覆多次的呼叫,導致整個系統性能下降。

如果你將命名改為ComputeNodesCount那麼呼叫者就會知道這是個耗時的操作,需要快取呼叫結果並減少呼叫。

  • 為名字附加更多的資訊

一個變數名就像一個小注釋,儘管空間不大,但不管你在命中擠進任何額外的資訊,每次有人看到命名時都會看到這些資訊。

例子:當你從網頁接收了請求的表單,裡面可能還有不安全的程式碼,如注入語句等,這時你在命名時需要體現該資料不安全,可以使用unsafeFormData,當呼叫完安全檢查方法後可以將其改為 safeFormData = HandleUnsafeData(unsafeFormData).這樣程式碼閱讀者就知道可以放心的使用該變量了。

下表給出了更多需要給名字附加額外資訊的例子

  • 不要賣弄風騷

使用最常用,眾所周知的單詞。不要在程式碼命名時賣弄你的學識,要讓你的程式碼快速準確的表達出你的想法才是真正的牛人。

public static string ConvertXml2Html (string sourcePath)

有些人在看到這個方法的時候怎麼想也想不明白這個2是做什麼用的,是把一個Xml檔案變成兩個Html?

熟悉英語文化的人可能知道這是To的俚語表達。如果你不能保證所有閱讀你程式碼的人都知道2是To的縮寫。那麼請使用ConvertXmlToHtml命名。

特定場景下的命名最佳實踐

  • 名稱空間

  1. 要使用PascalCasing,並用點號來分隔名字空間中的各個部分。

    如Microsof.Office.PowerPoint

  2. 要用公司名作為名稱空間的字首,這樣就可以避免與另外一家公司使用相同的名字。
  3. 要用穩定的,與版本無關的產品名稱作為名稱空間的第二層
  4. 不要使用公司的組織架構來決定名稱空間的層次結構,因為內部組織結構經常改變。
  5. 不要用相同的名字來命名名稱空間和該空間內的型別。

    例如,不要先將名稱空間命名為Debug,然後又在該空間中提供Debug類。大部分編譯器包括VS要求使用者在這樣的型別前加上完整的限定符。

  • 要讓介面的名字以字母I開頭

如IComponet,IDisposable 大家一看就知道是介面。

同時要確保如果一個類是一個介面的標準實現,那麼這個類和介面應該只差一個"I"字首。

  • 派生類的末尾使用基類名稱

例如,從 Stream 繼承的 Framework 型別以 Stream 結尾,從 Exception 繼承的型別以 Exception 結尾。

  • 泛型型別引數的命名

  1. 使用描述性的名字來命名泛型型別引數,並且在前面加上T字首

    如下面都是很好的命名

    public delegate TOutput Converter<TInput, TOutput>(TInput from);

  2. 如果只有一個型別引數,可以只用一個字母T來表示泛型

    public class Nullable<T>

    public class List<T>

  3. 如果泛型引數有約束,那麼需要在泛型型別引數名中需要顯示出該約束

    public interface ISessionChannel<TSession> where TSession:ISession

  • 列舉型別的命名

  1. 要用單數名詞而不是複數命名列舉型別,如要用ConsoleColor而不是ConsoleColors

    public enum ConsoleColor

    {

    Red,

    Yellow,

    Blue

    }

  2. 不要給列舉型別加"Enum"、"Flag"等字尾。

    ColorEnum,ColorFlag都不好,因為本身就是列舉,再加上就是沒有意義的重複

  • 要用動詞和動詞短語命名方法

  • 屬性的命名

  1. 要用名詞、名詞短語或形容詞來命名屬性
  2. 要用描述集合中具體內容的短語的複數形式來命名屬性集合,而不要用短語的單數形式加"List"、"Array"或"Collection"字尾

    class BinaryTree

    {

    //Good Naming

    public NodeCollection Nodes { get; set; }

    //Bad Naming

    public NodeCollection NodesCollection { get; set; }

  3. 要用肯定性的短語命名布林屬性。最好在前面選擇性的加入"Is"、"Can"、"Has"等字首。

    CanSeek比CantSeek和Seekable都更準確和容易理解。

  • 事件的命名

  1. 要用動詞或動詞短語命名事件

    如: Clicked、Painting、DroppedDown 等等

  2. 要用現在進行時(ing)和過去式(ed)來賦予事件發生之前和之後的概念。而不是使用Before和After.

    如視窗關閉前發生的close事件應該命名為Closing,而在視窗關閉之後發生的應該命名為Closed.

  • 欄位的命名

    1. 禁止使用例項的公有欄位和受保護欄位,請使用屬性代替。

    Tips:在VisualStudio中輸入"prop"可快速建立外部可修改的屬性,輸入"propg"可快速建立不允許外部修改的屬性。如:

//propg

public int NodesCount { get; private set; }

//prop

public List<BinaryNode> Nodes { get; set; }

  1. 一般只使用靜態欄位
  2. 要使用名詞、名詞短語或形容詞命名欄位
  3. 不要給欄位加字首如"g_"、"s_"來表示靜態欄位。因為欄位和屬性是非常相似的,所以要遵循相同的命名規範。