讓程式碼具有可讀性#
可讀性(readability)本質上是主觀的,但其核心目標是:讓其他工程師能夠快速且正確地理解程式碼在做什麼。要達成這個目標,往往需要具備同理心,試著從他人的角度思考哪些地方可能令人困惑或產生誤解。
本章涵蓋三大面向:
- 讓程式碼能夠自我解釋的技巧
- 確保程式碼中的細節對他人清晰可見
- 正確使用語言特性的原則
mindmap root((程式碼可讀性)) 命名 Naming 註解 Comments 程式碼簡潔性 Conciseness 風格一致性 Style Consistency 巢狀深度 Nesting Depth 函式呼叫清晰度 Call Clarity 數值解釋 Magic Numbers 語言特性運用 Language Features
5.1 使用描述性名稱#
名稱不只是用來唯一識別事物,更是一份簡短摘要。就像「toaster」這個詞同時告訴你它是什麼、做什麼;如果改叫「object A」,就完全失去了這層資訊。程式碼中的類別、函式、變數命名也是同樣的道理。
非描述性名稱讓程式碼難以閱讀#
當命名毫無意義時(例如 T、pns、s、f),即使花 20-30 秒盯著程式碼看,也完全無法理解它在做什麼。
註解不是描述性名稱的替代品#
有人會透過大量註解來彌補糟糕的命名,但這會帶來問題:
- 程式碼變得更雜亂,工程師需要同時維護註解和程式碼
- 必須不斷上下捲動才能理解各個變數的意義
- 在函式呼叫端看到
t.f(n)時,仍然無法直接理解它的用途
解決方案:讓名稱具有描述性#
將 T 改為 Team、pns 改為 playerNames、f() 改為 containsPlayer(),程式碼立刻變得容易理解:
- 變數、函式和類別變成自我解釋的
- 即使脫離上下文,像
team.containsPlayer(playerName)這樣的呼叫也一目了然 - 程式碼比使用註解更簡潔,維護負擔更低
5.2 適當使用註解#
註解可以服務不同目的:
- 解釋程式碼做了什麼(what)
- 解釋程式碼為什麼這樣做(why)
- 提供使用說明等其他資訊
冗餘註解可能有害#
當程式碼已經足夠自我解釋時,重複描述「做了什麼」的註解是多餘的:
- 增加維護負擔——修改程式碼時還得同步更新註解
- 讓程式碼更雜亂——想像每一行都有一個說明註解
註解是不可讀程式碼的糟糕替代品#
如果需要靠註解來解釋程式碼做了什麼,真正的問題往往是程式碼本身不夠可讀。更好的做法是重構程式碼,例如用命名良好的 helper function 取代魔術般的陣列索引存取。
註解適合解釋「為什麼」#
程式碼不擅長自我解釋的是為什麼要這樣做。以下情境特別適合使用「why」註解:
- 產品或商業決策的原因
- 修復某個不直覺的 bug
- 處理相依套件的奇怪行為
何時使用 why 註解:當程式碼的存在原因與某些背景知識有關,而其他工程師不一定知道這些背景時,「why」註解特別有價值。例如:「Legacy 使用者(v2.0 之前註冊)的 ID 是基於姓名產生的,詳見 issue #4218。」
高層級摘要註解很有用#
可以把註解想成書的概要:
- 每一段都加上摘要 → 很煩、降低可讀性(如同低層級的 what 註解)
- 書封底的簡介 → 非常有用,讓人快速判斷是否相關(如同類別層級的摘要文件)
適合撰寫高層級文件的場景:
- 類別整體做什麼,以及需要注意的重要細節
- 函式的輸入參數代表什麼
- 函式的回傳值代表什麼
務必使用常識:本節的建議不是硬性規則。如果不得已需要包含位元運算等難以理解的邏輯,用 what 註解來解釋也完全合理。
5.3 不要執著於程式碼行數#
一般而言,程式碼行數越少越好——更少的行數意味著更少的維護負擔和更低的認知負荷。但有些工程師會把「最小化行數」推到極端,認為它比任何其他品質因素都重要。
行數只是一個代理指標(proxy measurement),我們真正在意的是:
- 容易理解
- 難以誤解
- 難以意外破壞
不是所有的行都是平等的:一行極難理解的程式碼,其品質可能遠不如用 10 行(甚至 20 行)清楚表達的版本。
避免精簡但不可讀的程式碼#
以 parity bit 驗證為例,一行程式碼壓縮了大量的假設:
- 最低 15 位元包含值、最高位元包含 parity bit
- 各種 bitmask 的含義(
0x7FFF、0x8000) - parity 的計算邏輯
這讓其他工程師不得不耗費大量時間才能理解,也使得假設不夠明確而容易被破壞。
解決方案:即使需要更多行數,也要讓程式碼可讀#
透過定義命名良好的 helper function 和常數(如 PARITY_BIT_INDEX、extractEncodedParity()、calculateParity()),程式碼變得更容易理解,子問題也變得可重用。
行數 vs 可讀性:保持行數精簡是好的原則,但確保程式碼可理解、健壯且不容易出錯更為重要。如果需要更多行數才能做到這些,那就值得。
5.4 遵循一致的程式碼風格#
就像書寫英文有文法規則和慣例一樣(例如 SaaS 而非 SAAS),程式碼也需要一致的風格慣例。
不一致的風格會造成混淆#
以常見慣例為例:類別名稱用 PascalCase、變數名稱用 camelCase。如果一個類別被命名為 connectionManager(小寫開頭),讀者很容易誤認為它是一個實例變數,而非一個類別。當程式碼寫成 connectionManager.terminateAll() 時,讀者可能以為這只會影響當前實例的連線,但實際上它是靜態方法,會終止所有連線——這就是一個因風格不一致而造成的嚴重 bug。
解決方案:採用並遵循風格指南#
- 將類別命名為
ConnectionManager而非connectionManager,就能避免上述混淆 - 風格指南通常涵蓋:命名慣例、語言特性的使用、縮排方式、套件結構、文件撰寫方式
- 大多數團隊已有既定的風格指南,遵循即可
- 若團隊尚無風格指南,可採用現成的(如 Google Style Guides ↗)
善用 Linter:Linter 是能自動偵測風格違規的工具,有些還能警告容易出錯的程式碼模式。雖然它們只能捕捉較簡單的問題,但作為快速檢查非常有用。
5.5 避免深層巢狀#
典型的程式碼由巢狀區塊組成:函式定義一個區塊、if 判斷式定義一個區塊、for 迴圈定義一個區塊。這些區塊層層嵌套時,可讀性會急速下降。

Figure 5.1: Control-flow logic (such as if-statements and for-loops) can often result in blocks of code that are nested within one another.
深層巢狀難以閱讀#
人眼不擅長追蹤每一行程式碼處於哪一層巢狀中。當多個 if-else 層層嵌套時:
- 難以一眼看出各段邏輯在何種條件下執行
- 難以判斷某些行程式碼是否可達(reachable)
解決方案:重構以減少巢狀#
當巢狀的每個分支都以 return 結束時,通常可以輕鬆地重新排列邏輯來消除巢狀——使用 early return 模式即可。例如:
// 巢狀版本
if (condition1) {
return A;
} else {
if (condition2) {
return B;
} else { ... }
}
// 扁平版本(early return)
if (condition1) { return A; }
if (condition2) { return B; }
...巢狀通常是函式做太多事的徵兆#
當巢狀分支不以 return 結束時,通常意味著函式承擔了太多責任。例如一個函式同時包含「查找地址」和「寄送信件」的邏輯。
解決方案:拆分為更小的函式#
將子問題(如「查找車主地址」)提取到獨立函式中,就能:
- 在子函式中輕鬆使用 early return 消除巢狀
- 讓主函式更簡潔、更專注於高層邏輯
5.6 讓函式呼叫具有可讀性#
即使函式命名良好,如果呼叫時不清楚各引數的用途,可讀性仍然很差。例如 sendMessage("hello", 1, true) 中,1 和 true 分別代表什麼?
過多參數的警訊:函式呼叫的可讀性會隨著引數數量增加而下降。過多的參數通常反映更根本的問題,例如抽象層次不當或模組化不足。
解決方案一:使用具名引數(Named Arguments)#
越來越多的現代語言支援具名引數:
sendMessage(message: "hello", priority: 1, allowRetry: true);在 TypeScript 中,可以透過 object destructuring 達到類似效果:定義一個參數介面,函式接收一個物件並立即解構。
解決方案二:使用描述性型別#
用更具描述性的型別取代原始型別(primitive type):
- 用 class 包裝值(如
MessagePriority) - 用 enum 取代 Boolean(如
RetryPolicy.ALLOW_RETRYvstrue)
這樣即使不看函式定義,呼叫也一目了然:
sendMessage("hello", new MessagePriority(1), RetryPolicy.ALLOW_RETRY);有時沒有完美解法#
當函式接收多個同型別的參數(如 BoundingBox(Int top, Int right, Int bottom, Int left)),如果語言不支援具名引數,最佳做法可能只是使用行內註解:
new BoundingBox(
/* top= */ 10,
/* right= */ 50,
/* bottom= */ 20,
/* left= */ 5);但這依賴於註解正確且被持續維護。Builder pattern 是另一個選項,但會犧牲編譯期安全性。

Figure 5.2: Some IDEs augment the view of the code to make function calls more readable.
IDE 的輔助不能取代可讀性#
有些 IDE 會自動在呼叫處顯示參數名稱,但不能依賴這個功能——不是每個工程師都用同樣的 IDE,程式碼也常在沒有此功能的工具中被閱讀(如 code review 工具、merge 工具)。
5.7 避免使用未解釋的值#
硬編碼的值(hard-coded value)需要同時傳達兩件事:
- 值是什麼(電腦執行需要)
- 值代表什麼意思(工程師理解需要)
未解釋的值令人困惑#
以動能計算為例,程式碼中出現 907.1847 和 0.44704 這類數字(magic number),讀者無法得知它們分別是美噸轉公斤和英里/時轉公尺/秒的換算係數。當其他工程師需要修改程式碼時(例如改用公斤為單位),可能因不理解這些值而忘記移除,導致計算錯誤。
解決方案一:使用命名良好的常數#
private const Double KILOGRAMS_PER_US_TON = 907.1847;
private const Double METERS_PER_SECOND_PER_MPH = 0.44704;在程式碼中使用常數名稱,讓意圖一目了然。
解決方案二:使用命名良好的函式#
有兩種方式:
- Provider function:回傳常數值的函式(如
kilogramsPerUsTon()),概念上與常數類似 - Helper function:執行轉換的函式(如
usTonsToKilograms(Double usTons)),將轉換視為子問題來解決,使用者不需要知道具體的換算係數
考慮重用性:如果其他工程師可能需要重用這些常數或 helper function,最好將它們放在公共的工具類別中,而非僅保留在當前使用的類別內。
5.8 適當使用匿名函式#
匿名函式(anonymous function)是沒有名稱的函式,通常在需要的地方以 inline 方式定義。
匿名函式適合小而自明的邏輯#
對於簡單、一目了然的操作,匿名函式非常簡潔有效:
allFeedback.filter(feedback -> !feedback.getComment().isEmpty());匿名函式可能難以閱讀#
由於匿名函式沒有名稱,它不提供「這段邏輯在做什麼」的摘要。如果內容不夠自明(例如包含位元運算的 parity bit 檢查),讀者會很困惑。
解決方案:改用具名函式#
將複雜的匿名函式提取為具名函式(如 isParityBitCorrect),讓高層邏輯更清晰:
ids.filter(id -> id != 0)
.filter(isParityBitCorrect);讀者立刻明白:過濾掉零值、過濾掉 parity bit 不正確的 ID。如果想了解細節再去看具名函式即可。
大型匿名函式尤其有問題#
有些工程師把「函數式程式設計」等同於「使用 inline 匿名函式」,導致產出巨大且巢狀的匿名函式。Functional style 完全可以用具名函式來實現。
經驗法則:如果匿名函式超過 2-3 行,就應該考慮將它拆分為一個或多個具名函式。這不僅提升可讀性,也增加了可重用性。
解決方案:將大型匿名函式拆分為具名函式#
將巨大的 inline 匿名函式拆分為多個小型具名函式(如 buildFeedbackItem()、buildTitle()、buildCommentText()、buildCategories()),可以:
- 讓每個函式的職責一目了然
- 讓高層邏輯清楚地表達 UI 顯示了什麼資訊
- 各子函式可被獨立重用
5.9 審慎使用新語言特性#
工程師都喜歡「閃亮的新東西」,程式語言的新特性也不例外。語言設計者在加入新特性前會深思熟慮,很多場景下新特性確實能大幅提升程式碼品質。
新特性可以改善程式碼#
例如 Java 8 引入的 Stream API,讓過濾清單這類操作從冗長的 for 迴圈變成簡潔的鏈式呼叫,既更可讀也更不容易出錯。
冷門特性可能造成困惑#
即使某個特性有明確好處,也要考量團隊中其他工程師是否熟悉它。如果團隊只維護少量 Java 程式碼,且成員都不熟悉 Stream,那麼使用它帶來的改善可能比不上造成的困惑。
使用最適合工作的工具#
不要因為新特性能用就到處用。例如用 Stream 來從 Map 取值——這比直接呼叫 map.get(key) 既不可讀又低效。
避免為了新而新:使用語言特性的理由應該是「它是解決這個問題的最佳工具」,而不是「它很新很酷」。
5.10 本章摘要#
- 不可讀的程式碼會導致:
- 其他工程師浪費時間嘗試解讀
- 誤解導致引入 bug
- 當其他工程師需要修改時容易破壞程式碼
- 提升可讀性有時會讓程式碼更冗長,但這通常是值得的取捨
- 撰寫可讀程式碼需要同理心,設想他人可能覺得困惑之處
- 真實場景各有不同的挑戰,撰寫可讀程式碼永遠需要常識與判斷力