c#代碼規範
1.1.Tab
? 要使一個Tab為4個空格長。
1.2.縮進
? 要使一個代碼塊內的代碼都統一縮進一個Tab長度。
1.3.空行
? 建議適當的增加空行,來增加代碼的可讀性。
? 在在類,接口以及彼此之間要有兩行空行:
? 在下列情況之間要有一行空行:
方法之間;
局部變量和它後邊的語句之間;
方法內的功能邏輯部分之間;
1.4.函數長度
每個函數有效代碼(不包括註釋和空行)長度不要超過100行,盡可能控制在50行。
1.5.{”,“}”
? 開括號“{”要放在塊的所有者的下一行,單起一行;
? 閉括號“}”要單獨放在代碼塊的最後一行,單起一行。
1.6.行寬
每行代碼和註釋不要超過70個字符或屏幕的寬度,如超過則應換行,換行後的代碼應該縮進一個Tab。
1.7. 空格
′ 括號和它裏面的字符之間不要出現空格。括號應該和它前邊的關鍵詞留有空格,如:while (true) {};
′ 但是方法名和左括號之間不要有空格。
? 參數之間的逗號後要加一空格。如:method1(int i1, int i2)
? for語句裏的表達式之間要加一空格。如:for (expr1; expr2; expr3)
? 二元操作符和操作數之間要用空格隔開。如:i + c;
? 強制類型轉換時,在類型和變量之間要加一空格。如:(int) i ;
2. 註釋
2.1.註釋的基本約定
? 註釋應該增加代碼的清晰度;
? 保持註釋的簡潔,不是任何代碼都需要註釋的,過多的註釋反而會影響代碼的可讀性。
′ 註釋不要包括其他的特殊字符。
? 建議先寫註釋,後寫代碼,註釋和代碼一起完成
? 如果語句塊(比如循環和條件分枝的代碼塊)代碼太長,嵌套太多,則在其結束“}”要加上註釋,標誌對應的開始語句。如果分支條件邏輯比較復雜,也要加上註釋。
? 在VS2005環境中通過配置工程編譯時輸出XML文檔文件可以檢查註釋的完整情況,如果註釋不完整會報告編譯警告;
2.2.註釋類型
2.2.1.塊註釋
? 主要用來描述文件,類,方法,算法等,放在所描述對象的前邊。具體格式以IDE編輯器輸入“///”自動生成的格式為準,另外再附加我們自定義的格式,如下所列:
/// <Remark>作者,創建日期,修改日期</ Remark >
對類和接口的註釋必須加上上述標記,對方法可以視情況考慮
2.2.2.行註釋
? 主要用在方法內部,對代碼,變量,流程等進行說明。整個註釋占據一行。
2.2.3.尾隨註釋
? 與行註釋功能相似,放在代碼的同行,但是要與代碼之間有足夠的空間,便於分清。例:
int m = 4 ; // 註釋
? 如果一個程序塊內有多個尾隨註釋,每個註釋的縮進要保持一致。
2.3.註釋哪些部分
項目 |
註釋哪些部分 |
參數 |
參數用來做什麽 任何約束或前提條件 |
字段/屬性 |
字段描述 |
類 |
類的目的 已知的問題 類的開發/維護歷史 |
接口 |
目的 它應如何被使用以及如何不被使用 |
局部變量 |
用處/目的 |
成員函數註釋 |
成員函數做什麽以及它為什麽做這個 哪些參數必須傳遞給一個成員函數 成員函數返回什麽 已知的問題 任何由某個成員函數拋出的異常 成員函數是如何改變對象的 包含任何修改代碼的歷史 如何在適當情況下調用成員函數的例子適用的前提條件和後置條件 |
成員函數內部註釋 |
控制結構 代碼做了些什麽以及為什麽這樣做 局部變量 難或復雜的代碼 處理順序 |
事件成員 |
事件用途 事件可能引發的異常 事件的參數含義 |
? 不是所有的成員函數都必須要註釋,以下功能函數可以不加註釋:
界面模塊的功能按鈕事件函數
override的函數
private的函數可以選擇性的加入註釋,如果private函數的功能十分簡單,可以不加入註釋,否則建議加入註釋
? private的字段/屬性可以不加註釋
? 局部變量只需要用”//”說明即可
2.4.程序修改註釋
? 新增代碼行的前後要有註釋行說明,對具體格式不作要求,但必須包含作者,新增時間,新增目的。在新增代碼的最後必須加上結束標誌;
? 刪除代碼行的前後要用註釋行說明,刪除代碼用註釋原有代碼的方法。註釋方法和內容同新增;刪除的代碼行建議用#region XXX #endregion 代碼段折疊,保持代碼文件幹凈整潔
? 修改代碼行建議以刪除代碼行後再新增代碼行的方式進行(針對別人的代碼進行修改時,必須標明,對於自己的代碼進行修改時,酌情進行)。註釋方法和內容同新增;
3. 命名
3.1.命名的基本約定
? 要使用可以準確說明變量/字段/類的完整的英文描述符,如firstName。對一些作用顯而易見的變量可以采用簡單的命名,如在循環裏的遞增(減)變量就可以被命名為 ” i ”。
? 要盡量采用項目所涉及領域的術語。
? 要采用大小寫混合,提高名字的可讀性。為區分一個標識符中的多個單詞,把標識符中的每個單詞的首字母大寫。不采用下劃線作分隔字符的寫法。有兩種適合的書寫方法,適應於不同類型的標識符:
PasalCasing:標識符的第一個單詞的字母大寫;
camelCasing:標識符的第一個單詞的字母小寫。
下表描述了不同類型標識符的大小寫規則:
標識符 |
大小寫 |
示例 |
命名空間 |
Pascal |
namespace Com.Techstar.ProductionCenter |
類型 |
Pascal |
public class DevsList |
接口 |
Pascal |
public interface ITableModel |
方法 |
Pascal |
public void UpdateData() |
屬性 |
Pascal |
Public int Length{…} |
事件 |
Pascal |
public event EventHandler Changed; |
私有字段 |
Camel |
private string fieldName; |
非私有字段 |
Pascal |
public string FieldName; |
枚舉值 |
Pascal |
FileMode{Append} |
參數 |
Camel |
public void UpdateData(string fieldName) |
局部變量 |
Camel |
string fieldName; |
′ 避免使用縮寫,如果一定要使用,就謹慎使用。同時,應該保留一個標準縮寫的列表,並且在使用時保持一致。
? 對常見縮略詞,兩個字母的縮寫要采用統一大小寫的方式(示例:ioStream,getIOStream);多字母縮寫采用首字母大寫,其他字母小寫的方式(示例:getHtmlTag);
′ 避免使用長名字(最好不超過 15 個字母)。
′ 避免使用相似或者僅在大小寫上有區別的名字。
3.2.各種標示符類型的命名約定
3.2.1.程序集命名
? 公司域名(GTA)+ 項目名稱 + 模塊名稱(可選),例如:
中心系統程序集:GTA.ProductionCenter;
中心系統業務邏輯程序集:GTA. ProductionCenter.Business;
3.2.2.命名空間命名
? 采用和程序集命名相同的方式:公司域名(GTA)+ 項目名稱 + 模塊名稱。 另外,一般情況下建議命名空間和目錄結構相同。例如:
中心系統:GTA.ProductionCenter;
中心系統下的用戶控件:GTA.ProductionCenter.UserControl;
中心系統業務邏輯:GTA. ProductionCenter.Business;
中心系統數據訪問:GTA. ProductionCenter.Data;
3.2.3.類和接口命名
? 類的名字要用名詞;
′ 避免使用單詞的縮寫,除非它的縮寫已經廣為人知,如HTTP。
? 接口的名字要以字母I開頭。保證對接口的標準實現名字只相差一個“I”前綴,例如對IComponent的標準實現為Component;
? 泛型類型參數的命名:命名要為T或者以T開頭的描述性名字,例如:
public class List<T>
public class MyClass<TSession>
′ 對同一項目的不同命名空間中的類,命名避免重復。避免引用時的沖突和混淆;
3.2.4.方法命名
? 第一個單詞一般是動詞
? 如果方法返回一個成員變量的值,方法名一般為Get+成員變量名,如若返回的值 是bool變量,一般以Is作為前綴。另外,如果必要,考慮用屬性來替代方法,具 體建議見10.1.2節;
? 如果方法修改一個成員變量的值,方法名一般為:Set + 成員變量名。同上,考慮 用屬性來替代方法;
3.2.5.變量命名
? 按照使用範圍來分,我們代碼中的變量的基本上有以下幾種類型,類的公有變量;類的私有變量(受保護同公有);方法的參數變量;方法內部使用的局部變量。這些變量的命名規則基本相同,見標識符大小寫對照表。區別如下:
i. 類的公有變量按通常的方式命名,無特殊要求;
ii. 類的私有變量采用兩種方式均可:采用camalString,例如:workerName;采用加“m”前綴,例如mWorkerName;
iii. 方法的參數變量采用camalString,例如workerName;
iv. 方法內部的局部變量采用camalString,例如workerName;
′ 不要用_或&作為第一個字母;
? 盡量要使用短而且具有意義的單詞;
? 單字符的變量名一般只用於生命期非常短暫的變量。i,j,k,m,n一般用於integer;c,d,e 一般用於characters;s用於string
? 如果變量是集合,則變量名要用復數。例如表格的行數,命名應為:RowsCount;
? 命名組件要采用匈牙利命名法,所有前綴均應遵循同一個組件名稱縮寫列表
1.1.1.組件名稱縮寫列表
建議縮寫的基本原則是取組件類名各單詞的第一個字母,如果只有一個單詞,則去掉其中的元音,留下輔音。縮寫全部為小寫。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4. 聲明
? 每行要只有一個聲明,如果是聲明i,j,k之類的簡單變量可以放在一行;
? 除了for循環外,聲明要放在塊的最開始部分。for循環中的變量聲明可以放在for語句中。如:for(int i = 0; I < 10; i++) 。
′ 避免塊內部的變量與它外部的變量名相同。
5. 表達式和語句
? 每行建議只有一條語句。
? if-else,if-elseif語句,任何情況下,都應該有“{”,“}”,格式如下:
if (condition)
{
statements;
}
else if (condition)
{
statements;
}
else
{
statements;
}
? for語句格式如下:
for (initialization; condition; update)
{
statements;
}
如果語句為空:
for (initialization; condition; update) ;
? while語句格式如下:
while (condition)
{
statements;
}
如果語句為空:
while (condition);
? do-while語句格式如下:
do
{
statements;
}
while (condition);
? switch語句,每個switch裏都應包含default子語句,格式如下:
switch (condition)
{
case ABC:
statements;
/* falls through */
case DEF:
statements;
break;
case XYZ:
statements;
break;
default:
statements;
break;
}
? try-catch語句格式如下:
try
{
statements;
}
catch (ExceptionClass e)
{
statements;
}
finally
{
statements;
}
6. 類型設計規範
? 要確保每個類型由一組定義明確,相互關聯的成員組成,而不僅僅是一些無關功能的隨 機集合;
? 不要隨意使用public去定義一個類的範圍
6.1.類型和命名空間
? 要用命名空間把類型組織成相關域的層次結構。例如:
界面層:GTA.ProductionCenter;
業務邏輯層:GTA.ProductionCenter.Business;
數據訪問層:GTA.ProductionCenter.Data;
′ 避免過深的命名空間;
′ 避免太多的命名空間;
6.2.抽象類設計
′ 不要在抽象類中定義公有的或內部受保護的構造函數。因為抽象類無法實例化,所以這 種設計會誤導用戶;
? 要為抽象類定義受保護的構造函數或內部構造函數;
6.3.靜態類設計
靜態類是一個只包含靜態成員的類,它提供了一種純面向對象設計和簡單性之間的一個權衡,廣泛用來提供類似於全局變量或一些通用功能。
? 要少用靜態類。靜態類應該僅用作輔助類;
′ 避免把靜態類當作雜物箱。每個靜態類都應該有其明確目的;
? 不要在靜態類中聲明或覆蓋實例成員;
6.4.枚舉設計
? 要用枚舉來加強那些表示值的集合的參數,屬性以及返回值的類型性;
? 要優先使用枚舉而不是靜態常量。例如:
//不好的寫法
public static class Color
{
public static int Red = 0;
public static int Green = 1;
public static int Blue = 2;
}
//好的寫法
public enum Color
{
Red,
Green,
Blue
}
′ 不要把枚舉用於開放的場合,例如操作系統的版本,朋友的名字等;
′ 枚舉最後一個值不要加逗號;
′ 枚舉中不要提供為了今後使用而保留的枚舉值;
7. 成員設計規範
方法,屬性,事件,構造函數以及字段等統稱為成員
7.1.成員設計的一般規範
避免將不需要public的成員也public,盡量使用private或internal
不要畫蛇添足,增加一些無用的成員
7.2.方法的重載規範
′ 避免在重載中隨意的給參數命名。如果兩個重載中的某個參數表示相同的輸入,那麽該參數的名字應該相同。例如:
public class String
{
//好的寫法
public int IndexOf(string value) { ...}
public int IndexOf(string value, int startIndex) { ...}
//不好的寫法
public int IndexOf(string value) { ...}
public int IndexOf(string str, int startIndex) { ...}
}
′ 避免使重載成員的參數順序不一致。在所有的重載中,同名參數應該出現在相同的位置。 例如:
public class EventLog
{
public EventLog();
public EventLog(string logName);
public EventLog(string logName, string machineName);
public EventLog(string logName, string machineName, string source);
}
? 較短的重載應該僅僅調用較長的來實現。另外,重載如果需要擴展性,把最長重載 做成虛函數。例如:
public class String
{
public int IndexOf(string s)
{
//調用
return IndexOf(s, 0);
}
public int IndexOf(string s, int startIndex)
{
//調用
return IndexOf(s, startIndex, s.Length);
}
public virtual int IndexOf(string s, int startIndex, int Count)
{
//實際的代碼
}
}
? 要允許可選參選為null。這樣做是為了避免調用者調用之前需要檢查參數是否null。例 如:
//允許為null時的調用
DrawGeometry(brush, pen, geometry);
//不允許為null時的調用
if (geometry == null) DrawGeometry(brush, pen);
else DrawGeometry(brush, pen, geometry);
7.3.屬性和方法的選擇
? 基本原則是方法表示操作,屬性表示數據。如果其他各方面都一樣,優先使用屬性而不 是方法。
? 要使用屬性,如果該成員表示類型的邏輯attribue
? 如果屬性的值存儲在內存中,而提供屬性的目的僅僅是為了訪問該值,要使用屬性而不 要使用方法
? 如果該操作每次返回的結果不同,那麽要使用方法。例如來自於.net framework的例子:
//好的寫法
Guid.NewGuid();
//不好的寫法
DateTime.Now;
? 如果該操作比訪問字段慢一個或多個數量級,要使用方法。
? 如果該操作有嚴重的副作用,要使用方法。
7.4.屬性的設計規範:
? 如果不應該讓調用方法改變屬性值,要創建只讀屬性;
′ 不要提供只寫屬性;
? 要為所有的屬性提供合理的默認值,這樣可以確保默認值不會導致漏洞或效率低的代 碼;
? 要允許用戶以任何順序來設置屬性的值;
? 避免在屬性的獲取方法拋出異常。
屬性的獲取方法應該是個簡單的操作,不應該有任何的條件。如果一個獲取方法會拋出 異常,按麽可能它更應該設計為方法。
7.5.構造函數的設計規範
? 建議提供簡單的構造函數,最好是默認構造函數。簡單的構造函數增強易用性;
? 考慮擴展性,如果構造函數設計的不自然,建議用靜態的工廠方法來替代構造函數;
? 要把構造函數的參數用作設置主要屬性的便捷方法。如果構造函數參數僅用來設置屬 性,應和屬性名稱相同。僅有大小寫的區別;
? 要在構造函數中做最少的工作。任何其他處理應該推遲到需要的時候;
? 要在類中顯示的聲明公用的默認構造函數,如果這樣的構造函數是必須的。
如果沒有顯示默認構造函數,填加有參數構造函數時往往會破壞已有使用默認構造函數 的代碼;
′ 避免在對象的構造函數內部調用虛成員。這樣在擴展設計的時候會導致難以理解的現 象;
7.6.字段設計規範
′ 不要提供公有的或受保護的字段。代之以屬性來訪問字段;
? 要只用常量字段來表示永遠不會改變的量。否則會導致兼容性問題。下面是正確的例子:
public struct Int32
{
public const int MaxValue = 0x7fffffff;
public const int MinValue = unchecked((int)0x80000000);
}
? 要用公有的靜態只讀字段來定義預定義的對象實例。例如:
public struct Color
{
public static readonly Color Red = new Color(0x0000FF);
}
7.7.參數的設計規範
? 要用類結構層次中最接近基類類型來作為參數的類型,同時要保證該類型能夠提供成員 所需的功能。例如:
要設計一個集合遍歷的方法,那麽參數應該是IEnbumerable為參數,而不應該是IList, 這樣方法具有更強的適應性。
′ 不要使用保留參數。如果將來需要更多的參數,那麽可以增加重載成員。例如:
//不好的寫法
public void Method(string reserved, SomeOption option);
//好的寫法
public void Method(SomeOption option);
//將來填加
public void Method(SomeOption option, string path);
7.7.1.參數設計中枚舉和布爾參數的選擇規範
? 要用枚舉。在代碼閱讀,書寫中,枚舉都比布爾的可讀性好很多。例如:
//使用布爾型,閱讀的時候不會輕易了解參數的含義
FileStream f = File.Open(“1.txt”, true, false);
//使用枚舉型
FileStream f = File.Open(“1.txt”,CasingOptions.CaseSenstive, FileMode.Open);
′ 不要使用布爾參數,除非百分之百肯定絕對不需要兩個以上的值。即使此時,采用枚舉 往往也可以提供更好的可讀性,如上例。
? 考慮在構造函數中,對確實只有兩種狀態值的參數以及用來初始化布爾屬性的參數使用 布爾類型;
10.7.2. 參數驗證的規範:
? 要驗證傳給公有的,受保護的或顯示成員的參數是否合法。如果驗證失敗,應該拋出 System.ArgutmentException或其子類;
? 要拋出System.ArgutmentNullException,如果傳入的null,而該成員不支持null;
10.7.3. 參數傳遞的規範:
′ 避免使用輸出參數或引用參數;
8. 擴展性設計規範
′ 如果沒有恰當理由,不要把類密封起來。這些理由包括:
A)類為靜態類;
B)類的受保護成員保存了高度機密信息;
C)類繼承了許多虛成員,逐個密封的代價太高,不如密封整個類;
D)不要在密封類中聲明保護成員或虛成員,因為無法覆蓋其實現;
? 建議用保護成員用於高級定制。它提供了擴展性,同時也避免了公用接口過於復雜;
′ 不要使用虛成員,除非有合適的理由;
? 建議只有在絕對必須的時候才用虛成員提供擴展性,並使用Template Method模式;
? 要優先使用受保護的虛成員,而不是公有虛成員。公有成員通用調用受保護的虛成員的方式來提供擴展性;
9. 異常處理規範
? 異常的思想是只對錯誤采用異常處理:邏輯和編程錯誤,設置錯誤,被破壞的數據,資源耗盡,等等。通常的法則是系統在正常狀態下以及無重載和硬件失效狀態下,不應產生任何異常。異常處理時可以采用適當的日誌機制來報告異常,包括異常發生的時刻;
′ 一般情況下不要使用異常實現來控制程序流程結構;
′ 使用異常而不要用錯誤代碼來報告錯誤;
? 要通過拋出異常的方式來報告操作失敗。如果成員無法成功地完成它應該做的任務,那麽應該拋出異常;
9.1. 異常類型選擇規範
? 優先考慮使用System命名空間中已有的異常,而不是自己創建新的異常類型;
? 要使用最合理,最具針對性的異常。例如,對參數為空,應拋出System.ArgutmentNullException,而不是System.ArgutmentException
9.2. 異常處理規範
′ 不是百分之百確定的情況,不要吞掉異常;
? 建議捕獲特定類型的異常,如果理解該異常在具體環境當中產生的原因;
′ 不要捕獲不應該捕獲的異常,通常應該允許異常沿著調用棧傳遞;
? 進行清理工作時要用try-finally,避免使用try-catch;
? 要在捕獲並重新拋出異常時使用空的throw語句,這是保持調用棧的最好方法
9.3. 標準異常類的使用:
9.3.1.Exception與SystemException
′ 不要拋出這兩種類型的異常;
′ 避免捕獲這兩種異常,除非是在頂層的異常處理器中;
9.3.2.InvalidOperationException
? 對象處於不正確狀態時拋出;
9.3.3.ArgumentException,ArgumentNullException,ArgumentOutOfRangeException
? 如果傳入的是無效參數,要拋出參數異常,盡可能使用位於繼承層次末尾的類型;
? 要在拋出異常時設置ParaName屬性;
9.3.4.NullRefernceException,IndexOutOfRangeException,AccessViolationException
不要顯示拋出或捕獲;
9.3.5.StackOverflowException:
′ 不要顯示拋出或捕獲;
9.3.6.OutOfMemoryException:
′ 不要顯示拋出或捕獲;
9.4. 自定義異常類型設計規則:
′ 避免太深的繼承層次;
? 要從已有的異常基類繼承;
? 異常類要以“Exception”做為後綴;
? 要使異常可序列化,使其能跨應用程序域和遠程邊界仍能正常使用;
? 要把與安全性有關的信息保存在私有的異常狀態中
9.5. 異常與性能
? 如果在普通場景都會拋出異常,要采用先效驗合法性的方式來避免拋出異常引起的性能 問題;
10. 其他規定
? 為避免頻繁改動代碼,代碼中只寫比較簡單的和不會經常發生變化的SQL,如果SQL 經常發生變化或是比較復雜,存到存儲過程或配置文件中,比如統計用到的SQL;
? 在VS2005開發環境中,采用代碼分析工具來做自動化的代碼分析,以保證代碼質量, 具體的使用建議如下:
A)啟用代碼分析,並設置當風格不符合要求時為錯誤而不是警告;
B)如果不是做代碼審核,此開關應關閉。加上了這個選項的時候編譯很慢;
C)詳設的時候打開開關,檢查詳設是否符合編程規範;
D)所有的選項都應當打開。以下內容需要單獨設置:
編碼 |
名稱 |
大類 |
建議 |
使用等級 |
CA2209 |
程序集應聲明最小安全性 |
用法規則 |
不建議使用 |
警告 |
CA1814 |
與多維數組相比,首選使用交錯的數組 |
性能規則 |
使用,但降低等級 |
警告 |
CA1822 |
將成員標記為 static |
性能規則 |
較繁鎖,且影響代碼質量 |
禁用 |
CA2210 |
程序集應具有有效的強名稱 |
設計規則 |
影響Xcopy部署 |
禁用 |
CA1302 |
不要對區域設置特定的字符串進行硬編碼 |
全球化規則 |
很繁瑣,並且工具支持的不好。全球化規則全部禁用 |
禁用 |
CA2100 |
檢查 Sql 查詢中是否有安全漏洞 |
安全性規則 |
都采用參數化查詢,有可能會參數過長;如果是內部參數,也不會有安全問題 |
警告 |
c#代碼規範