核心觀點#
函式(Function)是軟體系統中的基本動詞,本章探討如何撰寫出可讀性高且易於維護的函式。
核心理念可歸納為:函式應短小,且只做一件事。
- 只做一件事 (Do One Thing): 函式應只做一件事,把它做好,且只做這件事。
這符合單一職責原則 (Single Responsibility Principle)- 單一層次抽象 (One Level of Abstraction): 函式內語句應維持在同層次的抽象概念,
避免混雜「高階邏輯」與「低階細節」
函式設計原則#
1. 簡短與專注#
函式的首則是「短小」,其次是「更短小」。
- 區塊與縮排:
if、else、while語句區塊應只有一行,這行通常是一個函式呼叫 - 判斷指標: 如果函式可被合理拆成多個段落(例如初始化區、處理區、結束區),代表它做太多了
範例:重構為單一職責
重構前 (做太多事): 混合了計算總和、尋找極值與計算平均
def process_scores(scores): total = 0 min_score = 100 max_score = 0 for score in scores: total += score if score % min_score: min_score = score if score % max_score: max_score = score average = total / len(scores) return average, min_score, max_score重構後 (各司其職): 將邏輯拆解,讓主程式碼像是在閱讀摘要
def calculate_average(scores): return sum(scores) / len(scores) def find_min_score(scores): return min(scores) def find_max_score(scores): return max(scores) # 使用端 scores = [85, 90, 78, 92, 88] average = calculate_average(scores) min_score = find_min_score(scores) max_score = find_max_score(scores)
2. 函式參數 (Function Arguments)#
參數是函式與讀者間的溝通障礙。參數越多,讀者就需要了解越多細節才能理解,測試也會變困難。
參數數量的優先級:
| 優先級 | 類型 | 說明 |
|---|---|---|
| 1 | 零參數 (Niladic) | 最佳選擇 |
| 2 | 單參數 (Monadic) | 常見且易懂 |
| 3 | 雙參數 (Dyadic) | 尚可,需確認順序 |
| 4 | 三參數 (Triadic) | 應盡量避免 |
| 5 | 多參數 (Polyadic) | 不應使用 |
- 物件參數:如果函式看來需兩或三個以上的參數,通常代表這些參數應被包裝成一個類別(Class)
- 避免輸出型參數:讀者預期參數是輸入(Input),若參數被用來輸出結果(修改內容後帶回),
會造成認知斷層
3. 分離命令與查詢 (Command Query Separation, CQS)#
函式應「執行動作(改變物件狀態)」或「回答問題(回傳資訊)」,但不該同時做這兩件事。
若一個函式名為 set(“username”, “bob”) 卻同時回傳布林值代表成功與否,
會讓讀者困惑:這是要設定屬性,還是要檢查屬性是否存在?
class Cart:
def __init__(self):
self.items = []
# Command: 改變狀態,不回傳值
def add_item(self, item):
self.items.append(item)
# Query: 回傳資訊,不改變狀態
def get_total_price(self):
return sum(item.price for item in self.items)
# Bad Practice: 混合了 Command 與 Query
def add_item_and_return_total(self, item):
self.items.append(item)
return self.get_total_price()進階結構與維護#
1. 避免副作用 (Side Effects)#
副作用是指函式在承諾只做一件事時,卻「偷偷」做了其他事(例如修改全域變數、初始化不該初始化的系統)。 這會導致時序性耦合(Temporal Coupling)和難除錯的 Bug。
2. 善用例外處理取代錯誤碼#
回傳錯誤碼(Error Codes)會迫使呼叫者立刻處理錯誤,導致邏輯深層巢狀化。 用例外(Exceptions)可將錯誤處理邏輯從主邏輯中分離出來。
3. Switch 語句的處理#
Switch 語句天生就做「N 件事」,易違反單一職責原則(SRP)與開放封閉原則(OCP)。 在物件導向設計中,應盡量用多型(Polymorphism)或資料結構(如 Dictionary/Map) 取代它。
範例:取代 Switch
修正前 (Switch/If-Else): 每次新增事件類型都需修改此函式,違反 OCP
def handle_event(event_type, event): if event_type == "CREATE": create_event(event) elif event_type == "UPDATE": update_event(event) elif event_type == "DELETE": delete_event(event) else: print("Invalid event type.")修正後 (Table-Driven / Map): 將邏輯對應關係抽離,程式碼更簡潔且易擴充
def handle_event(event_type, event): event_handlers = { "CREATE": create_event, "UPDATE": update_event, "DELETE": delete_event, } if event_type in event_handlers: event_handlers[event_type](event) else: print("Invalid event type.")
寫作的藝術#
不重複自己 (DRY)#
重複是軟體萬惡之源。許多原則與設計模式(Design Patterns)都是為消除重複而生。
當邏輯重複時,維護成本會成倍增加。
程式碼即故事#
寫程式就像寫作。你不可能一開始就寫出完美函式。
函式的誕生:從初稿到精緻作品#
| 階段 | 狀態描述 | 核心動作 |
|---|---|---|
| 初稿 (Draft) | 邏輯混亂、命名隨意、參數過多 | 快速實作,完成功能為優先 |
| 修飾 (Refining) | 程式碼處於變動中, 尋找最簡潔的表達方式 | 分解長函式、優化命名、消除重複 |
| 定稿 (Final) | 結構俐落、意圖明顯、高度可讀 | 交付符合專業標準的乾淨代碼 |
單元測試是重構階段的安全網,讓你能放心修改程式碼。
“Master programmers think of systems as stories to be told rather than programs to be written.”
程式設計師不只是在寫程式碼,而是在講述系統的故事。