【筆記】《易讀程式之美學 The Art of Readable Code》

終於讀完《易讀程式之美學》這本書了,在看的過程中一直把自己最近要交的作業拿回來改,由此就可以感受到這本書的好處。之後應該還會拿起來多看幾次,幸好當初有買下來!

不過真的是買書很便宜,但是看書挺貴的,所以我便將我覺得重要的部分記下來,以便未來較無時間時也能快速複習。

命名篇

替代詞彙

在命名時,選擇更精確的動詞可以讓程式碼更具表達力。以下是常見動詞的替代方案:

原詞可能的取代方案
Senddeliver, dispatch, announce, distribute, route
Findsearch, extract, locate, recover
Startlaunch, create, begin, open
Makecreate, set up, build, generate, compose, add, new

使用建議

根據實際的語意選擇更精確的動詞,例如:

  • sendEmail()deliverEmail() (強調送達)
  • findUser()locateUser() (強調定位) 或 searchUser() (強調搜尋過程)
  • startServer()launchServer() (強調啟動動作)
  • makeReport()generateReport() (強調產生過程)

帶上單位

變數名稱應包含單位資訊,避免誤用:

原始寫法改善後
Start(int delay)Start(int delay_secs)
CreateCache(int size)CreateCache(int size_mb)
ThrottleDownload(float limit)ThrottleDownload(float max_kbps)
Rotate(float angle)Rotate(float degrees_cw)

附加重要屬性

某些變數需要標註其狀態或特性,避免安全問題或錯誤使用:

情境原變數名稱改善後
以「明文」儲存的密碼,應先加密passwordplaintext_password
使用者提供的意見,顯示前須編碼commentunescaped_comment
轉換為 UTF-8 的 HTML 資料htmlhtml_utf8
做過 URL 編碼的輸入資料datadata_urlenc

命名長度原則

  • 小範圍 → 短名稱
  • 縮寫應被廣泛理解

避免誤解

動詞選擇

  • ❌ 不要使用 filter (語意模糊)
  • ✅ 使用 selectexclude
  • Clip 可改為 truncate (更明確)

區間命名

類型建議命名說明
包含邊界max / min閉區間兩端
閉區間first / last包含起點與終點
半閉區間begin / end包含起點,不含終點

布林命名

加上前綴詞讓意圖更清楚:

  • is (是否)
  • has (擁有)
  • can (能否)
  • should (應否)

程式碼對齊

程式碼越整齊、越對齊越好


註解篇

註解的目的是幫助讀者了解程式碼作者的思想

核心原則: 好程式 > 壞程式 + 好註解

記錄思想——導演評註

範例:

  • 「啟發式計算可能會漏幾個字,但很難 100% 解決」
  • 「這個 class 有點亂,或許可以建個子類別」

記錄程式碼缺陷

使用標準標記:

  • TODO - 待辦事項
  • FIXME - 需要修正
  • HACK - 暫時性解法
  • XXX - 有問題的程式碼

常數的註解

註解範例效果
// 只要…即可讓讀者對常數調整更有概念
// 加上合理的限制——沒人能讀那麼多文章!說明限制的理由
// 使用者認為 0.72 在大小/品質上有最好的平衡說明調校的結果

為讀者設想

預期疑問並解答

  • // 強制釋放記憶體(參考 STL swap trick)

標註實作細節

  • // 呼叫外部服務發送 email(逾時時間為 1 min)

說明複雜度

  • // O(number_tags * average_tag_depth)

全局註解

目標: 讓新進成員在一段輕鬆的談話後,能比自行閱讀程式碼知道更多事

摘要註解

用一句話解釋程式碼區塊的用途:

範例:

// 找出所有為自己購買商品的客戶
for (...) {
  for (...) {
    if (...) {
      // ...
    }
  }
}

註解原則

  • ✅ 註解「為何」而非「什麼」或「如何」
  • ✅ 盡力幫助讀者理解程式碼

寫註解的步驟

  1. 寫下心中想法
  2. 讀出註解
  3. 改善

讓註解精確且簡潔

原則

  • ❌ 不說廢話冗詞
  • ❌ 避免模稜兩可的代名詞
  • ✅ 修正草率語句
  • ✅ 精確描述函式行為

範例: 精確描述

  • ❌ 「回傳檔案行數」
  • ✅ 「回傳換行字元 (\n) 個數」

使用範例說明

提供具代表性的輸入/輸出範例

表達程式意圖

說明「為什麼這樣做」而非「做了什麼」

函式參數名稱註解

範例:

Connect(/* timeout_s = */ 10, /* use_encryption = */ false)

使用「訊息密集」的詞彙

專業術語可以更簡潔地傳達概念:

  • cache layer
  • 正規化 (normalization)
  • 啟發式 (heuristic)
  • 暴力法 (brute-force)
  • naive solution

簡化迴圈與邏輯

提高控制流程的可讀性

條件式的條件順序

左側「較有變化」,右側為「比較基準」

範例:

// ❌ 尤達表示法 (Yoda notation)
if (10 === length)

// ✅ 自然的順序
if (length === 10)

if/else 區塊順序

優先順序:

  1. 先處理肯定而非否定
  2. 先處理簡單的情況,以便同時顯示 if 和 else 區塊
  3. 先處理比較有趣/明顯的情況

三元運算子 ? :

有利有弊,視情況判斷使用時機

避免 do/while loop

理由:

  • 判斷式通常在區塊上方,因為閱讀順序由上到下
  • do/while 有時讓人需要讀兩次程式碼
  • C++ 之父也不推薦
  • continue 會變得難以理解

範例:

// ❌ 容易混淆
do {
  continue;
} while (false); // 只會執行一次

儘早從函式 return

迷思: 有些人認為只應有一個 return,因為想執行函式尾端的清除程式碼

現代解法:

  • C++: 解構子 (destructor)
  • Java, Python: try...finally
  • Python: with
  • C#: using

避免 goto

goto 惡名昭彰,多數情況下都應避免

減少巢狀結構

策略

  • return 把失敗情況先處理掉
  • continue 也很好用,但要小心不要讓它變成迴圈內的 goto

重構提醒

修正程式碼時,要以全新的角度審視,退一步以整體的角度來考慮程式碼

能否理解執行流程

注意在背景執行的 Thread


分解巨大表示式

核心概念

將巨大表示式分解為更容易消化的大小

解釋性變數

軟工課程中的「抽取函式」概念,但此處包含但不限於函式

摘要變數

把一整串表示式放到變數中,用以解釋意圖:

範例:

const user_owns_doc = request.user.id === document.owner_id;

笛摩根定律

善用 not 的分配律簡化邏輯

避免過度優化

  • ❌ 不要變成 short-circuit evaluation 魔人
  • ❌ 寫法「很酷」的程式碼會對後人造成困擾

思考技巧

當遇到複雜的邏輯時,可以尋找更優雅的方式 → 可以從相反的方向去思考


變數與可讀性

濫用變數的問題

會有三個問題:

  1. 變數越多越難同時記住全部
  2. lifetime 越長就必須記住越久
  3. 值越常變,越難記得當前數值

消除變數

若有個變數只用一次、不複雜,甚至是對理解程式沒有幫助,就果斷拿掉它吧!

常是程式碼編輯後的「殘骸」

策略

  • 消去中間結果
  • 讓函式「儘快完成任務」是個好習慣
  • 善加利用結構化程式設計來消除控制流程變數

限縮變數的範圍

核心原則: 儘可能減少「可以看到變數」的程式碼行數

關於類別成員變數

  • 某種程度上,類別的成員變數就是類別內部的全域變數
  • 盡量讓成員變數退化成區域變數
  • 需要限制類別成員存取權 → 盡量使用靜態方法 (static method)
  • 將大類別縮成小類別(小類別間須互相獨立,互不干涉,否則沒有效果)
  • 主要動機就是隔離資料,也就是變數

最佳實踐

  • 下移宣告,讓變數與函式在第一次使用前才出現
  • 偏好單次寫入的變數 → 操作變數的地方越多,就越難記得當前數值

重新組織程式碼

積極尋找並抽離不相關的子問題

核心概念: 當想著「要是 Lib 有提供 xyz() 函式就好了」的時候,就動手做一個

好處

  • 抽出程式碼之後有意外好處: 更容易理解,也更容易改善可靠度、加入新功能、處理邊界值
  • 通用程式碼的好處就是可以用在各個專案(一般會放在 util/ 目錄之下)
  • 就算抽出的問題和專案相關也無妨,有抽出來就是好事

重塑界面

永遠不必屈就於不夠好的 interface,你可以自行幫它整形,甚至重塑界面,避免被細節干擾

一次只做一項工作

將想法轉為程式碼

  • 清楚描述邏輯 → 前面提過的簡化迴圈
  • 善用函式庫所提供的協助

程式碼越少越好

核心原則: 可讀性最高的程式碼就是完全沒有程式碼

策略

  • 有些功能非必要,就不用開發
  • 詢問與分解需求 → 可以事半功倍

維持程式碼小而美

  • 建立通用程式碼
  • 移除用不到的程式碼與功能
  • 將專案切成不相連的子專案
  • 注意程式的份量,維持其輕巧

其他建議

  • 熟悉使用的函式庫
  • 用 Unix 工具來代替撰寫程式

測試與可讀性

核心原則

讓測試易讀與維護

對使用者隱藏不重要的細節,突顯重要的細節

策略

  • 測試敘述最小化
  • 自製迷你語言、爬蟲 → 個人覺得有點走火入魔了…但可以理解為什麼這麼做
  • 手工打造錯誤訊息

選擇良好的測資

優先使用簡單、明確但仍能達到測試效果的輸入值

test-friendly 的開發

單單在寫程式時考慮到測試,就能讓程式碼有很大的改善


後記

大概是這樣。剛剛才發現這本書居然絕版了⋯⋯