終於讀完《易讀程式之美學》這本書了,在看的過程中一直把自己最近要交的作業拿回來改,由此就可以感受到這本書的好處。之後應該還會拿起來多看幾次,幸好當初有買下來!
不過真的是買書很便宜,但是看書挺貴的,所以我便將我覺得重要的部分記下來,以便未來較無時間時也能快速複習。
命名篇
替代詞彙
在命名時,選擇更精確的動詞可以讓程式碼更具表達力。以下是常見動詞的替代方案:
原詞 | 可能的取代方案 |
---|---|
Send | deliver, dispatch, announce, distribute, route |
Find | search, extract, locate, recover |
Start | launch, create, begin, open |
Make | create, 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) |
附加重要屬性
某些變數需要標註其狀態或特性,避免安全問題或錯誤使用:
情境 | 原變數名稱 | 改善後 |
---|---|---|
以「明文」儲存的密碼,應先加密 | password | plaintext_password |
使用者提供的意見,顯示前須編碼 | comment | unescaped_comment |
轉換為 UTF-8 的 HTML 資料 | html | html_utf8 |
做過 URL 編碼的輸入資料 | data | data_urlenc |
命名長度原則
- 小範圍 → 短名稱
- 縮寫應被廣泛理解
避免誤解
動詞選擇
- ❌ 不要使用
filter
(語意模糊) - ✅ 使用
select
或exclude
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 (...) {
// ...
}
}
}
註解原則
- ✅ 註解「為何」而非「什麼」或「如何」
- ✅ 盡力幫助讀者理解程式碼
寫註解的步驟
- 寫下心中想法
- 讀出註解
- 改善
讓註解精確且簡潔
原則
- ❌ 不說廢話冗詞
- ❌ 避免模稜兩可的代名詞
- ✅ 修正草率語句
- ✅ 精確描述函式行為
範例: 精確描述
- ❌ 「回傳檔案行數」
- ✅ 「回傳換行字元 (
\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 區塊順序
優先順序:
- 先處理肯定而非否定
- 先處理簡單的情況,以便同時顯示 if 和 else 區塊
- 先處理比較有趣/明顯的情況
三元運算子 ? :
有利有弊,視情況判斷使用時機
避免 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 魔人
- ❌ 寫法「很酷」的程式碼會對後人造成困擾
思考技巧
當遇到複雜的邏輯時,可以尋找更優雅的方式 → 可以從相反的方向去思考
變數與可讀性
濫用變數的問題
會有三個問題:
- 變數越多越難同時記住全部
- lifetime 越長就必須記住越久
- 值越常變,越難記得當前數值
消除變數
若有個變數只用一次、不複雜,甚至是對理解程式沒有幫助,就果斷拿掉它吧!
常是程式碼編輯後的「殘骸」
策略
- 消去中間結果
- 讓函式「儘快完成任務」是個好習慣
- 善加利用結構化程式設計來消除控制流程變數
限縮變數的範圍
核心原則: 儘可能減少「可以看到變數」的程式碼行數
關於類別成員變數
- 某種程度上,類別的成員變數就是類別內部的全域變數
- 盡量讓成員變數退化成區域變數
- 需要限制類別成員存取權 → 盡量使用靜態方法 (static method)
- 將大類別縮成小類別(小類別間須互相獨立,互不干涉,否則沒有效果)
- 主要動機就是隔離資料,也就是變數
最佳實踐
- 下移宣告,讓變數與函式在第一次使用前才出現
- 偏好單次寫入的變數 → 操作變數的地方越多,就越難記得當前數值
重新組織程式碼
積極尋找並抽離不相關的子問題
核心概念: 當想著「要是 Lib 有提供 xyz() 函式就好了」的時候,就動手做一個
好處
- 抽出程式碼之後有意外好處: 更容易理解,也更容易改善可靠度、加入新功能、處理邊界值
- 通用程式碼的好處就是可以用在各個專案(一般會放在
util/
目錄之下) - 就算抽出的問題和專案相關也無妨,有抽出來就是好事
重塑界面
永遠不必屈就於不夠好的 interface,你可以自行幫它整形,甚至重塑界面,避免被細節干擾
一次只做一項工作
將想法轉為程式碼
- 清楚描述邏輯 → 前面提過的簡化迴圈
- 善用函式庫所提供的協助
程式碼越少越好
核心原則: 可讀性最高的程式碼就是完全沒有程式碼
策略
- 有些功能非必要,就不用開發
- 詢問與分解需求 → 可以事半功倍
維持程式碼小而美
- 建立通用程式碼
- 移除用不到的程式碼與功能
- 將專案切成不相連的子專案
- 注意程式的份量,維持其輕巧
其他建議
- 熟悉使用的函式庫
- 用 Unix 工具來代替撰寫程式
測試與可讀性
核心原則
讓測試易讀與維護
對使用者隱藏不重要的細節,突顯重要的細節
策略
- 測試敘述最小化
- 自製迷你語言、爬蟲 → 個人覺得有點走火入魔了…但可以理解為什麼這麼做
- 手工打造錯誤訊息
選擇良好的測資
優先使用簡單、明確但仍能達到測試效果的輸入值
test-friendly 的開發
單單在寫程式時考慮到測試,就能讓程式碼有很大的改善
後記
大概是這樣。剛剛才發現這本書居然絕版了⋯⋯