初探 Clean Code

2023-01-25

筆記關於 Clean Code 的設計實務之道。

logo

說明

Clean Code 強調的是程式碼的可讀性 (Readability),因為任何人都可以寫出電腦看得懂 (Compiler & Interpreter 可以通過) 的程式碼,但卻很難寫出讓未來的自己或其他人看得懂的程式碼。

筆記源自 Cory House 在 Pluralsight 的 Clean Coding Principles in C#

進階參考資源包含 Steve McConnell 的 Code Complete、Robert C.Martin 的 Clean Code 以及 Andrew Hunt 與 David Thomas 的 The Pragmatic Programmer

GitHub

根據 Cory House 指出,Clean Code 有三個原則,分別是用正確的工具處理工作、讓程式碼呈現資訊而非噪音 (signal to noise ratio) 以及讓程式碼本身就是文件 (Self-Documenting)。

用正確的工具處理工作

注意程式碼的邊界,不要互相干涉以及混雜再一起,例如 MVC 專案當中 C#、HTML、CSS 以及 JS 各司其職,不要越俎代庖 (避免用一種程式語言去組合另一種程式語言),儘量用 Convention over Configuration 以及大家有共識的方式去處理問題。

太過複雜的 Regex 不要用,相較於硬體資源上的效率,可讀性仍是處在第一位重要。

讓程式碼呈現資訊而非噪音

訊號 (signal) 的特質包含 TED (Terse, Expressive, Does one thing);相對地噪音 (noise) 則是具有高 cyclomatic complexity、浮誇的縮排、不好的命名、重複的程式碼、冗長的單一函式以及巨大的類別等。

讓程式碼本身就是文件

透過程式碼排版的技巧來增進可讀性,讓程式碼的縮排 (Indent)與層次 (Layer)等結構本身向閱讀者傳達訊息,而非透過註解來傳達。

Why Clean Code

在程式碼的開發過程中,反覆閱讀程式碼的時間勝過撰寫程式碼,因此程式碼的可讀性特別重要,而好閱讀的程式碼能夠為需求改變或功能擴充帶來更快的實踐。

原則與技巧

Naming

類別的命名,使用明確的名詞,命名要能夠反映出單一責任與高內聚的特性,例如 User, Account, QueryBuilder, ProductRepository。

避免命名出現 And, If, Or,如果有這樣的命名表示類別的責任太多了,應該再切割為更小的類別

命名必須要與信任感,不要有命名預期外的狀況 (例如讀取的功能卻會造成資料寫入)

布林值的命名技巧: isOpem, isActive, loggedIn

Conditoinals

適當的簡化條件的布林值,可以提升程式碼可讀性:

if (loggedIn == ture)

✔️
if (loggedIn)
bool doSomeThing;
if (weather == Weather.Sunny)
  doSomeThing = true;
else
  doSomeThing = false;

✔️
bool doSomeThing = weather == Weather.Sunny;

在條件撰寫上避免使用雙重否定。

適當使用三元運算子 (Ternary) 增進可讀性。

使用強型別 (Enum) 進行比較,減少使用字串比較。

避免使用 Magic Number (使用有意義命名的變數或者 Enum 替代)。

如果條件的判斷式有多項,另外抽成有意義命名的變數替代,增進可讀性。

if (employee.Age > 65 && employee.IsRetired)

✔️
bool eligibleForPension = employee.Age > 65 && employee.IsRetired;
if (eligibleForPension)

如果複雜到變數 hold 不住,就抽出封裝成 Method。

使用多形 (Polymorphism) 取代 Switch 分歧。

使用陳述性的方式處理邏輯 (Linq 的 Predicate)。

提高程式碼的可維護性,使用 Table Driven 將資料儲存在資料庫當中,而非直接在程式碼的邏輯中處理。

Clean Method

考慮將程式碼抽為 Method 或 Function 的時機,包含發現程式碼出現相似的規律 (Pattern) 以及重複的程式碼時;出現箭頭形狀的縮排 (Arrow Code) 造成程式碼可讀性下降時;程式碼的用途不明確時。

此外在處理 Arrow Code 時,除了抽為 Method,也可以利用 Fail Fast 的技巧,打破巢狀的 If 讓會報錯直接被處理。

public void RegisterUser(string username, string password)
{
  if (!string.isNullOrWhitespace(username))
  {
    if (!string.isNullOrWhitespace(password))
    {
      ...
    }
    else
    {
      throw new ArgumentException();
    }
    throw new ArgumentException();
  }
}

✔️
public void RegisterUser(string username, string password)
{
  if (string.isNullOrWhitespace(username))
  {
    throw new ArgumentException();
  }
  if (string.isNullOrWhitespace(password))
  {
    throw new ArgumentException();
  }
}

而當程式碼的用途不明確時,也應該抽為 Method,例如:

if ((fileExtension == ".jpg" || fileExtension == ".png" ) && isFileExists)

✔️
private bool ValidaFileExists(string fileExtension, bool isFileExists)
{
  var validFileExtensions = new List<string>() {".jpg", ".png"};

  return validFileExtensions.Contains(fileExtension) && isFileExists;

}

💡 在 Method 內部的變數使用上,不要集中宣告在開頭,而是要緊鄰使用此內部變數的程式碼段落,讓閱讀上可以更容易識別變數的用途。

💡 如果 Method 的參數過多,可以將參數另外設計為類別 (Class) 提升可讀性。

💡 避免在 Method 加入 Flag 參數,這樣會讓單一 Method 負擔額外的工作,分開成不同的 Method。

Clean Class

類別的設計上,首重高內聚 (Cohesion),設計上要避免讓類別淪為龐雜的程式碼聚合,以提升類別程式碼的可讀性以及被重複使用的可能。(相對於類別過於龐雜的問題,很少發生類別被抱怨設計的太"迷你")。

在物件導向設計上,要避免 Primitive Obsession (偏好使用 int, double, string 等基本型別) 而忽略使用類別 (Class) 藉由封裝所帶來的可讀性提升。

Clean Comment

正:寫註解是為了提升程式碼的可讀性

反:不寫註解,讓程式碼表現意義則是提升程式碼的可讀性

合:原則上不寫註解,正確的使用註解以提升可讀性

💡 避免廢話類型的註解 (這個變數宣告為 int 並賦值)

💡 避免用註解表現程式碼用途,使變數、方法、類別的命名賦予用途

💡 移除註解掉的程式碼,讓版本控制工具來管理被移除的程式碼

💡 移除分隔程式碼段落的註解以及標註程式碼起始的註解

💡 移除作者資訊以及程式碼用途摘要

使用 Visual Studio 所支援的 //TODO 以及 //HACK,對於類別或者方法,使用 Summary

實作

檢驗指標

Maintainability Index

Cyclomatic

Code Smells

Arrow Code

  • Nested Ifs
  • Guard Clause
  • Indents

Comments

  • Repeat
  • Version Control

Mayfly Variables

  • Check dependency

Failing Fast

Table Driven

Tests before Refactoring

Avoid Abbreviation

  • Long descriptive name is accepted

TED Principles

  • Terse
  • Expressive
  • Doe one thing

Readability

  • Naming
  • Short & Simple
  • Linq Predicate

Extension Method over Loop Methods

Least Surprise Principle

參考資料

Clean Code concepts adapted for .NET/.NET Core