---
# System prepended metadata

title: '[OnGoing] 好程式的藝術，Clean Code Driven Design'
tags: [Growing]

---

<span style="font-size: 2.5em; font-weight: bold;">好程式的藝術</span>
<span style="font-size: 1.2em; color: gray;">Clean Code Driven Design</span>

> 「寫程式就像寫作，任何人都能寫出電腦懂的程式碼，但只有優秀的工程師能寫出人類懂的程式碼。」
> 
> 本文件是筆者閱讀 《Clean Code》 的筆記整理。希望能幫助讀者**快速理解**其重要性與精神，亦能作為已讀者的**快速複習手冊**。


### Why? 為什麼要寫好程式、什麼是爛程式的代價？

多數人剛開始學寫程式時，心中只有一個目標：**讓它動**
能跑、能出結果、能交差，似乎就已經完成任務了。
但當你進入真實世界的專案後，很快就會發現，「能動」只是程式生命的起點，而不是終點。
寫程式，更像是在撰寫一份會被長期閱讀、修改與擴充的文件，而不是一次性的作品。

#### 程式不是寫給電腦看的，是寫給人看的。

電腦對程式碼沒有情緒，也沒有理解成本。
只要語法正確，邏輯能跑，電腦就會乖乖執行。

但人不一樣。

根據經驗與研究，工程師花在「讀程式」上的時間，往往是「寫程式」的十倍以上。
當你要加新功能、修 bug、或只是理解一段舊程式時，你其實正在閱讀別人（或過去的自己）留下來的文字。

如果程式碼難讀，那每一次修改都是在痛苦中前進。
如果程式碼清楚，那撰寫新程式反而會變得更快。

這也是為什麼有人說：

> 任何人都能寫出電腦懂的程式碼，但只有優秀的工程師能寫出人類懂的程式碼。

#### **爛程式的代價，會隨時間放大**

有一個殘酷但真實的法則，叫做 **LeBlanc’s Law**：

> Later equals never
> 之後再整理，通常等於永遠不會整理。

一開始偷懶留下來的爛程式碼，未來幾乎不可能「等有空再修」。
因為專案只會越來越大、時程只會越來越趕，而爛程式碼會像債務一樣，不斷累積利息。

結果就是：

- 每加一個功能，風險都在上升
- 每修一個 bug，都可能引出新的 bug
- 工程師開始害怕改動程式碼，因為「不知道會壞哪裡」

最終，整個系統會變成一個誰都不敢碰的黑盒子。

#### **好程式，其實是在降低「認知成本」**

Clean Code 並不是追求炫技、也不是寫給面試官看的漂亮程式。
它真正的價值，在於降低理解的成本。

好的程式碼，會讓你在閱讀時：
- 幾乎不用猜作者想幹嘛
- 名稱本身就表達了意圖
- 邏輯直截了當，而不是層層推理
- 每一段程式都只負責一件清楚的事

**當你看到程式碼時，執行結果會和你腦中預期的幾乎一致。**
那一刻，你會感覺這門程式語言彷彿就是為了解決這個問題而存在的。
這種「讀起來順」的感覺，並不是偶然，而是刻意設計的結果。

#### 好程式碼，是一種職業態度

Clean Code 的背後，其實不是技巧，而是一種態度。
它代表著：
- 你尊重下一個要維護這份程式碼的人
- 你願意為未來的自己省下時間
- 你相信軟體是會被長期使用與演進的

這也是為什麼會有「童子軍規則」這個比喻：

> 離開營地時，讓營地比來的時候更乾淨一點。

寫好程式，不一定代表一次到位、完美無缺。
但它代表你願意在每一次修改時，讓程式碼變得稍微好一點點。

而這種持續性的改善，本身就是一種專業精神。


### What？ 那什麼能被稱作 Clean Code？

Clean Code 並不是一種特定的寫法，也不是遵守某幾條規則就能自動達成的狀態。
它更像是一種 **「讀起來毫不費力」的感覺**。

當你打開一份程式碼，不需要來回跳轉、不需要在腦中模擬太久，就能大致理解它在做什麼，這通常就是 Clean Code 的開始。

#### Clean Code 的核心，不是聰明，而是清楚

許多人會誤以為好程式碼是「看起來很厲害的程式碼」。
充滿技巧、濃縮到極限、用最少的行數完成最多的事。

但 Clean Code 剛好相反。

它追求的是：

- 邏輯直截了當，而不是機巧取勝
- 抽象清楚，而不是細節堆疊
- 意圖明確，而不是需要猜測

好的程式碼，不會掩蓋設計者的想法，反而會把意圖攤在檯面上。
你不是在解謎，而是在閱讀一段有條理的敘述。

#### 程式碼應該只做好「它該做的那件事」

在 Clean Code 的世界裡，「只做一件事」不是口號，而是判斷設計好壞的重要指標。

無論是 class、function，甚至是一小段邏輯，都應該有一個清楚的責任範圍。
當一個 function 同時處理驗證、轉換、計算、存取資料，那它看似方便，實際上卻是在累積未來的混亂。

一個簡單的判斷方式是：

> 如果你能從一個 function 中，再抽出另一個有意義的 function，
> 那通常代表它已經做超過一件事了。

Clean Code 偏好把事情拆開，讓每個單位都小、單純、可理解。

#### 好的名稱，本身就是最好的註解

Clean Code 很少需要解釋性註解，因為名稱本身就已經說明了一切。

variable 、 function 、 class 的命名，不是為了節省鍵盤敲擊次數，而是為了傳達意圖。
一個好的名稱，會讓你在使用它的時候，幾乎不用再回頭確認定義。

當名稱夠清楚時：

- 程式碼會變得可預測
- 閱讀時會自然「順著念下去」
- 你不需要在腦中對照上下文來猜意思

Clean Code 的理想狀態，是讓程式碼看起來就像在描述問題本身，而不是在展示語言技巧。

#### Clean Code 是「可以被他人安心修改的程式碼」

一份真正乾淨的程式碼，並不專屬於原作者。

它應該能被他人閱讀、理解、修改，並在不破壞既有行為的前提下持續擴充。

因此，Clean Code 往往會伴隨 Test 存在。
不只是因為測試很「專業」，更是因為它讓改動變得安全。

當你知道改一行程式碼不會引發連鎖災難，你才有勇氣讓程式碼變得更好。

### How? 那我該怎麼寫出像這樣的好程式碼呢？

#### 把名字取好

Clean Code 的實作，幾乎都是從命名開始的。

名稱應該代表意圖，而不是型別、實作細節，或作者腦中的臨時縮寫。
當名稱取得好，程式碼本身就會變得可讀，甚至不需要額外註解。

好的命名通常具備幾個特徵：
- 能唸出來、能在對話中被使用
- 可以被搜尋，避免過短或過於泛用
- 在同一個概念上，使用同一個詞彙

e.g.(名稱是否能表達意圖)
```
// Bad
int d;
```
漂亮，看到 d，你根本不知道這是什麼東西。

```
// Good
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;
```
好的名稱，會直接告訴你「它是什麼」，而不是「你自己去猜」。

e.g.(能唸出來、能在對話中被使用)
```
// Bad
String genymdhms;
```
這能唸的出來，也算是種語文天才。

```
// Good
String generationTimestamp;
```
你在 debug 時就可以跟別人說
這個 bug 發生在 generationTimestamp 計算錯誤時，順順順。

e.g.(是否能被搜尋)
```
// Bad
for (int i = 0; i < 34; i++) {
    ...
}
```
當你在專案裡搜尋 i 或 34，會很難找到你要的那一段。

```
for (int userIndex = 0; userIndex < MAX_USERS; userIndex++) {
    ...
}
```
可以被搜尋的名稱，能讓你在大型專案中快速定位程式碼，

e.g.(在同一個概念上，使用同一個詞彙)
假設你在很多 class 裡面都有 Add 這個 method，這邊的 Add 都是**相加**或**相連**的概念
這時你要新增一個新的 class，他裡面有一個 method 想要將參數加進一個集合裡
這時就不該用 Add，而是該用 Insert 或是 Append 等新的 method 命名

別說雙關語，一致性非常重要。

e.g.(避免把型別資訊塞進名稱裡)
```
// Bad(有爭議，很多人是使用這種方式)
Interface: IShapeFactory
Impl: ShapeFactory
```
這個名稱強調的是「它是一個 interface」，
而不是「它的角色是什麼」。

```
// Good
Interface: ShapeFactory
Impl: ShapeFactoryImpl
```
如果真的要區分，應從實作下手

#### 類別與函式，應該自己說明自己是誰

class 與 Object，通常代表的是「事物」，所以命名應該用名詞。
當你發現自己用上 Manager、Processor、Data、Info 這類模糊的詞，往往意味著設計還不夠清楚。

function 和 method 則相反，它代表的是「行為」，命名應以動詞。
一個好的函式名稱，應該像一句動作描述，讓人一看就知道它會做什麼。

當類別與函式的名稱都站得住腳，程式碼會開始有「結構感」，而不是一堆零散的工具集合。

e.g.(避免使用模糊的 Class 名稱)
```
// Bad
class UserManager {
    void handle();
    void process();
}
```
會很難看懂這個 `class` 到底在管理什麼東西，通常 `Manager` 代表很多事都往這裡丟

```
// Good
class UserAuthenticator {
    boolean authenticate(User user);
}

class UserRepository {
    User findByUserId(UserId id);
}
```
好的 `Class` 名稱，會明確說出「它是什麼角色」，
而不是只告訴你「它在處理一些事情」。

e.g.(Function 名稱應該清楚描述「做了什麼」)
```
// Bad
void handle();
void doIt();
void process();
```
這類 function 名稱，幾乎沒有提供任何資訊，
讀者只能進去看實作，才能知道發生了什麼事。

```
// Good
void validateOrder(Order order);
void calculateTotalPrice(Order order);
void sendConfirmationEmail(Order order);
```
好的 function 名稱，本身就是一行說明文件。

e.g.(當命名正確時，結構會自然浮現)
```
// Bad
class DataProcessor {
    void processUser();
    void processOrder();
    void processPayment();
}
```
這是一個典型的「萬能類別」，
什麼都能放，卻什麼都不清楚。

```
// Good
class UserService {
    void registerUser();
}

class OrderService {
    void placeOrder();
}

class PaymentService {
    void processPayment();
}
```
當 `class` 與 `method` 的名稱都清楚時，
系統的結構會直接反映在檔案與目錄上，不需要額外說明。


#### 讓 function 小到無法再拆

> Clean Code 對 function 的要求，其實很嚴格：
> 短、小、只做一件事。

這裡的「一件事」，不是技術上的分類，而是抽象層級上的一致。
如果一個 function 同時在「做事」又在「描述怎麼做」，那它很可能混合了不同層次的責任。

> 一個實用的自我檢查方式是：
> 如果你能從一個 function 中，再抽出另一個有意義、好命名的 function，那通常代表原本的 function 太胖了。

#### 參數越少，理解越快

function 的參數數量，會直接影響閱讀與理解的難度。

理想情況下：
- 沒有參數最好
- 一個參數可以接受
- 兩個參數勉強
- 三個以上，除非真的有特殊的理由，否則幾乎都該重新設計

每多一個參數，讀者就必須在腦中多記一個前提條件。
尤其是 boolean 參數，它通常暗示著：這個 function 其實在做不只一件事。

與其用參數切換行為，不如把行為拆成不同的函式，讓名稱直接表達差異。

#### 明確的資料流與責任歸屬

> 如果一個 function 需要改變某個物件的狀態，那就讓那個物件自己負責改變，而不是把它當成輸出參數丟來丟去。

e.g.

```
AppendFooter(s);
```

這邊的 method 可能有兩種情境
1. s 接在某個東西的後方
2. 這個 method 會在 s 後面加上 footer

請看：

```
public void AppendFooter(StringBuilder report){
    ...
}
```

我們需要進去看 method 才能消除疑慮
但如果是這樣命名

```
report.AppendFooter();
```

就能一目瞭然這邊想幹嘛了

同樣地，錯誤處理也應該清楚表達意圖。
使用 exception 來呈現 error handling，不僅能讓錯誤的情況從正常的主程式抽離出來，還能讓程式碼變得更簡化

#### 不要害怕長名字，害怕的是不清楚

在實務上，長而清楚的命名，遠比短而模糊的名稱好，也比長而清楚的註解好。
