# 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;
}
```