函式是軟體系統的基本動詞。整章的核心可以濃縮成一句話:函式應該短小,只做一件事,把它做好,且只做這件事。當一個函式同時處理高階流程與低階細節,讀者就得在不同思考層級間反覆跳躍;把細節抽成命名良好的子函式,主函式就變成一段能順讀的敘事。
🧠 Core Ideas
- 首則是短小,其次是更短小。
if、else、while的區塊最好只有一行,通常就是一個函式呼叫。如果函式能被合理拆成「初始化區、處理區、結束區」,代表它做太多了。 - 只做一件事=單一職責。判斷方法:若你還能從這個函式裡「抽出另一個函式,而它的名字不只是複述原函式」,那它就做了不只一件事。
- 維持單一抽象層次。函式內的語句要停在同一個抽象概念上,別把高階邏輯(
payEmployee)和低階細節(字串拼接、迴圈索引)混在同一層。 - 參數越少越好。零參數最佳,單參數次之,三參數以上應盡量避免;參數是函式與讀者之間的溝通障礙,也讓測試組合爆炸。若需要三個以上參數,通常代表它們該被包成一個物件。
- 分離命令與查詢(CQS)。一個函式要嘛「做動作、改變狀態」,要嘛「回答問題、回傳資訊」,別同時做兩件事——
set("username","bob")卻回傳布林值會讓讀者困惑。 - 用例外取代錯誤碼。回傳錯誤碼會迫使呼叫者立刻在原地處理,導致深層巢狀;用例外能把錯誤處理從主邏輯中分離出來,讓正常路徑保持乾淨。
TIP
Switch 語句天生就做「N 件事」,容易同時違反 SRP 與 OCP——每新增一種類型都得回去改它。在物件導向設計裡,優先用多型或 table-driven(把 event_type 對應到 handler 的字典)取代長串 if-else,讓新增類型變成「加一筆資料」而非「改一段邏輯」。
⚖️ 副作用:函式偷偷做的第二件事
副作用是函式在承諾「只做一件事」時,卻偷偷做了別的——修改全域變數、初始化不該它管的系統、留下輸出型參數。它是最難察覺、也最傷人的違規。
副作用如何製造難除錯的 Bug
-
時序性耦合(Temporal Coupling)。當
checkPassword順手初始化了某個 Session,呼叫者就被迫「只能在對的時機、對的順序」呼叫它——這種隱藏的時序依賴一旦被打破,就是難以重現的 Bug。 -
輸出型參數造成認知斷層。讀者預期參數是輸入(Input);若某個參數其實被拿來帶回結果(修改後回傳),讀者得回去讀實作才能發現,這正是「函式名字沒說實話」的典型。
-
DRY:重複是萬惡之源。很多設計模式的存在就是為了消除重複;當同一段邏輯散落多處,任何一次需求變更都得同步修改所有副本,維護成本成倍增加。
🖼️ 好函式是重構出來的,不是一次寫成的
Uncle Bob 坦言:沒有人能一開始就寫出完美的函式。書中把函式的誕生描述成寫作——先有雜亂、命名隨意、參數過多的「初稿」,再進入「修飾」階段分解長函式、優化命名、消除重複,最後才成為結構俐落、意圖明顯的「定稿」。這個過程沒有神來一筆,只有一連串小而安全的改動。
而讓這種持續重構成為可能的,是單元測試這張安全網——它讓你敢在不破壞行為的前提下大膽拆解與改名。這正呼應了「程式設計師把系統當成故事來講述,而非程式來撰寫」的理念:主函式讀起來應該像一段摘要,每個被抽出的子函式則是故事的段落。當每個函式都短小、命名良好、只做一件事,整段程式碼就能被「由上而下」順讀,像讀一篇條理分明的文章。
IMPORTANT
短小與單一職責不是為了美觀,而是為了「可替換、可測試、可理解」。一個只做一件事的小函式,出錯時範圍明確、測試時組合單純、閱讀時意圖清晰——這三個好處疊加起來,就是讓系統「易於改變」的微觀基礎,也是整潔架構在檔案層級的起點。
🔑 Takeaways
- 函式的首則是短小,第二則是更短小;能再抽出一個有意義的子函式,就代表它做太多了。
- 維持單一抽象層次、參數越少越好;三個以上參數該包成物件。
- 分離命令與查詢、用例外取代錯誤碼、用多型或查表取代 switch。
- 警惕副作用與輸出型參數:它們是函式「偷做第二件事」的時序耦合來源。
- 延伸:把「不可變、無副作用」推到極致,就是函數式設計的世界,見 函數式設計:不可變與無副作用。
No notes yet — jot your takeaways or Q&A here.