函式

函式 (Functions) #

核心觀點 #

函式(Function)是軟體系統中的基本動詞,本章探討如何撰寫出可讀性高且易於維護的函式。
核心理念可歸納為:函式應短小,且只做一件事。

  • 只做一件事 (Do One Thing): 函式應只做一件事,把它做好,且只做這件事。這符合單一職責原則 (Single Responsibility Principle)
  • 單一層次抽象 (One Level of Abstraction): 函式內語句應維持在同層次的抽象概念,避免混雜「高階邏輯」與「低階細節」

函式設計原則 #

1. 簡短與專注 #

函式的首則是「短小」,其次是「更短小」。

  • 區塊與縮排: ifelsewhile 語句區塊應只有一行,這行通常是一個函式呼叫
  • 判斷指標: 如果一個函式可被合理拆分成多個不同段落(例如初始化區、處理區、結束區),代表它做太多了
範例:重構為單一職責
  • 重構前 (做太多事): 混合了計算總和、尋找極值與計算平均。

    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):尚可接受,但需確認順序(如 x, y 座標)
    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)都是為消除重複而生。當邏輯重複時,維護成本會成倍增加。

程式碼即故事 #

寫程式就像寫作。你不可能一開始就寫出完美函式。

  1. 初稿: 通常冗長、雜亂、參數過多
  2. 修飾: 通過單元測試(Unit Test)覆蓋後,打磨程式碼——分解函式更改名稱消除重複
  3. 定稿: 最終產出符合上述規則的乾淨函式

“Master programmers think of systems as stories to be told rather than programs to be written.”
程式設計師不只是在寫程式碼,而是在講述系統的故事。