iT邦幫忙

DAY 3
3

Kuick Application & ORM Framework系列 第 3

Kuick -- C# 代碼規範和設計指引

建立軟體開發團隊的第一步驟,統一代碼編寫規範。
這裡提供一份超過 30 頁的 Word 檔案,針對 C# 程式編寫樣式規範與命名慣例,歡迎下載參考,或是修改成適合貴公司需求的內容。
1 為何需要規範程式碼編寫樣式
1.1 降低研發支出
軟體研發支出有80%用在系統維護。

1.2 簡化系統維護
軟體幾乎不可能持續由原作者維護。

1.3 協同合作開發
程式碼編寫樣式規範將能夠有效提昇原始檔的可讀性,工程師將能較迅速且全面地瞭解新的程式碼。

1.4 提昇產品價值
如果程式碼將會被提供至客戶端,良好的程式碼編寫樣式將能提昇產品的價值。

2 一般說明
2.1 範圍
本份文件以 .Net Framework 4.0, Microsoft Visual Studio 2010 with C# 作為說明的主體。
為了達成依據此份文件所編寫的元件,能夠同時被 C# 與 VB.NET 引用,請特別注意這兩種語言對於變數名稱英文大小寫判別上的差異。

2.2 專有名詞
為避免中英文專有名詞翻譯習慣不同,導致語意上的錯置或誤解,文件中專有中文名詞盡量輔以英文名詞。

2.3 縮寫
2.3.1 LOC
Lines of Code
程式碼行數

2.3.2 IDE
Integrated Development Environment
整合性開發環境,在此指Microsoft Visual Studio 2010

2.4 變數

例如:

public class TestClass
{
	private string _Field;
	public void TestFunction(string parameter)
	{
		string variable;
	}
}

2.5 編輯環境
2.5.1 Tab & Space
2.5.1.1 樣式
保有{Tab}原本的樣式,不以{Space}取代。

2.5.1.2 可見性
將{Tab}, {Space}字元,設定成可見形式,並適當調整顯示的顏色深淺。

2.5.1.3 寬度
將{Tab}與{Space}的寬度關係設定成 {Tab} = 4 x {Space}

2.5.2 字型
使用固定寬度的字型(fixed-width fonts)。
強烈建議使用Courier New, Consolas,作為程式碼顯示的字型

2.6 檔案組織
2.6.1 檔案
2.6.1.1 行數
單一程式檔案,以不超過2000 LOC為原則。
善用#region .. #endregion,規劃出讓程式更易於閱讀瞭解的編排結構

2.6.1.2 file & class
每一原始檔案內,不可以有兩個以上的public class,除非是巢狀的結構。

2.6.1.3 File Name & class, enum, struct Name
每一個class, enum, struct,皆單獨置於不同的實體檔案,檔名與class, enum, struct名稱相同。
例如:檔名Heartbeat.cs, 類別名稱class Heartbeat

2.6.2 目錄
以命名空間對應至實體目錄規劃
例如:目錄 MyProject/Xxx/Yyy ,命名空間 MyProject.Xxx.Yyy

3 命名規則 Naming Guideline
命名規則制定的目的,在於提供每位開發人員對於程式碼內容,有一致且直觀的理解。
在良好的開發工具下,好的變數命名主是要達到語意的描述,而不再是型態的指示。

3.1 命名規則介紹
3.1.1.1 巴斯卡命名法Pascal Casing
複合字,每一單字字首字母大寫
例如:PascalCasing

3.1.1.2 駱駝命名法Camel Casing
複合字,除了第一個字的字母小寫之外,其餘每一單字字首字母大寫
例如:camelCasing

3.1.1.3 大寫命名法Upper case
全以大寫字母表示,字母之間以下底線連結
例如:UPPER_CASE

3.1.1.4 Hungarian notation
以特定前置詞表示變數型態,後接Pascal Casing
除了關於UI實例(instance)宣告之外,不再使用匈牙利命名法。
例如:txtUserName

3.2 視別子與命名規則


3.3 一般命名規範
3.3.1 文字選用
使用合乎情境且常用的文字
儘量不使用縮寫文字
除const變數之外,不使用下底線
不使用程式關鍵字,複合字不在此限

3.3.2 縮寫
視別子名稱要達到可以一目了然的原則,一般不採用縮寫為視別子命名。
例如:

GetConnection(); // good
GetConn();        // bad

3.4 為現存API新增一視別子
為舊視別子加上保留屬性(Attribute),新視別子以舊視別子的名稱作為prefix
/
/ old
[Obsolete("GetEmail has been deprecated. Please use GetEmail2")]
public string GetEmail(string userName)
{
...
}

// new
public string GetEmail2(string userName) 
{
	...
}

3.5 Namespace and Assembly
Namespace表達邏輯上的程式群組,Assembly則是實體上的集合體。
不同的Assembly,可能擁有相同的Namespace。
原則上Assembly名稱等同於Root Namespace名稱。

3.6 類別(Type/Class)成員的名稱
類別(Type/Class)成員包括:
方法(method), 屬性(property), 事件(event), 建構式(constructor), 參數(field/data member)。

範例:

using System;

namespace Kuick.Data
{
	public abstract class SqlDatabase
	{
		public SQLDatabase(ConfigDatabase setting)
		{
			this.Setting = setting;
		}

		public ConfigDatabase Config
		{
			get;
			private set;
		}

		public void GetDatabaseVersion(IntervalLogger il)
		{
		}
	}
}

3.7 ASP.Net表單控制項物件命名
使用匈牙利命名法(Hungarian notation),例如:

4 排版
4.1 Visual Studio 設定
請參照另一附件Kuick-2012-10-10.vssettings

4.2 換行
4.2.1 逗號〝,〞後換行

TestMethodCall(
	Arg1,
	Arg2,
	Arg3,
);

4.2.2 運算子後換行

total = 
	a * b / (c - g + f) +
	4 * z;

4.2.3 巢狀程式碼,依外層區隔符號換行

// good
zTotal = 
	a * b / (c - g + f) +
	4 * z;

// bad
zTotal = a * b / (c - g + 
	f) + 4 * z;

4.2.4 Namespace, class, method, property 前置大括號〝{〞與後置大括號〝}〞皆單獨置於一行。if, else, switch, for, get, set 〝}〞單獨置於一行

namespace Kuick
{
	public class TestClass
	{
		public void TestFunction(int index)
		{
			switch(index) {
				case 1:
					...
					break;
				default:
					...
					break;
			}

			if(index == 0) {
				...
			}
			else {
			}

			for (var i; i < len; i++) {
				...
			}
		}
	}
}

4.3 空白
空白的目的是增加程式的可讀性。
將設定一個{Tab}字元等於4個的{Space}字元寬度。
僅用{Space}作為單行程式碼內的調整,不可使用{Tab}。

4.3.1 縮排
以{Tab}作為內縮字元。
每一層縮排,內縮一個{Tab}。
{Tab}一律以4個{space}字元代替
單行程式碼內不使用{Tab}

4.3.2 空格
陳述內,空格置於〝;〞後

for (var i; i < len; i++) {
    ...
}

多參數間區隔,空格置於〝,〞後

TestFunction(Var1, Var2, Var3);

變數後註解,以空格對齊

使用空白作為同一行程式碼之間的對齊排版,不要使用tab處理

4.3.3 空白行
空行的目的是增加程式的可讀性。
使用時機:
區域變數與陳述之間
方法內的程式邏輯區段之間。
函數之間
using與namespace之間。

4.4 單行長度
考慮當前螢幕解析度,程式碼單行欄位數目以不超過100字元為原則。
中文字字元寬度以兩倍計算。

4.5 區塊
善加利用區塊指令(#region .. #endregion),規劃出讓程式更易於閱讀瞭解的編排結構。
類別內一般可分成Fields, Constructors, Properties, Methods,請依前述順序編程,且分別使用區塊指令包覆,如類別內程式碼行數低於200行,使用區塊指令的義意不大。
函數內複雜的邏輯區塊,以獨立成輔助函數為優先,其次再適當地使用區塊指令包覆。

4.6 註解
註解行數最好不要超過所解釋的程式碼行數。如果超過,代表程式過於精簡、複雜,容易出現錯誤,不易維護。

4.6.1 區塊註解
請不要再使用區塊註解,原因有下列幾點:
區塊註解通常是延用自C語言的習慣,現今的IDE中,可由工具快速將多行程式轉成單行型式的註解。
區塊註解不符合人類閱讀習慣。
外掛的文件產生器(NDoc)無法判定這類的註解。

4.6.2 行後註解
程式碼行後的註解型式為簡易的註解方式,主要用於變數宣告說明,其餘地方不建議使用。

4.6.3 單行註解
單行註解為唯一的允許的註解型式。

4.6.4 說明文件註解
使用NDoc作為文件產生器,註解的規則請遵照NDoc的說明。
http://ndoc.sourceforge.net/

4.7 宣告
4.7.1 變數(Field)
類別變數定義需置於區段最前部份
函數內變數需要時再宣告,勿宣告於函數最前部份
變數宣告須給予初始值
一行一個變數宣告,後置變數說明

4.7.2 方法
如果有多項參數,會使得宣告超過95字元時,則將各個參數單獨列成一行。

4.8 其他
4.8.1 引用函數
單行可處理完成

無法於單行處理完成時,每一參數皆換行

4.8.2 回傳
回傳值本身不再使用括號

除了單純運算式可以直接回傳之外,其他請以變數回傳

4.8.3 陳述區塊
即使陳述區塊內僅有一行程式,也請使用大括號圈住
此規範包含if-else, for, foreach, while, do-while

4.8.4 一般規範
一行只包含單一個陳述。
避免程式結構出現超過三層巢狀區塊
相同或類似程式碼使用超過兩次者,一律獨立成程序
單一區塊程式碼儘量不超過50行
類別全域變數,如果有提供對應的屬性時,變數定義成private,其他函數、程序皆需以引用屬性的方式,修改與讀取其值。

5 設計規則Design Guideline
5.1 類別設計Type Design
5.1.1 型別類型
在CLR中,只有兩種型別
Reference types
資料配置在堆積(heap),複製是以參照方式完成
Value types
資料配置在堆疊(stack),複製則是複製完整的內容

在程式設計階段,各類別依Reference type/Value type區分如下:
Reference types
Classes
Static classes ( C# 2.0 only)
Collections and Array
Delegation and Exception
Attributes
Value types
Structs
Enums
Interface

5.1.2 採用Struct或是Class設計
使用Struct設計的考量條件:
只包基本型態
物件小於16 bytes
所存資料不再改變
資料不需時常轉型

5.1.3 採用Class或是Interface
一般而言,Class會是比較好的實作方式,主要原因是Interface比較沒有彈性,當Interface對外發佈後,通常就不能再作修改,因為修改Interface會破壞與所有實作類別的協議。
請常用Class,少用Interface
請常以Abstract替代Interface
當有多型繼承需求時,請使用Interface
當實作多層式架構時,請考慮制訂Interface

5.1.4 抽象類別(Abstract Class)
請不要定義public constructor,public constructor 唯有在他的確能夠完成建立物件時才使用。
請為抽象類別定義protected constructor。

5.2 成員設計Member Design
5.2.1 Methods and Overloading Methods
使用camelCasing且有意義的名稱為傳入參數命名。
使用camelCasing且縮寫為函數內變數命名

避免在多載的函數間使用不同的傳入參數名稱

多載函數如果有擴充的需求,請將最長的多載函數指定為virtual,其他較短的多載函數則是引用最長的函數完成。

多載函數不可使用ref/out 修飾詞(modifier),因為一些程式語言無法引用這類的多載函數。
以VB.NET編寫多載函數時,請勿使用Optional,請採用標準的多載函數處理。

當public/protected函數的傳入參數驗證失敗,請拋出System.ArgumentExcetption

如果驗證enum傳入參數失敗(enum值不在合理範圍),請拋出System.ArgumentOutOfExcetption
請勿使用System.Enum.IsDefined作為enum值的範圍驗證,因為使用System.Enum.IsDefined的代價昂貴。

5.2.2 如何決定使用Property或是Method設計
不要將所有的類別的函數與變數定義為public,儘量以屬性作為類別變數的存取方式,並且仔細考慮類別成員可視性的範圍(public, private, protected)

以下情況請使用property:
請以property表達物件的屬性
例如:Botton.Color
如果只需單純存取儲存於process的數值資料,請使用property

以下情況請使用method:
需占用資源的操作,例如對網路或檔案系統的操作。
處理資料型態轉換,例如:Object.ToString()
每次回傳的資料不用時,即使沒有傳入參數。
.Net framework的DateTime.Now就是一個不好的設計,或許使用
DateTime.GetCurrentDateTime()會是比較好的。
Array的回傳型態,除了PropertyGrid之外

5.2.3 this與base關鍵字的使用
this與base僅限定於建構式(constructor)與覆寫函數(new, override method)使用

5.2.4 變數與型態
使用C#內鍵的資料型態:
使用short不用Int16
使用int不用Int32
使用long不用Int64
使用string不用String
將field宣告成private,使用property以替代需要宣告成public/protected/internal的需求。
如有特定數值,請改以const或採用enum設定;No 'magic' Numbers

如有特定字串,請改以資源檔、const、設定檔(Configuration File)、登錄檔(Registry)處理
只有簡單型態資料可宣告成const,複雜型態資料請請宣告成readonly或static readonly
避免直接轉型(Cast),請改用as並驗證是否為null

儘量使用泛型(Generic Collection)替代一般的強型態集合的需求。
避免boxing/unboxing

5.2.5 字串處理
字串處理非常消耗系統資源,處理時須特別謹慎。
編寫字串實字時,請以@作為prefix,以替代escape string
習慣使用String.Format()或是StringBuilder,以替代字串串接
單純的動態字串串接使用 String.Concat,有需要格式的才改用 String.Fomat
若全為靜態字串,用@ 或 + 做為串接方式
String.Format 和 String.Concat 開頭大寫 String, 以區分 Class 和型別的不同
需大量Replace用StringBuilder的Replace方法,而不使用String的Replace

絕不在loop內使用字串串接
使用String.IsNullOrEmpty 作為空字串檢驗
字串比對如需考慮大小寫,請以String.Compare()或String.Equals()為之,不使用String.ToLower(),以避免建立暫存字串。

5.3 Exception
5.3.1 一般規範
只catch有能力處理的exception
不用try/catch包住流程控制
不宣告空的catch區段
catch區段內,不使用巢式try/catch
catch區段內,如需再拋出同一個exception,請用throw。

finally只用來釋放來自try區段中引用的資源。
使用驗證機制,避免過度使用try/catch

5.3.2 Exception Throwing
相較於使用數值回傳類型的錯誤報告機制,exception可提供其他更多且完整的訊息,請勿單純使用錯誤代碼作為exception時的回傳,請直接throw exception,不使用其他方式回報錯誤或是判斷是否拋出錯誤。
例如:

5.3.3 Exception Handling
對於資料物件操作時,請善用try-catch-finally,並且在finally區段將try區段中建立有使用到資料的物件結束。

當需要將Exception往外拋出時,請使用空的throw。

如需建立巢式try-catch,請將內層try-catch獨立出建立method處理。

5.3.4 Using Standard Exception Types
避免拋出System.Exception, System.SystemException,請拋出標準的Exception,如InvaidOperationException, ArgumentException, ArgumentNullException, ArgumentOutOfRangeException,或是自定的Exception。

5.3.5 Design Custom Exceptions
自定Exception需繼承System.Exception,並且設定且實作成serializable,因為需考慮系統跨平台整合或透過Remoting, Web Service處理資料交換時,物件將以Xml作為傳遞的型態。

5.4 一般規範
5.4.1 不省略存取修飾詞
使用private修飾詞雖然與無指定修飾詞的效果一樣,還是建議補上private

5.4.2 流程控制
避免建立遞迴的函數,改用迴圈(loop)或是巢狀的迴圈(nested loop)替代
使用三元運算子時,避免條件過於複雜
在條件陳述式中,不將布林變數與true/false作比較

5.4.3 元件編寫
仔細檢視每一個使用public修飾詞的class/property/method是否需要
避免設計使用超過7個傳入參數的函數,如有需求可考慮程式重構,或以自定物件作為傳入參數。
當複寫Equals()函數時,也請複寫 “==” 運算子
當建立實作IDisposable介面物件時,請使用using陳述,以確保Dispose()會被自動執行
通常需佔用特定資源的物件,皆實作IDisposable介面

避免自行建Finalize()作為解構函數,改採C#內鍵的表式語法

區隔企業邏輯層(business logic)與前端介面層(presentation layer) 獨立
當實作Design Pattern時,class name請以該pattern名稱作為結尾詞(suffix)

5.4.4 組件
組件需用強式名稱簽署組件。

5.4.5 其他
lock
只使用private/static private 物件作為lock對象
避免lock所在的實體(this)
不使用Get/Set作為property的prefix

5.5 註解
註解應該解釋「程式碼為什麼要這麼寫」
寫註解的重要態度:站在讀者的立場

6 Reference
1). SharpDevelop C# Coding Style Guide 0.3
http://www.icsharpcode.net
2). IDesign C# Coding Standard 2.32
http://www.idesign.net
3). Design Guidelines, Managed code and the .NET Framework, by Brad Abrams
http://blogs.msdn.com/brada/default.aspx

========================================
鐵人賽分享列表:Kuick Application & ORM Framework
開放原始碼專案:kuick.codeplex.com
直接下載原始碼:Kuick
下載相關文件檔:C# Code Conventions and Design Guideline
相關教學影片區:Kuick on YouTube


上一篇
什麼是 Kuick
下一篇
Kuick -- 你聽得到系統的心跳聲嗎?
系列文
Kuick Application & ORM Framework34

2 則留言

0
pajace2001
iT邦研究生 1 級 ‧ 2012-10-11 01:22:01

東西好多~
看來要好好消化一下~
讚

0
海綿寶寶
iT邦超人 1 級 ‧ 2012-10-11 08:19:19

文中有些標題不同但程式碼重覆的片段
可能是誤植吧

4.6.1到5.2.1
5.2.1 9 到 5.3.2
5.3.3 到 5.4.1 6

謝謝 antijava 幫忙細心校稿,
這篇內容是取自所附下載聯結的 Wrod 檔,花了些時間調整,如果可以還是直接看 Word 檔來得方便些。

發現在此分享文章,一篇內只最多只可包含 10 組程式碼,超過的部份會一直重複第一段,所以將超過的部份改為圖型。

我要留言

立即登入留言