C# 程式設計指南
前不久在 Github 上看見了一位大牛建立一個倉庫:CSharpCodingGuidelines,開啟之後看了一下 readme.md
相關描述,感覺應該很不錯,於是就 clone 到本地拜讀一下,這裡列一些自己的筆記,方便日後回顧。
基本原則
- Astonishment 原則:你的程式碼應該儘量做到讓每一個人都能理解。任何人都有寫出讓機器認識的程式碼,但是並不是每個人都能寫出讓人認識的程式碼;
- Kiss 原則:類似 Python 之禪 裡面說的那樣,簡單勝於複雜;
- YAGNI 原則:程式碼儘量能做到可擴充套件,但請不要過度重構,因為你不能預知未來;
- DRY 原則:不要重複造輪子,除非你有時間或者你造的輪子會比別人的優秀;
- 物件導向程式設計原則:繼承、封裝、多型、抽象;
類設計指南
- 一個類/介面應該只有一個用途,要符 合單一職責 原則;
- 只建立返回有用物件的建構函式,當建構函式的引數超過 3 的話,你就應該考慮你的類設計是否過於臃腫;
- 介面應該短小精悍,其定義要明確清晰地傳達出其具有的行為;
- 如果一種行為有多種實現,請考慮使用介面而不是基類;
- 儘量使用介面將類進行彼此解耦;
- 避免使用靜態類;
- 不要使用 new 關鍵字來禁止編譯器顯示相關警告;
public class Book
{
public virtual void Print()
{
Console.WriteLine("Printing Book");
}
}
public class PocketBook : Book
{
public new void Print()
{
Console.WriteLine("Printing PocketBook");
}
}
class Program
{
static void Main(string[] args)
{
PocketBook pocketBook = new PocketBook();
pocketBook.Print();
((Book)pocketBook).Print();
Console.ReadKey();
}
}
在上述程式碼段中,我們建立一個基類 book,並定義了一個 Print() 方法,接著我們建立一個子類 PocketBook,並通過 new 關鍵字來重寫基類方法。在專案複雜的情況下,使用這種方式將導致我們不能準確預測是子類呼叫還是父類呼叫,使程式碼複雜度提升。
- 應該可以將派生類當作基類物件來處理;
- 不要引用基類的派生類;
- 避免暴露一個物件依賴的其它物件;
- 避免雙向依賴;
- 類應該有狀態和行為;
- 類應該保護其內部狀態的一致性;
屬性成員設計指南
- 允許按任意順序設定屬性;
- 使用方法而不是屬性;
- 不要使用相互排斥的屬性;
- 屬性、方法或者本地方法只應該做一件事情;
- 不要通過靜態成員公開有狀態的物件;
- 用 IEnumerable
或者 ICollection 來代替具體的集合物件作為返回值; - 如果屬性、引數和返回值是字串或者集合型別的話,則永遠不應該為空;
- 儘可能地定義具體的引數;
- 考慮使用特定域的值型別而不是基元;
其他設計指南
- 丟擲異常而不是返回某種型別的狀態值;
- 提供完整而有意義的異常資訊;
- 丟擲適當的最具體的異常;
- 不要通過 try – catch 方式隱藏異常;
- 正確處理非同步程式碼中的異常;
- 呼叫事件委託前先判斷是否為空;
event EventHandler<string> Notify;
protected virtual void OnNotify(string args)
{
Notify?.Invoke(this, args);
}
- 使用受保護的虛方法來觸發每個事件;
- 考慮新增屬性變化事件;
- 當觸發事件時要確保 sender != nulll;
- 如果合適的話,請考慮使用泛型約束;
class SomeClass
{
}
/// <summary>
/// 不推薦
/// </summary>
class MyClass1
{
void SomeMethod<T>(T t)
{
object temp = t;
SomeClass obj = (SomeClass)temp;
}
}
/// <summary>
/// 推薦
/// </summary>
class MyClass2
{
void SomeMethod<T>(T t) where T :SomeClass
{
SomeClass obj = t;
}
}
- 在返回 LINQ 表示式之前計算它的結果;
- 如果不是必須,不要使用 this 和 base 關鍵字;
可維護性指南
- 方法內部的程式碼段儘量不要超過 7 行;
- 確保所有成員私有,類的型別預設為為 internal sealed
- 避免雙重條件;
- 在其包含的名稱空間內命名程式集;
- 將原始檔命名為它所包含的型別;
- 將原始檔的內容限制為一種型別;
- 將不同的邏輯函式放到同一個部分類中;
- 在使用一個型別時,使用 using 關鍵字匯入需要的名稱空間,而不是型別的完整空間標識;
- 不要使用魔法數;
- 只有當型別顯而易見時才使用 var 關鍵字;
- 定義變數時儘可能地初始化;
- 在相互獨立的程式碼段中定義臨時變數;
- 若物件有集合需要初始化的話在進行物件初始化的同時進行集合初始化;
- 不要顯式進行 bool 值的比較;
- 避免巢狀迴圈;
- 在使用 if、else、do、while、for、foreach、case 的同時使用
{}
; - 在 switch case 程式碼段中新增 default 邏輯;
- 在所有的 if 、else if 後再新增 else;
- 避免使用多個返回值;
- 考慮使用簡單的條件語句代替 if else;
- 封裝屬性、方法或區域性函式中的複雜表示式;
- 再合適的情況下嘗試過載方法;
- 使用可選引數來代替過載;
- 避免使用命名引數;
- 避免定義超過3個引數的簽名;
- 避免函式簽名為布林型別;
- 不要將引數作為臨時變數使用;
- 將模式作為操作;
- 不要註釋程式碼;
命名指南
- 不要在變數、引數和型別成員中包含數字;
- 不要在欄位新增字首;
- 不要使用縮寫;
- 成員、引數和變數定義要根據它們代表的意義;
- 使用名詞、名詞短語或者形容詞來定義型別;
- 使用描述性名稱命名泛型引數;
- 在類成員中不要重複定義和類相同的名稱;
- 成員定義可參考 .Net Framework 的定義方式;
- 避免使用可能被誤解的段名稱或欄位;
- 正確定義屬性;
- 在命名方法或區域性函式時使用謂詞或謂詞物件;
- 使用名稱、層、謂詞和功能申明名稱空間;
- 使用動詞或動詞字首來定義事件;
- 使用 ing 和 end 字尾來表達事件預處理和傳送事件;
- 使用 on 字首來定義事件處理程式;
- 使用 Async 或者 TaskAsync 來標識非同步方法;
效能指南
- 使用 Any() 判斷 IEnumerable
是否為空 ; - 僅對低密集型活動使用非同步;
- 對於 CPU密集型使用 Task.Run;
- 避免同時將 async/await 和 Task.Wait 混合使用;
- 避免 async/await 在單執行緒環境下出現死鎖;
框架指南
- 使用 C# 型別 別名而不是系量進行顯式呼叫;
- 不要硬編碼;統名稱空間中的型別;
- 盡
- 使用最高警告級別編譯程式碼;
- 對於簡單的表示式避免使用 LINQ;
- 使用 lambda 表示式來代替匿名函式;
- 只用在使用動態物件時才使用 dynamic 關鍵字;
- 支援非同步/等待任務延續;
文件指南
- 使用美式英語來編寫相關文件;
- 文件中的程式碼部分要保證完整性;
- 與其他開發人員一起編寫 xml 文件;
- 編寫 MSDN 風格的技術文件;
- 避免內聯註釋;
- 註釋值應該用來解釋複雜的演算法或討論;
- 不要使用註釋來跟蹤要在以後完成的工作;
佈局指南
- 使用常規佈局;
- 根據公式要求進行名稱空間的約束;
- 將成員置於定義良好的順序中;
- 謹慎使用
#region
; - 適當使用表現健全的成員;