目錄 1.簡介... 2 2.適用範圍... 2 3.規範目的... 2 4.代碼組織與風格... 2 4.1 Tab鍵... 2 4.2 縮進... 3 4.3空行... 3 4.4函數長度... 3 4.5行寬... 3 4.6{ “,”} 3 5.文件命名... 3 6.命名... 4 6.1 ...
目錄
1.簡介... 2
2.適用範圍... 2
3.規範目的... 2
4.代碼組織與風格... 2
4.1 Tab鍵... 2
4.2 縮進... 3
4.3空行... 3
4.4函數長度... 3
4.5行寬... 3
4.6{ “,”} 3
5.文件命名... 3
6.命名... 4
6.1基本約定... 4
6.2程式集命名... 4
6.3命名空間命名... 4
6.4類和介面命名... 5
6.5方法命名... 5
6.7變數命名... 5
6.8組件名稱縮寫列表... 5
6.9資料庫縮寫列表... 7
6.10其他命名... 8
7.類型設計... 8
7.1類型和命名空間... 8
7.2類型和介面選擇... 9
7.3屬性和方法的選擇... 9
7.4類設計... 9
7.5枚舉設計... 9
8.成員設計... 10
8.1方法重載... 10
8.2屬性設計... 11
8.3構造函數設計... 11
8.4欄位設計... 11
8.5參數設計... 11
8.6擴展性設計... 12
8.7異常處理... 12
9.代碼註釋... 12
9.1代碼註釋約定... 12
9.2文件頭部註釋... 13
9.3方法註釋... 14
9.4代碼行註釋... 15
9.5變數註釋... 15
10.參考文檔... 15
術語:
PasalCasing:標識符的第一個單詞的字母大寫;
CamelCasing:標識符的第一個單詞的字母小寫。
1.簡介
本規範為一套編寫高效可靠的 C# 代碼的標準、約定和指南。它以安全可靠的軟體工程原則為基礎,使代碼編寫得易於理解、維護和增強,同時提高生產效率。
2.適用範圍
- 本規範主要以C#為開發語言的原則性規範;
- 適用人員:軟體工程師;
- 適用產品:各類軟體。
3.規範目的
編碼規範背景:
1. 一個軟體的生命周期中,80%的花費在於維護;
2. 幾乎沒有任何一個軟體,在其整個生命周期中,均由最初的開發人員來維護;
3. 編碼規範可以改善軟體的可讀性,可以讓程式員儘快而徹底地理解新的代碼。為了執行規範,每個軟體開發人員必須一致遵守編碼規範:
- 使用統一編碼規範的主要原因,是使應用程式的結構和編碼風格標準化,以便於閱讀和理解這段代碼;
- 好的編碼約定可使源代碼嚴謹、可讀性強且意義清楚,與其它語言約定相一致,並且儘可能的直觀,這樣對於後期維護升級非常重要。
4.代碼組織與風格
4.1 Tab鍵
換行要使用一個為四個空格的Tab。
4.2 縮進
一個代碼塊內同級代碼統一使用相同的Tab長度。
4.3空行
適當增加空行,增加代碼塊之間的可讀性。
在類與類、介面與介面之間添加兩行空行。
以下情況可以適當添加一行空行:
方法之間、局部變數和適當語句之間、不同功能邏輯塊之間等情況。
4.4函數長度
每個方法有效代碼(不包括註釋和空行)不要超過50行,做到方法職責單一。
4.5行寬
每行代碼和註釋不要超過70個字元或屏幕的寬度,如超過則應換行,換行後的代碼應該縮進一個Tab。
4.6{ “,”}
開括弧“{”要放在代碼塊的所有者的下一行,單起一行;
閉括弧“}”要單獨放在代碼塊的最後一行,單起一行。
5.文件命名
- 文件命名原則是更容易區分不同的文件類型和作用。
在文件名前增加三字元的首碼,首碼字母一律為小寫,例如:
一個窗體文件可以增加frm首碼,frmImportData.cs
- 文件主體名必須用名詞或動名詞,且主體名必須是單詞首字大寫的方式表示
例如:
數據導入的窗體可以命名為frmImportData.cs
- 文件名必須採用在不影響原意表達時儘量採用單詞縮寫的形式命名,以達到文件名簡潔明瞭的命名目的。
- 文件名要和類名匹配
例如,對於類HelloWorld, 相應的文件名應為
HelloWorld.cs (或, HelloWorld.vb)
6.命名
6.1基本約定
標示符 |
命名類型 |
示例 |
命名空間 |
Pascal |
namespace Com. SMSoft.ProductionCenter |
類型 |
Pascal |
public class Student |
介面 |
Pascal |
public interface ICreateTable |
方法 |
Pascal |
public void UpdateData() |
屬性 |
Pascal |
public int Length{ get; set; } |
事件 |
Pascal |
public event EventHandler ChangedEvent; |
枚舉值 |
Pascal |
FileEnun{Open; Close; } |
非私有欄位 |
Pascal |
public string FieldName; |
私有欄位 |
Camel |
private string fieldName; |
參數 |
Camel |
public void UpdateData(string fieldName) |
局部變數 |
Camel |
string fieldName; |
6.2程式集命名
公司功能變數名稱(SMSoft)+ 項目名稱 + 模塊名稱(可選),例如:
中心系統程式集:SMSoft.ProductionCenter;
中心系統業務邏輯程式集:SMSoft. ProductionCenter.Business;
6.3命名空間命名
命名空間應使用解決方案的名稱,每個項目應設置一個二級命名空間,並以項目名命名。
採用和程式集命名相同的方式:公司功能變數名稱(SMSoft)+ 項目名稱 + 模塊名稱。 另外,一般情況下建議命名空間和目錄結構相同。例如:
中心系統:SMSoft.ProductionCenter;
中心系統下的用戶控制項:SMSoft.ProductionCenter.UserControl;
中心系統業務邏輯:SMSoft. ProductionCenter.Business;
中心系統數據訪問:SMSoft. ProductionCenter.Data;
6.4類和介面命名
類的名字要用名詞,避免使用單詞的縮寫,除非它的縮寫已經廣為人知,
如TCP、HTTP、SQLHelper。
介面的名字要以字母I開頭。保證對介面的標準實現名字只相差一個“I”首碼,例如對ICompare的標準實現為Compare;
泛型類型參數的命名:命名要為T或者以T開頭的描述性名字,例如:
public class List<T>
public class MyClass<TSession>
對同一項目的不同命名空間中的類、介面要避免命名重覆。避免引用時的衝突和混淆。
6.5方法命名
方法名一般採用:動詞+名詞,表達要做什麼。
如果方法返回一個成員變數的值,方法名一般為Get+成員變數名;
如果方法返回的值是bool變數,一般以Is作為首碼或Try首碼;
如果方法修改一個成員變數的值,方法名一般為:Set + 成員變數名。
6.7變數命名
按照使用範圍來分,我們代碼中的變數的基本上有以下幾種類型:
類的公有變數(受保護同公有);類的私有變數;方法的參數變數;方法內部使用的局部變數。這些變數的命名規則基本相同。區別如下:
- 類的公有變數按通常的方式命名,無特殊要求。例如WorkerName;
- 類的私有變數採用兩種方式均可:採用加“m”首碼更好區別局部變數。
例如mWorkerName,wokrerName;
- 方法的參數變數採用camalString,例如workerName;
- 方法內部的局部變數採用camalString,例如workerName;
不要用_或&作為第一個字母;
儘量要使用短而且具有意義的單詞;
單字元的變數名一般只用於生命期非常短暫的變數。i,j,k,m,n一般用於integer;c,d,e 一般用於characters;s用於string
如果變數是集合,則變數名要用複數。例如表格的行數,命名應為:RowsCount;
命名組件要採用匈牙利命名法,所有首碼均應遵循同一個組件名稱,參考縮寫列表。
6.8組件名稱縮寫列表
縮寫的基本原則是取組件類名各單詞的第一個字母,如果只有一個單詞,則去掉其中的母音,留下輔音。縮寫全部為小寫。
組件類型 |
簡寫 |
標準命名舉例 |
Label |
lbl |
lblMessage |
LinkLabel |
llbl |
llblToday |
Button |
btn |
btnSave |
TextBox |
txt |
txtName |
MainMenu |
mmnu |
mmnuFile |
CheckBox |
chk |
chkStock |
RadioButton |
rbtn |
rbtnSelected |
GroupBox |
gbx |
gbxMain |
PictureBox |
pic |
picImage |
Panel |
pnl |
pnlBody |
DataGrid |
dgrd |
dgrdView |
ListBox |
lst |
lstProducts |
CheckedListBox |
clst |
clstChecked |
ComboBox |
cbo |
cboMenu |
ListView |
lvw |
lvwBrowser |
TreeView |
tvw |
tvwType |
TabControl |
tctl |
tctlSelected |
DateTimePicker |
dtp |
dtpStartDate |
HscrollBar |
hsb |
hsbImage |
VscrollBar |
vsb |
vsbImage |
Timer |
tmr |
tmrCount |
ImageList |
ilst |
ilstImage |
ToolBar |
tlb |
tlbManage |
StatusBar |
stb |
stbFootPrint |
OpenFileDialog |
odlg |
odlgFile |
SaveFileDialog |
sdlg |
sdlgSave |
FoldBrowserDialog |
fbdlg |
fgdlgBrowser |
FontDialog |
fdlg |
fdlgFoot |
ColorDialog |
cdlg |
cdlgColor |
PrintDialog |
pdlg |
pdlgPrint |
6.9資料庫縮寫列表
數據類型 |
數據類型簡寫 |
標準命名舉例 |
Connection |
con |
conNorthwind |
Command |
cmd |
cmdReturnProducts |
Parameter |
parm |
parmProductID |
DataAdapter |
dad |
dadProducts |
DataReader |
dtr |
dtrProducts |
DataSet |
dst |
dstNorthWind |
DataTable |
dtbl |
dtblProduct |
DataRow |
drow |
drowRow98 |
DataColumn |
dcol |
dcolProductID |
DataRelation |
drel |
drelMasterDetail |
DataView |
dvw |
dvwFilteredProducts |
6.10其他命名
每行要只有一個聲明或一條語句,變數名要避免塊內部的變數與它外部的變數名相同。
除了for迴圈外,聲明要放在塊的最開始部分。for迴圈中的變數聲明可以放在for語句中。如:
for(int i = 0; I < 10; i++)
{ }
if-else,if-elseif語句,任何情況下,都應該有“{”,“}”;
switch語句,每個switch里都應包含default子語句;
7.類型設計
確保每個類型由一組定義明確相互關聯的成員組成,而不是一些無關功能的隨機集合。
7.1類型和命名空間
要用命名空間把類型組織成相關域的層次結構。例如:
界面層:SMSoft.ProductionCenter;
業務邏輯層:SMSoft.ProductionCenter.Business;
數據訪問層:SMSoft.ProductionCenter.Data;
避免過深的命名空間;
避免太多的命名空間;
7.2類型和介面選擇
要優先採用類而不是介面。
介面的缺點在於語義變化時改變困難。註意介面並不是協定,把協定和實現分開並非一定用介面實現,用基類和抽象類同樣可以表達;
建議使用抽象類而不是介面來解除協定與實現間的偶合;
要定義介面,來實現類似多重繼承的效果;
精心定義介面的標誌是一個介面只做一件事情。關鍵是介面的協定需要保持不變, 如果一個介面包含太多功能,那麼這個胖介面產生變化的機會就會大得多。
7.3屬性和方法的選擇
基本原則是方法表示操作,屬性表示數據。如果其他各方面都一樣,優先使用屬性而不是方法。
要使用屬性,如果該成員表示類型的邏輯attribute
如果屬性的值存儲在記憶體中,而提供屬性的目的僅僅是為了訪問該值,要使用屬性而不要使用方法
如果該操作每次返回的結果不同,那麼要使用方法。
如果該操作比訪問欄位慢一個或多個數量級,要使用方法。
如果該操作有嚴重的副作用,要使用方法。
7.4類設計
抽象類:
不要在抽象類中定義公有的或內部受保護的構造函數。因為抽象類無法實例化,所以這種設計會誤導用戶;
要為抽象類定義受保護的構造函數或內部構造函數;
靜態類:
靜態類是一個只包含靜態成員的類,它提供了一種純面向對象設計和簡單性之間的一個權衡,廣泛用來提供類似於全局變數或一些通用功能。
要少用靜態類。靜態類應該僅用作輔助類、工具類;
避免把靜態類當作雜物箱。每個靜態類都應該有其明確目的;
不要在靜態類中聲明或覆蓋實例成員;
7.5枚舉設計
要用枚舉來加強那些表示值的集合的參數,屬性以及返回值的類型性;
要優先使用枚舉而不是靜態常量
8.成員設計
方法,屬性,事件,構造函數以及欄位等統稱為成員。
1) 一個方法只完成一個任務。不要把多個任務組合到一個方法中,即使那些任務非常小。
2) 使用C#的特有類型,而不是System命名空間中定義的別名類型。
3) 別在程式中使用固定數值,用常量代替。
4) 避免使用很多成員變數。聲明局部變數,並傳遞給方法。不要在方法間共用成員變數。如果在幾個方法間共用一個成員變數,那就很難知道是哪個方法在什麼時候修改了它的值。
5) 別把成員變數聲明為 public 或 protected。都聲明為 private 而使用 public/protected 的屬性
6) 不在代碼中使用具體的路徑和驅動器名。 使用相對路徑,並使路徑可編程。
7) 應用程式啟動時作些“自檢”並確保所需文件和附件在指定的位置。必要時檢查資料庫連接。出現任何問題給用戶一個友好的提示。
8) 如果需要的配置文件找不到,應用程式需能自己創建使用預設值的一份。
9) 如果在配置文件中發現錯誤值,應用程式要拋出錯誤,給出提示消息告訴用戶正確值。
10) DataColumn取其列時要用欄位名,不要用索引號。
例: 正確DataColumn[“Name”]
不好 DataColumn[0]
11) 在一個類中,欄位定義全部統一放在class的頭部、所有方法或屬性的前面。
12) 在一個類中,所有的屬性全部定義在一個屬性塊中,用region摺疊:
13) 文件讀取、數據連接方法最後要手動清理。
8.1方法重載
避免在重載中隨意的給參數命名。如果兩個重載中的某個參數表示相同的輸入,那麼該參數的名字應該相同。
避免使重載成員的參數順序不一致。在所有的重載中,同名參數應該出現在相同的位置。
較短的重載應該僅僅調用較長的來實現。另外,重載如果需要擴展性,把最長重載做成虛函數。
要允許可選參選為null。這樣做是為了避免調用者調用之前需要檢查參數是否null。
8.2屬性設計
如果不應該讓調用方法改變屬性值,要創建只讀屬性;
不要提供只寫屬性;
要為所有的屬性提供合理的預設值,這樣可以確保預設值不會導致漏洞或效率低的代碼;
要允許用戶以任何順序來設置屬性的值;
避免在屬性的獲取方法拋出異常。
屬性的獲取方法應該是個簡單的操作,不應該有任何的條件。如果一個獲取方法會拋出異常,那麼可能它更應該設計為方法。
8.3構造函數設計
建議提供簡單的構造函數,最好是預設構造函數。簡單的構造函數增強易用性;
考慮擴展性,如果構造函數設計的不自然,建議用靜態的工廠方法來替代構造函數;
要把構造函數的參數用作設置主要屬性的便捷方法。如果構造函數參數僅用來設置屬性,應和屬性名稱相同。僅有大小寫的區別;
要在構造函數中做最少的工作。任何其他處理應該推遲到需要的時候;
要在類中顯示的聲明公用的預設構造函數,如果這樣的構造函數是必須的。
如果沒有顯示預設構造函數,填加有參數構造函數時往往會破壞已有使用預設構造函數的代碼;
避免在對象的構造函數內部調用虛成員。這樣在擴展設計的時候會導致難以理解的現象;
8.4欄位設計
不要提供公有的或受保護的欄位。代之以屬性來訪問欄位;
要只用常量欄位來表示永遠不會改變的量。否則會導致相容性問題。
要用公有的靜態只讀欄位來定義預定義的對象實例。
8.5參數設計
要用類結構層次中最接近基類類型來作為參數的類型,同時要保證該類型能夠提供成員所需的功能。
要設計一個集合遍歷的方法,那麼參數應該是IEnbumerable為參數,而不應該是IList,這樣方法具有更強的適應性。
不要使用保留參數。如果將來需要更多的參數,那麼可以增加重載成員。
8.6擴展性設計
如果沒有恰當理由,不要把類密封起來。這些理由包括:
A)類為靜態類;
B)類的受保護成員保存了高度機密信息;
C)類繼承了許多虛成員,逐個密封的代價太高,不如密封整個類;
D)不要在密封類中聲明保護成員或虛成員,因為無法覆蓋其實現;
建議用保護成員用於高級定製。它提供了擴展性,同時也避免了公用介面過於複雜;
不要使用虛成員,除非有合適的理由;
建議只有在絕對必須的時候才用虛成員提供擴展性,並使用Template Method模式;
要優先使用受保護的虛成員,而不是公有虛成員。公有成員通用調用受保護的虛成員的方式來提供擴展性;
8.7異常處理
異常的思想是只對錯誤採用異常處理:邏輯和編程錯誤,設置錯誤,被破壞的數據,資源耗盡,等等。通常的法則是系統在正常狀態下以及無重載和硬體失效狀態下,不應產生任何異常。異常處理時可以採用適當的日誌機制來報告異常,包括異常發生的時刻;
一般情況下不要使用異常實現來控製程序流程結構;
發生異常時,給出友好的消息給用戶,但要精確記錄錯誤的所有可能細節,包括發生的時間,和相關方法,類名等。
要通過拋出異常的方式來報告操作失敗。如果成員無法成功地完成它應該做的任務,那麼應該拋出異常;
不要“捕捉了異常卻什麼也不做“。如果隱藏了一個異常,你將永遠不知道異常到底發生了沒有。
優先考慮使用System命名空間中已有的異常,而不是自己創建新的異常類型;
要使用最合理,最具針對性的異常。例如,對參數為空,應拋出 System.ArgutmentNullException,而不是System.ArgutmentException。
所有外部資源都必須顯式釋放。例如:資料庫連接對象、IO對象等。
9.代碼註釋
9.1代碼註釋約定
1. 所有的方法和函數都應該以描述這段代碼的功能的一段簡明註釋開始(方法是乾什麼)。這種描述不應該包括執行過程細節(它是怎麼做的),因為這常常是隨時間而變的,而且這種描述會導致不必要的註釋維護工作,甚至更糟—成為錯誤的註釋。代碼本身和必要的嵌入註釋將描述實現方法。
2. 當參數的功能不明顯且當過程希望參數在一個特定的範圍內時,也應描述傳遞給過程的參數。被過程改變的函數返回值和全局變數,特別是通過引用參數的那些,也必須在每個過程的起始處描述它們。
9.2文件頭部註釋
以一個物理文件為單元的都需要有模塊頭部註釋,例如:C#中的.cs文件。
用於每個模塊開頭的說明,主要包括:(粗體字為必需部分,其餘為可選部分)
1. 文件名稱(File Name):此文件的名稱
2. 功能描述(Description):此模塊的功能描述與大概流程說明
3. 數據表(Tables):所用到的數據表,視圖,存儲過程的說明,如關係比較複雜,則應說明哪些是可擦寫的,哪些表為只讀的。
4. 作者(Author):
5. 日期(Create Date):
6. 參考文檔(Reference)(可選):該檔所對應的分析文檔,設計文檔。
7. 引用(Using) (可選)﹕開發的系統中引用其它系統的Dll、對象時,要列出其對應的出處,是否與系統有關﹙不清楚的可以不寫﹚,以方便製作安裝檔。
8. 修改記錄(Revision History):若檔案的所有者改變,則需要有修改人員的名字、修改日期及修改理由。
9. 分割符:*************************** (前後都要)
9.3方法註釋
- C# 提供一種機制,使程式員可以使用含有XML 文本的特殊註釋語法為他們的代碼編寫文檔。在源代碼文件中,具有某種格式的註釋可用於指導某個工具根據這些註釋和它們後面的源代碼元素生成XML。具體應用當中,類、介面、屬性、方法必須有<summary>節,另外方法如果有參數及返回值,則必須有<param>及<returns>節點。示例如下:
/// <summary>
/// 方法作用
/// </summary>
/// <param name=””></param>
/// <returns></returns>
- 事件不需要頭註解,但包含複雜處理時(如:迴圈/資料庫操作/複雜邏輯等),應分割成單一處理函數,事件再調用函數。
- 所有的方法必須在其定義前增加方法註釋。
- 方法註釋採用 /// 形式自動產生XML標簽格式的註釋。
標記 |
說明 |
備註 |
<c> |
提供了一種將說明中的文本標記為代碼的方法 |
|
<code> |
提供了一種將多行指示為代碼的方法 |
|
<example> |
可以指定使用方法或其他庫成員的示例。一般情況下,這將涉及到 <code> 標記的使用。 |
|
<exception> |
對可從當前編譯環境中獲取的異常的引用。 |
|
<include> |
得以引用描述源代碼中類型和成員的另一文件中的註釋。 |
|
<list> |
用於定義表或定義列表中的標題行。 |
|
<para> |
用於諸如<summary>、<remarks> 或 <returns> 等標記內,使您得以將結構添加到文本中。 |
|
<param> |
應當用於方法聲明的註釋中,以描述方法的一個參數。 |
|
<paramref> |
提供了一種指示詞為參數的方法。 |
|
<permission> |
得以將成員的訪問記入文檔。 |
|
<remarks> |
用於添加有關某個類型的信息,從而補充由 <summary> 所指定的信息。 |
|
<returns> |
應當用於方法聲明的註釋,以描述返回值。 |
|
<see> |
得以從文本內指定鏈接。 |
|
<seealso> |
對可以通過當前編譯環境進行調用的成員或欄位的引用。 |
|
<summary> |
應當用於描述類型或類型成員。 |
|
<value> |
得以描述屬性。 |
|
- 在公用類庫中的公用方法需要在一般方法的註釋後添加作者、日期及修改記錄信息,統一採用XML標簽的格式加註,標簽如下:
<Author> </Author> 作者
<CreateDate></CreateDate> 建立日期
<RevisionHistory> 修改記錄
<ModifyBy> </ModifyBy> 修改作者
<ModifyDate> </ModifyDate> 修改日期
<ModifyReason> </ModifyReason> 修改理由
<ModifyBy> </ModifyBy> 修改作者
<ModifyDate> </ModifyDate> 修改日期
<ModifyReason> </ModifyReason> 修改理由
</RevisionHistory>
<LastModifyDate> </LastModifyDate> 最後修改日期
9.4代碼行註釋
- 如果處理某一個功能需要很多行代碼實現,並且有很多邏輯結構塊,類似此種代碼應該在代碼開始前添加註釋,說明此塊代碼的處理思路及註意事項等。
- 註釋從新行增加,與代碼開始處左對齊。
- 雙斜線與註釋之間以空格分開。
- 定義變數時需添加變數註釋,用以說明變數的用途。
- Class級變數應以採用 /// 形式自動產生XML標簽格式的註釋,
- 方法級的變數註釋可以放在變數聲明語句的後面(也可以放在前一行),與前後行變數聲明的註釋左對齊,註釋與代碼間以Tab隔開。
9.5變數註釋
- 定義變數時需添加變數註釋,用以說明變數的用途。
- Class級變數應以採用 /// 形式自動產生XML標簽格式的註釋,
- 方法級的變數註釋可以放在變數聲明語句的後面(也可以放在前一行),與前後行變數聲明的註釋左對齊,註釋與代碼間以Tab隔開。
10.參考文檔
本規範很多內容都參考了這本《.NET設計規範》,書中對規範背後的背景和原則做了深入討論。另外也參考了很多博客園的博客。有不對之處或有更好的建議歡迎拍磚,請郵件聯繫[email protected]。
磨刀不誤砍柴工