寫程式的參考準測 (coding guideline) - C# 篇

曾有一些朋友問我,在微軟公司裡是否有寫程式的準則 (coding guideline).這件事因不同的團隊而異,大部份的團隊都會依循 MSDN 文件裡的建議,但並非每一個團隊都有文件記錄這些準則.以前我在 Windows 部門裡的某個團隊就正好有文件說明 C# coding guideline.除了 C# coding guideline 以外,還有其他的文件,例如 code review 文件, database 開發文件等等.在這篇文章中,我將從 C# coding guideline 開始寫起.這些 coding guideline 不是什麼秘密,很多都是來自 MSDN 的文件.若你的團隊也需要一份 C# coding guideline, 希望能派的上用場.

1. 在一份 C# 原始碼裡,別有 Tab , space 並存.這一個可以善用 Visual Studio 的編輯器設定幫你解決.一般來說,我們用 space 來取代 tab,例如一個 tab 等於四個 spaces.

2. 一行程式碼的字數通常設定在 120 個字元左右.為了方便閱讀,若有一個 api 有多個參數需要傳入,將多個參數分行排列,如下例
<script src="https://gist.github.com/yihcheng/93503e6c1e73274e960745347aa195c6.js"></script>

3. 有關設定變數 (variable),盡可能地將其 scoping 範圍找小一點的.

4. 變數宣告時不一定要馬上給初始值  (initialize).用到此變數時在給即可.若需要給一個初始值,請給一個有意義的值做為初始值.這邊所謂有意義的初始值不代表該資料結構的預設值.如 int count = 0; ,這是多此一舉的行為.

5. Local variable 前面加上 _ .若是 global variable,則不用.
如  int _memberCount;   
int MemberCount;

6. 相同型別的變數要分行宣告.別擠在一行一起宣告,應分行.如:
int foo;
int bar;
int baz;

7. 對於常數型的變數 (內容不會變動) 記得使用 const 宣告. 如:
private const int _defaultCount = 100;

另外,對於這類常數型變數,最好能加上一個 comment 用來說明為何選用此內容. 如:
// 100 is chosen because of the size limit of the queue
private const int _defaultCount = 100;

8. 對於多個性質相關的常數型變數,可以考慮將他們整合在一個 static class 裡宣告並使用,如:
<script src="https://gist.github.com/yihcheng/a18375e591f98932e93c0f7b17b2a27a.js"></script>
可改成
<script src="https://gist.github.com/yihcheng/87c42439df866282af143b1a7dc4ae2a.js"></script>

9. 用 string.Empty 來取代 ""

10. 用 string.IsNullOrWhiteSpace() 來檢查字串是否為 null 或是空白.

11. 對於一些 “應用值”,不應該 “hard-code”,而是該透過其他較彈性的方式取得 (像 Configuration).如
網路連線重試次數,timeout 時間, thread 數量, 資料庫連線字串等等

12. 在 if , while , for, foreach, return 前多個空行,以便於閱讀.

13. Class 裡的 method 之間最好有一個或二個空白行.

14. 若有需要,善用 #region, #endregion 將相關的程式放在同一個區塊裡.同時 #region 之間也該有一個或二個空白行.

15. 善用 IDE 裡的編輯器幫助你處理好空白的事情.如
<script src="https://gist.github.com/yihcheng/76b56b7a37c9fcc49be3bd034dd6477e.js"></script>

16. 遇到一些特別情況時,善用 comment 以便未來工作.寫 comment 時不用一行一行寫 (inline),請在適當地方用一塊區域來說明程式的運作目的.
<script src="https://gist.github.com/yihcheng/bfb64ea3cbc7d9249a360104249917eb.js"></script>

17. 對於 public method 都該檢查輸入參數是否符合你的預期.如:
<script src="https://gist.github.com/yihcheng/912002ca883f2bb82f81aad9b4496848.js"></script>

18. 該有括號的地方都打上去,別偷懶不打.如:
<script src="https://gist.github.com/yihcheng/131c517466866818ed2a1c104dd9e022.js"></script>

19. public method, property 等一律使用 PascalCasing ,而非 public 改用 camelCasing. 如:
public int GetMyReward();
public string MyName {get; set;}

private int getMyReward();
private string _myName;

20. 為 variable, property, method, class, interface 取名是件重要的事情.取一個完整的名稱,別用簡稱.如 使用 GetConsoleWindow() 而不用 GetConWin()

21. Namespace 命名以 PascalCasing 方式,名字應以名詞為主.如: System.Security

22. Class 命名以 PascalCasing 方式,名字應以名詞為主,如 StreamReader, DataCollector 等

23. Interface 命名以 PascalCasing 方式,通常在最前面加上 I ,名字應以名詞為主,如 IList, IReadOnlyCollection 等

24. Variable 與 parameter 命名以 camelCasing 方式,名字應以名詞為主,如:
public int ToInt32(string itemValue, int itemCount);

25. Method 命名以 PascalCasing 方式,名字應以動詞為主,如:
ToString(), Write(), ExecuteCommand();

26. Property 命名以 PascalCasing 方式,名字應以名詞為主,如:
public int Length { get; set; }

27. Event 命名以 PascalCasing 方式,名字應以動詞為主,如:
public event EventHandler ObjectChanged;

28. Public field 命名以 PascalCasing 方式,而 private field 以 camelCasing 方式,名字應以名詞為主,如:
public TimeSpan Timeout;
private TimeSpace _firstTimeout;

29. Enum 命名以 PascalCasing 方式,名字應以名詞為主,如:
FileMode 
{
Open,
Create,
Append,
}

30. Class 的名字通常和對應的 Interface 名字有關係,如:
<script src="https://gist.github.com/yihcheng/f5e65af8fc71446fdfc633b968cbeea6.js"></script>

31. 大部份的情況下, Enum 最好要加上 None 元素用來代表該 enum 用不到的情況下,並且將它設定為 0.如:
<script src="https://gist.github.com/yihcheng/ae0d5b15f1efb9056946f07b0079752a.js"></script>

32. Enum 若是 Flags 的屬性,別在名字加上 Flag,可用複數名詞來代表. 如
<script src="https://gist.github.com/yihcheng/9b3bd8830efb7be4b45a9aa9ff6e9e26.js"></script>

33. 為了便於閱讀和 code review,將關聯性高的 class 成員寫在相鄰的位置.

34. 一個檔案最好只有一個 class,並且檔案名字與 class 名字一致.若是 nested class 或是一些簡單且臨時在程式間用的 class 除外.

35. 所有的宣告都應加上 public, private, internal.若是只在一個 method 裡使用的臨時變數可以忽略.

36. 在 switch 裡要加上 default: ,即使它的內容是空的,也要加上.

37. 要產生準確的 exception.如,當你檢查參數是否為空時,就該用 ArgumentNullException 而不是用 ArgumentException.

38. 產生 exception 時,要產生有意義且可知道錯誤細節與解決方法的 exception,不該把許多可能會發生錯誤的 code 放在一個 try block 裡,然後只有一個 catch  (Exception ex).

39. 當處理商業邏輯時,很可能找不到適合的 exception type,此時應自行建立自訂的 exception.

40. Exception 產生時不應該影響程式的正常流程,並且 exception 產生時需被處理,例如 log.

41. 在 try-catch 裡若使用到一些資源,可在 finally block 裡釋放.如:
<script src="https://gist.github.com/yihcheng/f21f0e276d3110293ba002d7ddc7cf42.js"></script>

42. 在你的程式裡,在最上層 (可能是在 UI 層) 要能抓住所有可能會發生的 exception,做好相對應的處理或畫面顯示,避免程式中斷.

43. 在 catch block 裡應該都為該 exception 做些處理動作,而不是空白 (什麼事都不做).除非,程式裡已有邏輯在處理那段錯誤,如:
<script src="https://gist.github.com/yihcheng/96bec32a07d681cf29fd8cfe02f21e7d.js"></script>

44. 不用重覆拋出 exception.應該加一些錯誤處理機制並且只需要 throw 即可.如:
<script src="https://gist.github.com/yihcheng/d5682c60f631ce42e545b6599ecb7ba0.js"></script>

45. 對於有實做 IDisposable 的  class 應善用 using 來確保資源能適時適當地被釋放.如:
<script src="https://gist.github.com/yihcheng/d5b48faacb61e4f0ade8dd2bb5495bd3.js"></script>

別在 try-finally 裡使用 Dispose,如:
<script src="https://gist.github.com/yihcheng/8e122eaccd809b3ac2590a33111a5efd.js"></script>

46. 盡可能使用 generic collection 來儲存物件,例如使用 List<t> 而不用 ArrayList.

47. 所有的 public class, property, method 都應該要有 XML 文件註解,如:
<script src="https://gist.github.com/yihcheng/e46ddd6f71f510da6c7d7cb405e75d6f.js"></script>

48. 有適合的語法糖時就使用,以便於閱讀.如:
<script src="https://gist.github.com/yihcheng/d92d41ef04ee8f7e37541faf16e141f2.js"></script>

49. 在不影響程式邏輯的情況下,應把程式碼編排縮排量減少.如:
<script src="https://gist.github.com/yihcheng/74ec46974bcd6afa64b137a82b976ae3.js"></script>

50. Public method 的參數應盡量使用 interface,如:
<script src="https://gist.github.com/yihcheng/dc5dfed1f376b83d908c02bb104eaab6.js"></script>

51. 若無特別需求,為每個非同步呼叫加上 ConfigureAwait(false)

52. 若無特別需求,不自行建立 Thead ,一律使用 Task 讓 .Net threadpool 來管理相關資源.

53. 多個 Task 有執行順序時,善用 ContinueWith(), WhenAll(), 或 WhenAny(),而不該用 Task.Delay.

轉貼自 http://www.woolycsnote.tw/2018/11/80-coding-guideline-c.html