函數是程式碼組織的基本單元,良好的函數設計直接決定專案的可讀性、可測試性與維護成本。本文整理 Python 函數設計的三大核心維度——聚合性(Cohesion)、大小(Size)、耦合性(Coupling),並提供具體程式碼範例與效能實測,幫助開發者建立專業級的函數設計習慣。
原則:函數應專注於單一職責,輸入明確、輸出可預期、副作用最小化。
def process_order(user_id, items, payment_info, shipping_addr):
# 驗證用戶
# 計算價格
# 處理付款
# 扣減庫存
# 發送確認信
# 記錄日誌
# 產生報表
pass # 200+ 行程式碼
def validate_user(user_id): ...
def calculate_total(items): ...
def charge_payment(payment_info, amount): ...
def reserve_inventory(items): ...
def send_confirmation(email, order_id): ...
def log_transaction(order_id, details): ...
def process_order(user_id, items, payment_info, shipping_addr):
validate_user(user_id)
total = calculate_total(items)
charge_payment(payment_info, total)
reserve_inventory(items)
order_id = create_order_record(...)
send_confirmation(get_user_email(user_id), order_id)
log_transaction(order_id, {"total": total, "items": items})
return order_id
✅ 優點:各函數可獨立測試、複用、修改,且主流程一目瞭然。
經驗法則:
# ❌ 巢狀 4 層
def analyze(data):
if data:
for item in data:
if item.valid:
for sub in item.sub_items:
if sub.flag:
process(sub)
# ✅ 提早回傳 + 拆分
def analyze(data):
if not data: return
for item in data:
process_valid_item(item)
def process_valid_item(item):
if not item.valid: return
for sub in item.sub_items:
process_sub_item(sub)
def process_sub_item(sub):
if sub.flag:
process(sub)
# ✅ 良好:輸入輸出皆明確
def calculate_discount(price: float, member_level: str) -> float:
rates = {"gold": 0.2, "silver": 0.1, "bronze": 0.05}
return price * (1 - rates.get(member_level, 0))
# ❌ 差:依賴全域變數
DISCOUNT_RATES = {"gold": 0.2, "silver": 0.1}
def calculate_discount_bad(price):
return price * (1 - DISCOUNT_RATES.get(current_user.level, 0))
Python 變數查找順序:Local → Enclosing → Global → Built-in (LEGB)。區域變數查找最快,全域變數較慢。
import timeit
# 全域變數版本
GLOBAL_A = 5
def global_version():
res = 0
for i in range(10_000_000):
res = GLOBAL_A + i
return res
# 區域變數版本
def local_version():
a = 5
res = 0
for i in range(10_000_000):
res = a + i
return res
print("Global:", timeit.timeit(global_version, number=1)) # 約 3.7 秒
print("Local:", timeit.timeit(local_version, number=1)) # 約 1.2 秒
📊 結果:區域變數快 3 倍以上,且避免了全域狀態造成的副作用與除錯困擾。
# ❌ 危險:呼叫端列表被竄改
def add_item_bad(lst, item):
lst.append(item)
# ✅ 安全:建立副本或回傳新列表
def add_item_good(lst, item):
new_lst = lst.copy() # 或 lst[:]
new_lst.append(item)
return new_lst
# 或使用不可變資料結構
from dataclasses import dataclass
@dataclass(frozen=True)
class Order:
items: tuple
# a.py
PI = 3.14
def set_pi(new_val):
global PI
PI = new_val
return PI
# b.py
import a
my_pi = a.set_pi(3.14159) # 明確意圖,不直接改 a.PI
print(my_pi, a.PI)
| 檢核項目 | 標準 |
|---|---|
| 單一職責 | 函數名稱能精確描述其行為,無「且」、「或」 |
| 長度 | ≤ 80 行,巢狀 ≤ 3 層 |
| 參數 | ≤ 5 個,優先使用關鍵字參數與型別標註 |
| 回傳 | 單一回傳類型,避免 None 與實值混用 |
| 副作用 | 無修改全域/外部狀態,或在文件字串明確宣告 |
| 可測試 | 無外部依賴,或可透過參數注入 Mock 物件 |
「簡單並不代表容易」,好的函數設計需要刻意練習與持續重構。遵循高內聚、低耦合、單一職責三大原則,配合型別提示、單元測試與程式碼審查,能讓 Python 專案在規模增長時依然保持清晰、穩定且易於擴展。