1 目錄 1 目錄 2 前言... 3 2.1 編寫目的... 3 2.2 適用範圍... 4 3 命名規範... 4 3.1 命名約定... 4 3.1.1 PascalCasing. 4 3.1.2 camelCasing. 4 3.1.3 UPPER_CAPS. 4 3.1.4 私有變數的命名 ...
1 目錄
2 前言... 3
2.1 編寫目的... 3
2.2 適用範圍... 4
3 命名規範... 4
3.1 命名約定... 4
3.1.1 PascalCasing. 4
3.1.2 camelCasing. 4
3.1.3 UPPER_CAPS. 4
3.1.4 私有變數的命名... 4
3.1.5 首字母縮寫詞的大小寫... 4
3.1.6 複合詞的大小寫... 5
3.2 命名選擇... 5
3.2.1 名字一定要能夠表達出標識符的含意... 5
3.2.2 命名要與使用者的期望相匹配... 6
3.2.3 不要賣弄風騷... 6
3.3 命名最佳實踐... 6
3.3.1 命名空間... 6
3.3.2 要讓介面的名字以字母I開頭... 6
3.3.3 派生類的末尾使用基類名稱... 7
3.3.4 泛型類型參數的命名... 7
3.3.5 枚舉類型的命名... 7
3.3.6 屬性的命名... 7
3.3.7 事件的命名... 7
3.3.8 欄位的命名... 8
4 註釋... 8
4.1 註釋約定... 8
4.1.1 類註釋約定... 8
4.1.2 類屬性註釋約定... 8
4.1.3 方法註釋約定... 8
4.1.4 代碼間註釋約定... 9
4.1.5 強制註釋的約定... 9
4.2 不需要的註釋... 9
4.2.1 不要為了註釋而註釋... 10
4.2.2 不要用註釋來粉飾糟糕的代碼... 10
4.2.3 日誌式註釋... 10
4.2.4 個人簽名... 11
4.2.5 位置標識... 11
4.2.6 註釋掉的代碼... 11
4.3 需要的註釋... 11
4.3.1 記錄你對代碼有價值的見解... 11
4.3.2 為代碼中的不足寫註釋... 11
4.3.3 對意料之中的疑問添加註釋... 12
4.3.4 公佈可能的陷阱... 12
4.3.5 對於代碼塊總結性地註釋... 12
4.4 如何寫好註釋... 13
4.4.1 避免使用不明確的代詞... 13
4.4.2 精確描述方法的行為... 13
4.4.3 用輸入輸出例子來說明特殊的情況... 13
4.4.4 更新代碼時記得更新註釋... 14
4.4.5 只有能讓別人讀懂的註釋才是合格的註釋... 14
4.5 region的使用... 14
4.6 c#中巧用#if debug進行調試... 14
4.7 c#特性代碼簡潔... 14
2 前言
2.1 編寫目的
為了保證大家編寫出的程式都使用統一的風格,以方便閱讀和後期維護。
編碼規範對於程式員而言尤為重要,有以下幾個原因:
- 一個軟體的生命周期中,80%的花費在於維護。
- 幾乎沒有任何一個軟體,在其整個生命周期中,均由最初的開發人員來維護。
- 編碼規範可以改善軟體的可讀性,可以讓程式員儘快而徹底地理解新的代碼 。
- 編碼規範可以保證代碼閱讀者在閱讀代碼時產生儘可能少的歧義。
- 編碼規範可以使我們的代碼統一、美觀,讓客戶可以一眼看出我們是專業的團隊。
編碼規範的核心出發點是
- 讓其他人能夠快速,準確的知道代碼的作用並且確保不會出現歧義(其他人也包括一段時間後的自己)
- 讓我們的代碼看著更加清晰、美觀、專業、統一。
2.2 適用範圍
本公司內部開發人員
3 命名規範
任何標識符的名字都應該可以簡單、清楚、正確的表示出該標識符的作用。
同時我們要將儘可能多的信息裝入到標識符的名字當中去,以便讀代碼的人可以快速的讀懂代碼。
3.1 命名約定
我們在命名標識符時(包括參數,常量,變數),應使用單詞的首字母大小寫來區分一個標識符中的多個單詞,如UserName.
3.1.1 PascalCasing
PascalCasing包含一到多個單詞,每一個單詞第一個字母大寫,其餘字母均小寫。例如:HelloWorld、SetName等。
除了參數、變數、常量外,所有命名空間名稱、類、函數、介面、屬性、事件、枚舉等名稱的命名,使用 Pascal 風格。
3.1.2 camelCasing
camelCasing包含一到多個單詞,第一個單詞首字母小寫,其餘單詞首字母大寫。例如:name、productId等。
參數與變數的命名使用camelCasing.
3.1.3 UPPER_CAPS
UPPER_CAPS包含一到多個單詞,每個單詞的所有字母都大寫,單詞與單詞之間用”_”連接,該風格目前在c#中只用於const常量。
如:public const string DEFAULT_PAGE = "default.aspx";
3.1.4 私有變數的命名
Private 的私有變數使用下劃線”_”+camelCasing的大小寫規則,以便快速確認該變數的作用域。
如: private int _userId;
3.1.5 首字母縮寫詞的大小寫
首字母縮寫詞是由一個短語的首字母組成的,如Xml(ExtensibleMarkuLaguage),IO(Input and Output)。它和單詞縮寫是有區別的,單詞縮寫僅僅是把一個單詞的長度變短。
- 把兩個字母的首字母縮寫詞全部大寫,除非它是camelCasing的第一個單詞。
using System.IO;
public void StartIO(Stream ioStream)
- 由三個或以上的字母組成的首字母縮寫詞,只有第一個字母大寫,如Xml,Html.除非首字母是camelCasing標識符的第一個單詞。
using System.Xml;
public void ProcessXmlNode(XmlNode xmlNode)
3.1.6 複合詞的大小寫
不要把複合詞中的首字母大寫。複合詞要當成一個單詞來處理。
如endpoint, callback,metadata,namespace等都是正確的寫法
3.2 命名選擇
3.2.1 名字一定要能夠表達出標識符的含意
標識符名字必須要表達出該標識符的意義,絕對不可以使用無意義的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];
}
}
在給標識符命名時,一定不能產生歧義,代碼中的很多錯誤都是由於命名時的歧義造成的。例如:
public const int CART_TOO_BIG_LIMIT = 10;
if (ShoppingCart.Count() >= CART_TOO_BIG_LIMIT)
{
LogError("Too many items in cart.");
}
3.2.2 命名要與使用者的期望相匹配
有些名字之所以會讓人誤解是因為帶嗎閱讀者對它們有先入為主的印象,就算你本意並非如此。這種情況下,你最好是選用一個與使用者期望所匹配的名字。
如很多程式員都習慣了把Get開始的方法當作“輕量級訪問器“,他只是簡單的返回成員變數。
大家看到以下的代碼
class BinaryTree
{
public int GetNodesCount()
會以為只是返回內部private int _nodesCount; 私有變數的訪問器。
但如果實際你的代碼可能是一個非常耗時的代碼,內部實現是廣度優先遍歷所有的樹節點,還要去資料庫查找父節點和子節點的關係,然後累加。
那麼這麼一個耗時的方法可能由於你的命名,導致了被調用者反覆多次的調用,導致整個系統性能下降。
如果你將命名改為ComputeNodesCount那麼調用者就會知道這是個耗時的操作,需要緩存調用結果並減少調用。
3.2.3 不要賣弄風騷
使用最常用,眾所周知的單詞。不要在代碼命名時賣弄你的學識,要讓你的代碼快速準確的表達出你的想法才是真正的牛人。
如public static string ConvertXml2Html (string sourcePath)
有些人在看到這個方法的時候怎麼想也想不明白這個2是做什麼用的,是把一個Xml文件變成兩個Html?
熟悉英語文化的人可能知道這是To的俚語表達。如果你不能保證所有閱讀你代碼的人都知道2是To的縮寫。那麼請使用ConvertXmlToHtml命名。
3.3 命名最佳實踐
3.3.1 命名空間
- 要使用PascalCasing,並用點號來分隔名字空間中的各個部分。
如Microsof.Office.PowerPoint
3.3.2 要讓介面的名字以字母I開頭
如IComponet,IDisposable 大家一看就知道是介面。
同時要確保如果一個類是一個介面的標準實現,那麼這個類和介面應該只差一個”I“首碼。
3.3.3 派生類的末尾使用基類名稱
例如,從 Stream 繼承的 Framework 類型以 Stream 結尾,從 Exception 繼承的類型以 Exception 結尾。
3.3.4 泛型類型參數的命名
- 使用描述性的名字來命名泛型類型參數,並且在前面加上T首碼
如下麵都是很好的命名
public delegate TOutput Converter<TInput, TOutput>(TInput from);
- 如果只有一個類型參數,可以只用一個字母T來表示泛型
public class Nullable<T>
public class List<T>
- 如果泛型參數有約束,那麼需要在泛型類型參數名中需要顯示出該約束
public interface ISessionChannel<TSession> where TSession:ISession
3.3.5 枚舉類型的命名
- 要用單數名詞而不是複數命名枚舉類型,如要用ConsoleColor而不是ConsoleColors
public enum ConsoleColor
{
Red,
Yellow,
Blue
}
- 不要給枚舉類型加”Enum“、”Flag”等尾碼。
ColorEnum,ColorFlag都不好,因為本身就是枚舉,再加上就是沒有意義的重覆 。
3.3.6 屬性的命名
- 要用名詞、名詞短語或形容詞來命名屬性
- 要用描述集合中具體內容的短語的複數形式來命名屬性集合,而不要用短語的單數形式加”List“、”Array”或”Collection“尾碼
class BinaryTree
{
//Good Naming
public NodeCollection Nodes { get; set; }
//Bad Naming
public NodeCollection NodesCollection { get; set; }
- 要用肯定性的短語命名布爾屬性。最好在前面選擇性的加入”Is“、”Can“、”Has“等首碼。
CanSeek比CantSeek和Seekable都更準確和容易理解。
3.3.7 事件的命名
- 要用動詞或動詞短語命名事件
如: Clicked、Painting、DroppedDown 等等
- 要用現在進行時(ing)和過去式(ed)來賦予事件發生之前和之後的概念。而不是使用Before和After.
如視窗關閉前發生的close事件應該命名為Closing,而在視窗關閉之後發生的應該命名為Closed.
3.3.8 欄位的命名
- 禁止使用實例的公有欄位和受保護欄位,請使用屬性代替。
Tips:在VisualStudio中輸入”prop”可快速創建外部可修改的屬性,輸入”propg”可快速創建不允許外部修改的屬性。如:
//prop
public int NodesCount { get; private set; }
//propg
public List<BinaryNode> Nodes { get; set; }
- 一般只使用靜態欄位
- 要使用名詞、名詞短語或形容詞命名欄位
- 不要給欄位加首碼如“g_”、”s_”來表示靜態欄位。因為欄位和屬性是非常相似的,所以要遵循相同的命名規範。
4 註釋
註釋毫無疑問是讓別人以最快速度瞭解你代碼的最快途徑,但寫註釋的目的絕不僅僅是”解釋代碼做了什麼“,更重要的儘量幫助代碼閱讀者對代碼瞭解的和作者一樣多。
當你寫代碼時,你腦海裡會有很多有價值的信息,但當其他人讀你代碼時,這些信息已經丟失,他們所見到的只是眼前代碼。
4.1 註釋約定
如果IDE提供註釋格式,則儘量使用IDE提供的格式,否則使用”//”來註釋。類、屬性和方法的註釋在Visual Studio中都使用輸入”///”自動生成的格式。
4.1.1 類註釋約定
///<summary>
///角色信息
///</summary>
public class CarRoleModel
4.1.2 類屬性註釋約定
///<summary>
///角色id
///</summary>
publicstring RoleId { get; set; }
4.1.3 方法註釋約定
///<summary>
///用戶登錄
///</summary>
///<param name="userName">用戶名</param>
///<param name="password">密碼</param>
///<returns>返回用戶登錄結果</returns>
public ActionResult SubmitLogin(string userName, string password)
4.1.4 代碼間註釋約定
- 單行註釋,註釋行數<3行時使用
//單行註釋
- 多行註釋,2<註釋行數<=10時使用
/*多行註釋1
多行註釋2
多行註釋3*/
- 註釋塊,10<註釋行數時使用,用50個*
/***************************************************
* 代碼塊註釋1
* 代碼塊註釋2
* ......
* 代碼塊註釋10
* 代碼塊註釋11
***************************************************/
4.1.5 強制註釋的約定
- 以下三種情況我們需要在所有的類、類屬性和方法都必須按照上述格式編寫註釋
1) 客戶方對代碼註釋重視程度較高
2) 我們需要提供代碼註釋自動生成的API文檔。
3) 目前編寫的是公共核心模塊
- 如果客戶方沒有對註釋特殊要求,那麼按照下文中討論的只在需要的地方加註釋。不要加無謂的註釋。
4.2 不需要的註釋
閱讀註釋會占用閱讀真實代碼的時間,並且每條註釋都會占用屏幕上的空間。所以我們約定所加的註釋必須是有意義的註釋,否則不要浪費時間和空間。
區別要不要寫註釋的核心思想就是:不要為那些能快速從代碼本身就推斷的事實寫註釋。
4.2.1 不要為了註釋而註釋
有些人可能以前的公司對於註釋要求很高,如“何時寫註釋”章節中的要求。所以很多人為了寫註釋而註釋。
再沒有特殊要求的情況下我們要禁止寫下麵這種沒有意義的註釋。
/// <summary>
/// The class definition for Account
/// </summary>
public class BinaryTree
{
/// <summary>
/// Total counts of the nodes
/// </summary>
public int NodesCount { get; private set; }
/// <summary>
/// All the nodes in the tree
/// </summary>
public List<BinaryNode> Nodes { get; set; }
/// <summary>
/// Insert a node to the tree
/// </summary>
/// <param name="node">the node you want insert into the tree</param>
public void InsertNode(BinaryNode node)
4.2.2 不要用註釋來粉飾糟糕的代碼
寫註釋常見的動機之一就是試圖來使糟糕的代碼能讓別人看懂。對於這種“拐杖式註釋”,我們不需要,我們要做的是把代碼改的能夠更具有”自我說明性“。
記住:“好代碼>壞代碼+好註釋”
如下麵這段函數的註釋
//Enforce limits on the reply as stated in the request
//such as the number of items returned, or total byte size,etc.
public void CleanReply(Request request,Reply reply)
既然知道這個函數名會讓人很難讀懂,那麼為什麼不直接改好名字呢?這樣所有調用這個函數的地方都能很快速知道這個函數的作用,不用再跟進來看函數的作用。
public void EnforceLimitsFromRequestOnReply(Request request,Reply reply)
4.2.3 日誌式註釋
有人喜歡在每次編輯代碼時,都在模塊開始處加一條註釋。這類註釋就像是一種記錄每次修改的日誌。在很久以前這種記錄對於維護還有意義。但是對於現在的源碼控制來說,這些記錄完全是冗餘的,需要完全廢除。
/***************************************************
* July-29-2014:Fix Bug-12345: Add new method to calculate nodes count
* July-20-2014:Fix Bug-11111: Add Insert new node method
* ......
* July-20-2014:Task-00001: Create BinaryTree class
***************************************************/
4.2.4 個人簽名
//Added By XXXX
有人認為這種註釋有助於不瞭解這段代碼含意的人和他討論。事實上確是這條註釋放在那一年復一年,後來的代碼和原作者寫的源碼越來越不一樣,和XXXX也越來越沒關係。
重申一下,TFS里都能看到這類信息,不要加在代碼里。
4.2.5 位置標識
//AddNodePlace1
//AddNodePlace2
有人喜歡在代碼註釋裡加入位置標識以方便他查找代碼的位置。
現在的IDE都集成了這些功能,如VS中可以使用Bookmark(Ctrl+b,t)。
不要將這類註釋加到代碼中。
4.2.6 註釋掉的代碼
直接把代碼註釋掉是非常令人討厭的做法。
其他人不敢刪掉這些代碼。他們會想代碼依然在這一定是有原因的,而且這段代碼很重要,不能刪除。而且每個閱讀代碼的人都會去看一下這些被註釋掉的代碼中是否有他們需要註意的信息。
這些註釋掉的代碼會堆積在一起,散髮著腐爛的惡臭。
4.3 需要的註釋
4.3.1 記錄你對代碼有價值的見解
你應該在代碼中加入你對代碼這段代碼有價值的見解註釋。
如: //出乎意料的是,對於這些數據用二叉樹比哈希表要快40%
//哈希運算的代價比左右比要大的多
這段註釋會告訴讀者一些重要的性能信息,防止他們做無謂的優化。
4.3.2 為代碼中的不足寫註釋
代碼始終在演進,並且在代碼中肯定會有不足。
要把這些不足記錄下來以便後來人完善。
如當代碼需要改進時:
//TODO:嘗試優化演算法
如當代碼沒有完成時:
//TODO:處理JPG以外的圖片格式
你應該隨時把代碼將來該如何改動的想法用註釋的方式記錄下來。這種註釋給讀者帶來對代碼質量和當前狀態的寶貴見解,甚至會給他們指出如何改進代碼的方向。
4.3.3 對意料之中的疑問添加註釋
當別人讀你的代碼的時候,有些部分可能讓他們有這樣的疑問:“為什麼要這樣寫?”你的工作就是要給這些部分加上註釋。
如:
// 因為Connection的創建很耗費資源和時間,而且需要多線程訪問,
// 所以使用多線程單例模式
public static Connection Instance
{
get
{
if(_instance==null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new Connection();
}
}
}
return _instance;
}
}
4.3.4 公佈可能的陷阱
當為一個函數或者類寫註釋時,可以這樣的問自己:”這段代碼有什麼出人意料的地方嗎?會不會被無用?“。基本上說就是你需要未雨綢繆,預料到別人使用你代碼時可能遇到的問題。如:
//XXX: 因為調用外部郵件伺服器發送郵件,所以耗時較長,請使用非同步方法調用以防止UI卡死。
public void SendEmail(string to, string subject, string body)
4.3.5 對於代碼塊總結性地註釋
對於代碼塊的總結性註釋可以使讀者在深入細節之前就能得到該代碼塊的主旨,甚至有時候都可以直接跳過該代碼塊,從而可以快速準確的把握代碼。
如讀者看到://下麵代碼使用了二分查找演算法來快速的根據用戶Id找到相應用戶
那麼他就可以快速理解下麵代碼的邏輯,否則自己看二分查找還是要用些時間的。
4.4 如何寫好註釋
4.4.1 避免使用不明確的代詞
有些情況下,”it”, “this”等代詞指代很容易產生歧義,最安全的方式是不要使用將所有可能產生歧義的代詞替換成實際指代的詞。
如://Insert the data into the cache,but check if it's too big first.
”it”是指”data“還是”cache“? 在讀完剩下的代碼前誰也不知道指代的是誰。那還要註釋做什麼?替換成要指代的詞後讀者就可以直接了當的知道接下來的代碼要做什麼了。
//Insert the data into the cache,but check if the data is too big first.
4.4.2 精確描述方法的行為
註釋一定要精確的描述方法的行為。避免由於註釋不准確而造成的誤調用。
如你寫了一個方法統計文件中的行數
//Return the number of lines in this file
public long CountLinesInFile(string fileName)
上面的註釋不是很精確,因為有很多定義行的方式,下麵幾種情況這個方法的返回值無法根據註釋快速的判斷出來。
- “”(空文件)——0或1行?
- “hello”——0或1行?
- “hello\n”——1或2行?
- “hello\n\r world\r”——2、3或4行?
假設該方法的實現是統計換行符的(\n)的個數,下麵的註釋就要比原來的註釋更好些。
//Count how many newline symbols('\n') are this file
這條註釋包含更多的信息。讀者可以知道如果沒有換行符,這個函數會返回0。讀者還知道回車符(\r)會被忽略。
4.4.3 用輸入輸出例子來說明特殊的情況
對於註釋來講,一個精挑細選的例子比千言萬語還要有效,而且更加直白有效,閱讀速度更快。
如: /// <summary>
/// Remove the suffix/prefix of charsToRemove from the input source
/// </summary>
public string StripPrefixAndSuffix(string source, string charsToRemove)
這條註釋不是很精確,因為它不能回答下麵的問題
- 是只有按charsToRemove中順序的字元才會被移除,還是無序的charsToRemove也會被移除?
- 如果在開頭和結尾有多個charsToRemove會怎樣?
而一個好例子就可以簡單直白的回答這些問題:
/// <summary>
/// Example: StripPrefixAndSuffix("abbayabbazbaba","ab") returns "yababz"
/// </summary>
4.4.4 更新代碼時記得更新註釋
再好的註釋也會隨著內容的更改而變得越來越沒有意義,有時候甚至會對讀者造成誤導,產生不必要的bug。所以在更改代碼後,記得要更新所更改代碼的註釋,使其表達最新代碼的含意。
4.4.5 只有能讓別人讀懂的註釋才是合格的註釋
當自己不確定自己的註釋是否合格時,請周圍的同事讀下你的註釋,看他讀完註釋後說出的想法是否是你想要表達的,是否有信息遺漏和誤解等。
4.5 region的使用
如下圖所示:
4.6 c#中巧用#if debug進行調試
#if DEBUG
UserID = "[email protected]";
Password = "123456";
#endif
當調試代碼的時候加上適當的判斷,而不影響Release的代碼。
通過#if預編譯指令對DEBUG進行判斷,如下:
#if DEBUG
// 調試用代碼
……#endif
調試用代碼在Debug狀態下是要執行的,而在Release狀態下根本執行,在生成的時候也直接忽略。
4.7 c#特性代碼簡潔
1.var car_Permission = _car_PermissionBusiness.GetModel(car_User.RoleID) ?? null;
2.var car_Permission = _car_PermissionBusiness.GetModel(car_User.RoleID) == null ? new Car_Permission() : _car_PermissionBusiness.GetModel(car_User.RoleID);
3.var userName= _car_UserBusiness.GetCarUser(car_User.RoleID)?.UserName ?? null;
總結:第一種跟第二種獲取的數據方法是一樣的