# C# 開發規範 ## 一、命名規範 1. 凡舉空間域、介面、類別、列舉請以 Pascal Case 命名。 ```C# namespace VirtualLibrary.Service { public interface IFooService { ... } } ``` 2. 凡舉方法、屬性、請以 Camel Case 命名。 ```C# public class Square { private float width; private float height; public Square(float width, float height) { this.width = width; this.height = height; } public float calculateArea() { return width * height; } } ``` 3. 應該避免無意義的變數命名,好的命名一目了然,無需額外的註解解釋。 * 糟糕的範例 ```C# int d1; // 交易起日 int d2; // 交易迄日 ``` * 優良的範例 ```C# int startDate; // 借閱起日 int endtDate; // 借閱迄日 ``` 4. 避免兩個命名太長且太相像 * 糟糕的範例 ```C# public class XYZControllerForEfficientHandlingOfStrings { .. } public class XYZControllerForEfficientStorageOfStrings { .. } ``` * 優良的範例 ```C# public class StringHandler { .. } public class StringStorageCotainer { .. } ``` 5. 代表同樣意思、同一件事、同一個東西時,命名應該一致 6. 請避免以數字命名變數 * 糟糕的範例 ```C# public static void swapCharArray(char a1[], char a2[]) { for(int i = 0 ; i <= a1.length ; i++) { int temp = a1[i]; a1[i] = a2[i]; a2[i] = temp; } } ``` * 優良的範例 ```C# public static void swapCharArray(char sourceArray[], char targetArray[]) { for(int i = 0 ; i <= sourceArray.length ; i++) { int temp = sourceArray[i]; sourceArray[i] = targetArray[i]; targetArray[i] = temp; } } ``` 7. 避免用不同字眼代表同一件事,例如 info 與 data ,或是 a, an, the 的差異。 8. 類別名稱請使用單數名詞命名。 ```C# public class Book { .. } public class Member { .. } public class Tag { .. } public class BlackList { .. } ``` 9. 方法名稱請使用動詞作為開頭命名。 ```C# namespace VirtualLibrary.Service { public class LendingBookService { /// <summary> /// 出借書籍 /// </summary> /// <param name="LendingBookInfoModel">出借書籍輸入物件</param> /// <returns>LendingBookDto 借書籍輸出物件</returns> public LendingBookDto lendBook(LendingBookInfoModel LendingBookInfoModel) { ... } /// <summary> /// 查詢出借書籍申請進度 /// </summary> /// <param name="LendingBookInfoModel">查詢出借書籍申請輸入物件</param> /// <returns>LendingBookDto 查詢出借書籍申請輸出物件</returns> public InuqireBookDto inquireApplicationStatus(InuqireBookInfoModel InuqireBookInfoModel) { ... } /// <summary> /// 取消出借書籍 /// </summary> /// <param name="CancelBookLendingInfoModel">取消出借書籍輸入物件/param> /// <returns>CancelBookLendingDto 取消出借書籍輸出物件</returns> public CancelBookLendingDto cancelBookLending(CancelBookLendingInfoModel CancelBookLendingInfoModel) { ... } } } ``` ### 三層式架構命名規範 1. 展示層(Presentation Layer)為 Web API 接口,命名請以 Controller 為結尾。 2. 展示層(Presentation Layer)輸入物件之命名請以 Param 為結尾。 3. 展示層(Presentation Layer)輸出物件之命名請以 ViewModel 為結尾。 ```C# namespace VirtualLibrary.Controller { public class BookBorrowingController { /// <summary> /// 借閱書籍 /// </summary> /// <param name="bookBorrowingParam">借閱書籍輸入物件</param> /// <returns>BookBorrowingViewModel 借閱書籍輸出物件</returns> public BookBorrowingViewModel borrowBook(BookBorrowingParam bookBorrowingParam) { ... } } } ``` 4. 商業邏輯層(Business Layer)負責處理商業邏輯,命名請以 Service 為結尾。 5. 商業邏輯層(Business Layer)輸入物件之命名請以 InfoModel 為結尾。 6. 商業邏輯層(Business Layer)輸出物件之命名請以 Dto 為結尾。 ```C# namespace VirtualLibrary.Service { public class BookBorrowingService { /// <summary> /// 借閱書籍 /// </summary> /// <param name="bookBorrowingInfoModel">借閱書籍輸入物件</param> /// <returns>BookBorrowingViewModel 借閱書籍輸出物件</returns> public BookBorrowingDto borrowBook(BookBorrowingInfoModel bookBorrowingInfoModel) { ... } } } ``` 7. 資料存取層(Data Layer)負責處理商業邏輯,命名請以 Repository 為結尾。 8. 資料存取層(Data Layer)輸入物件之命名請以 Condition 為結尾。 9. 資料存取層(Data Layer)輸出物件之命名請以 Model 為結尾。 ```C# namespace VirtualLibrary.Repository { public class BookBorrowingRepository { /// <summary> /// 借閱書籍 /// </summary> /// <param name="bookBorrowingInfoModel">借閱書籍輸入物件</param> /// <returns>BookBorrowingViewModel 借閱書籍輸出物件</returns> public BookModel borrowBook(BookBorrowingCondition bookBorrowingCondition) { ... } } } ``` ## 二、Coding Style 1. 一個檔案只包含一個namespace,只包含一個class,class名稱與檔名符合。 ```C# namespace VirtualLibrary.Service { public interface IFooService { ... } } ``` 2. Project名稱與dll名稱符合。 3. 大括號各自獨立新的一行,判斷式都一定要加上 { },嚴格禁止省略。 * 糟糕的範例 ```C# public int fooFunction(bool variable) { if(variable) return 1; return 0; } ``` * 優良的範例 ```C# public int barFunction(bool variable) { if(variable) { return 1; } return 0; } ``` 4. 宣告變數時,每個變數都獨立一行。 * 糟糕的範例 ```C# public class FooClass { private int a = b = c = 0; } ``` * 優良的範例 ```C# public class BarClass { private int a = 0; private int b = 0; private int c = 0; } ``` 5. Using namespace時,原生的namespace放最上面,最好都經過IDE的排序功能。 6. Visibility越高的,放越前面。外面看的到的,通常命名也都是Pascal。通常public放最上面,protected次之,internal次之,private放下面。建構式放上面,屬性放上面。 ```C# public class FooClass { public char character; protected float floatNumber; internal int integer; private string str; } 7. Interface 與其實作應分開成 2 個不同的檔案。 8. Namespace的階層與folder的階層一致。 9. 請使用 4 個空白對程式碼區塊進行縮排。 ### 註解 1. 註解使用//或///,不用/*...*/,除非是版權宣告。 2. 禁止使用"flowbox" ```C# // ************************************** // Comment block // ************************************** ``` 3. inline comment 只用來表示:假設、issues、演算法提示。 ```C# public class BarClass { public long getNextSerialNumber() { long serialNumber = calculateNextSerialNo(); // 使用雪花演算法取得號碼 } } ``` 4. 善用 TODO、UNDONE、HACK 等工作清單的關鍵字。 ```C# namespace VirtualLibrary.Service { public class LendingBookService { /// TODO 待 SA 確認商業邏輯再進行開發 public CancelBookLendingDto cancelBookLending(CancelBookLendingInfoModel CancelBookLendingInfoModel) { return null; } } } ``` 5. 我們有做版本控制,程式碼沒有打算開發被呼叫,請不要註解,移除片段即可,後續有要拿來使用,再倒版回去。 * 糟糕的範例 ```C# namespace VirtualLibrary.Service { public class LendingBookService { /// <summary> /// 查詢出借書籍申請進度 /// </summary> /// <param name="LendingBookInfoModel">查詢出借書籍申請輸入 bo</param> /// <returns>LendingBookDto 查詢出借書籍申請輸出 bo</returns> /// public InuqireBookDto inquireApplicationStatus(InuqireBookInfoModel InuqireBookInfoModel) /// { /// ... ///} } } ``` * 優良的範例 ```C# namespace VirtualLibrary.Service { public class LendingBookService { } } ``` 6. 請在每一個類別、方法上面加上註解,方便大家知道功能用法、及用途。 ```C# namespace VirtualLibrary.Service { <summary> 出借書籍 Service 實作 </summary> public class LendingBookService : ILendingBookService { <summary> 查詢出借書籍申請進度 </summary> <param name="LendingBookInfoModel">查詢出借書籍申請輸入物件</param> <returns>LendingBookDto 查詢出借書籍申請輸出物件</returns> public InuqireBookDto inquireApplicationStatus(InuqireBookInfoModel InuqireBookInfoModel) { ... } } } ``` ## 三、Language Usage 1. 宣告時要加上access modifier的宣告,而不使用默認的值。 * 糟糕的範例 ```C# int variable = 0; ``` * 優良的範例 ```C# private int variable = 0; ``` 2. 根據 Law of Demeter,宣告 access modifier 時要謹慎,原則為:外面要使用此物件,能看到的東西最少,但又缺一不可。可見度範圍越小越好。 3. 宣告值的範圍,越小越好。可以用 int 就不用 long,可以用 double ,就不用 decimal,避免浪費位元數。 * 糟糕的範例 ```C# long serialNumber = 1; ``` * 優良的範例 ```C# int serialNumber = 1; ``` 4. 因爲 IEEE754,浮點數計算會有誤差,需高精準度或算金額請用 decimal。 * 糟糕的範例 ```C# double money = 0; ``` * 優良的範例 ```C# decimal money = 0; ``` 5. 宣告成 constants 的應該是簡單的型別,複雜的型別應使用 readonly 或 static readonly。 * 糟糕的範例 ```C# public class Circle { private float radius; private float PI = 3.14; public Circle(float radius) { this.radius = radius; } public float ChangeYear() { return radius * radius * PI; } } ``` * 優良的範例 ```C# public class Circle { private float radius; private readonly float PI = 3.14; public Circle(float radius) { this.radius = radius; } public float ChangeYear() { return radius * radius * PI; } } ``` 7. 用 as 轉型並檢查是否為 null 防呆,而避免使用強轉型。用 as 轉型 + 判斷 null,再使用轉型後的物件,這樣的效率,比用 is 判斷型別,再強轉型,再使用轉型後的物件好,那是因為可以減少至少一次類別相容性檢查,多多少少能提昇一些效能。 * 糟糕的範例 ```C# object obj = "string"; if(obj is string) // 第一次類別相容性檢查 { string str = (string) obj; // 第二次類別相容性檢查 Console.WriteLine(str); } else { Console.WriteLine("Conversion Failed."); } ``` * 優良的範例 ```C# object obj = "string"; string str = obj as string; // 第一次類別相容性檢查 if(str != null) { Console.WriteLine(str); } else { Console.WriteLine("Conversion Failed."); } ``` 8. 避免 boxing 與 unboxing 的發生,會損耗記憶體,造成變數在記憶體的 Stack 與 Heap 之間多次搬移。 * 糟糕的範例 ```C# int remainingDays = 3; Console.WriteLine($"書籍借閱剩餘天數:{remainingDays} 天"); ``` ```C# public static string Format(string format, params object[] args) => args != null ? string.FormatHelper((IFormatProvider) null, format, new ParamsArray(args)) : throw new ArgumentNullException(format == null ? nameof (format) : nameof (args)); ``` * 優良的範例 ```C# int remainingDays = 3; Console.WriteLine($"書籍借閱剩餘天數:{remainingDays.toString()} 天"); ``` ```C# public static string Format(string format, params object[] args) => args != null ? string.FormatHelper((IFormatProvider) null, format, new ParamsArray(args)) : throw new ArgumentNullException(format == null ? nameof (format) : nameof (args)); ``` 9. 善用 String.Format 來呈現字串的pattern,請不要用 + 運算子來串字串,其背後原理是每一次準備更大的記憶體空間,將原本字串複製到這個空間,反覆執行直到字串串完。 * 糟糕的範例 ```C# string myBook = "book"; string myPencil = "pencil"; string FormatString = "This is a " + myBook + ", not a" + myPencil; Console.WriteLine(FormatString); ``` * 優良的範例 ```C# string myBook = "book"; string myPencil = "pencil"; string FormatString = String.Format("This is a {0}, not a {1}", myBook, myPencil); Console.WriteLine(FormatString); ``` 10. 善用 StringBuilder 來連結動態的字串。 * 糟糕的範例 ```C# string str = "Hello" + "World!"; Console.WriteLine(str); ``` * 優良的範例 ```C# StringBuilder sbBuilder = new StringBuilder("Hello"); string str = sbBuilder.Append(" World!").ToString(); Console.WriteLine(str); ``` 11. 判斷字串是否為空字串,避免用 == string.Empty 或 == "",而使用.Length==0,或是 string.IsNullOrEmpty。 * 糟糕的範例 ```C# string str = ""; if(str == string.Empty) { ... } ``` * 優良的範例 ```C# string str = ""; if(str == string.IsNullOrEmpty) { ... } ``` ## 四、流程控制 1. 避免遞迴,造成無窮迴圈,而改用 loop。 * 糟糕的範例 ```C# public int fibonacci(int number) { if(number <= 1) { return number; } return fibonacci(n - 1) + fibonacci(n - 2); } ``` * 優良的範例 ```C# public int fibonacci(int number) { int[] fib = new int[number]; fib[0] = 0; fib[1] = 1; int i; for(i = 2; i < number; i++) { fib[i] = fib[i - 1] + fib[i - 2]; } } ``` 2. 在 foreach 中,不要去異動集合範圍,例如在 loop 中移除可列舉元素,造成迭代器異常。 * 糟糕的範例 ```C# List<BookDataModel> books = BookRepositroy.searchBookByAuthor("三毛"); foreach(book in books) { if(book.bookName == "撒哈拉沙漠的故事") { books.Remove(book); } } ``` * 優良的範例 ```C# List<BookDataModel> books = BookRepositroy.searchBookByAuthor("三毛") .Where(book => book.bookName != "撒哈拉沙漠的故事") .ToList(); ``` 3. 只在很單純的情況下,使用條件運算子(三元運算子)。 * 糟糕的範例 ```C# public bool fooFunction() { int idx = 2; if(idx == 2) { return true; } return false; } ``` * 優良的範例 ```C# public bool fooFunction() { int idx = 2; return idx == 2 ? true : false; } ``` ```C# public bool fooFunction() { int idx = 2; return idx == 2; } ``` 4. 不在判斷式裡面assign變數值。 * 糟糕的範例 ```C# int i = 0; if((i=2) == 2) { ... } ``` * 優良的範例 ```C# int i = 2; if(i == 2) { ... } ``` 5. 判斷式若為判斷某一個 bool 變數,則不需要再用 ==true 或 ==false,因為bool值就代表該判斷式的意義。 * 糟糕的範例 ```C# bool flag = true; if(flag == true) { ... } else { ... } ``` * 優良的範例 ```C# bool flag = true; if(flag) { ... } else { ... } ``` ## 五、流程控制 1. 避免使用 try/catch 來當 if/else 用,try/catch 應該要能明確捕捉特定的 exception。嚴禁 try/catch 捕捉到錯誤後,完全沒處理。 * 糟糕的範例 ``` catch(Exception ex) { // do nothing } ``` * 優良的範例 ``` catch(Exception ex) { Log(ex); throw; } ``` 2. 嚴禁在 try/catch 的 catch 區塊中,再使用 try/catch。 * 糟糕的範例 ``` try { ... try { ... } catch(Exception ex) { ... } } catch(Exception ex) { ... } ``` * 優良的範例 ``` try { ... } catch(Exception ex) { ... } ``` 3. 避免re-throw exception。 * 糟糕的範例 ``` catch(Exception ex) { Log(ex); throw ex; } ``` * 優良的範例 ``` catch(Exception ex) { Log(ex); throw; } ```