模糊(obscurity)是複雜性的兩大成因之一,對策就是把程式碼寫得顯而易見:讀者能快速掃過、不必多想,而且第一直覺就是對的。關鍵在於——「顯而易見」不由作者宣稱,而是由讀者的腦袋來判斷。
🧠 Why It's a Problem
- 模糊直接製造複雜性:不顯而易見的程式碼逼讀者花大量時間與精力才能理解,而且容易誤解、容易埋進 bug。
- 顯而易見的程式碼更省成本:讀者不必額外蒐集資訊,甚至需要的註解更少——資訊已經寫在程式碼本身。
- 判準在讀者、不在作者:看別人的程式碼比看自己的更容易發現「不顯而易見」;所以判斷顯而易見性最好的方法就是 code review。別人說看不懂,它就是不顯而易見,再清楚都不能反駁。
- 從資訊角度看:程式碼之所以不顯而易見,多半是因為讀者缺了理解它所需的某些資訊。
TIP
別人喊「這段不顯而易見」時,別急著辯解。試著搞懂是什麼讓它變模糊,下次就能寫出更好的程式碼。
⚖️ 什麼讓程式碼變得不顯而易見
讓程式碼顯而易見有幾條正面途徑:好的命名(見 意圖揭示的命名)、一致性(相似的事用相似方式做,讀者能套用既有模式、不必細讀就下安全結論)、善用空白(用空行分隔邏輯區塊、讓註解更顯眼),以及在無法避免的模糊處補上戰略性註解。這裡聚焦反面——書中點名的幾個「把程式碼變模糊」的常見來源。
通用容器 — 元素被塞進泛用名,看不出真實意義
// 用通用 pair 回傳兩個值:呼叫端只能看到 first / second
function advanceTerm(state) {
return { first: state.currentTerm, second: false };
}
const r = advanceTerm(state);
// r.first 是什麼?r.second 又代表什麼?得回去翻實作才知道
if (!r.second) retry(r.first);通用容器對「寫的人」方便,卻對所有後來的讀者製造混亂——名字(first/second、getKey/getValue)完全藏住了元素的真實意義。
專用結構 — 有意義的名字,宣告處還能附文件
// 為這個用途定義專屬結構:名字自己會說話
function advanceTerm(state) {
return { currentTerm: state.currentTerm, electionWon: false };
}
const { currentTerm, electionWon } = advanceTerm(state);
if (!electionWon) retry(currentTerm);多花幾分鐘定義專用結構,換來的是明顯易懂——這正呼應書中的原則:軟體應為「易讀」設計,而不是為「易寫」設計。
CAUTION
當程式碼的意義與行為無法被快速看懂,這就是一面紅旗,通常代表有重要資訊沒被立即呈現給讀者。書中點名的模糊來源包括:
- 事件驅動程式設計:handler 從不被直接呼叫,事件模組透過函式指標/介面間接觸發,連看到呼叫點都猜不出會跑哪個 handler,控制流極難追蹤。對策是在每個 handler 的介面註解裡說明它何時被呼叫。
- 通用容器(如
Pair):元素被塞進泛用名,真實意義消失。 - 宣告型別與實際分配型別不一致:例如宣告成
List卻分配ArrayList——讀者只看宣告,會誤判它的效能與執行緒安全特性。最好讓兩者一致。 - 違反讀者預期的程式碼:例如
main結束了,建構式偷偷啟動的執行緒卻還在跑。務必用文件(介面註解+現場一行短註解)補上。
🔑 Takeaways
- 「顯而易見」的定義是:讀者快速掃過、不必多想、第一直覺就正確;它由讀者判斷,最佳檢驗手段是 code review。
- 正面途徑:好命名、一致性、善用空白、在不可避免的模糊處補戰略性註解。
- 模糊的常見來源:事件驅動的間接控制流、通用容器、宣告與分配型別不一致、違反讀者預期的行為。
- 不顯而易見=讀者缺資訊。三條解法:減少所需資訊(抽象、消除特例)、善用讀者已有的知識(遵循慣例與預期)、把重要資訊呈現在程式碼中(命名+註解)。
- 核心心法:為讀而寫,不是為寫而寫。
No notes yet — jot your takeaways or Q&A here.