ADR 0006: 擁抱 Functional Programming 與 Rust-Style 錯誤處理¶
Context (背景脈絡)¶
Time Compass 需要處理複雜的日曆事件轉換、時區換算與 Moodle 作業解析。 在傳統的 Python 開發中,往往會建立龐大的 CalendarManager 或 TaskService 等帶有內部狀態 (Internal State) 的大類別 (God Classes)。同時,在處理可能失敗的 API 呼叫或資料清洗時,也常會看到深不見底的 try-catch 巢狀結構。 這導致測試非常難寫,因為要 mock 一整個物件的狀態,且難以預測例外 (Exception) 會在流程的哪一層爆開。
Decision (技術決策)¶
在核心業務領域 (Domain) 與資料轉換層,強制採用 Functional Programming (FP) 風格;在錯誤處理上嘗試貼近 Rust 的 Result 模式。
具體實踐: 1. 無狀態純函數 (Pure Functions):資料的轉換(如 from_dict, to_toon_calendar)全數都是宣告為獨立的純函數,不依賴全局變數或物件屬性。 - 例外放寬:只有在直接接觸網路 I/O(如 FastAPI Router, MCP 的 Server 或 Provider)的地方,才允許定義 Class 來保留連線狀態。 2. Result Pattern:不隨意丟出 Exception 讓外層去接。當執行步驟有預期內的失敗風險時,函數應該返回明確的結果結構(如 ok, error 狀態碼或 Union Types),迫使呼叫者在當下立刻處理這條分支。
Consequences (決策結果)¶
Positive (優點)¶
- 極致的可測試性:因為所有的資料轉換都是無副作用的函數,我們可以非常輕易地利用
tests/snapshots的輸入輸出來進行驗證,完全不需建立複雜的 mock 實例。 - Error Boundary 清晰:將例外阻擋在網路邊界,業務邏輯內都是可預期的資料流,降低 Debug 追蹤呼叫堆疊 (Stack Trace) 的痛苦。
Current Tech Debt (當前技術債)¶
async_client 的例外處理不一致性: 目前在 async_client 層級尚未完全貫徹 Result Pattern。 - 下游 Client 仍使用 raise 丟出錯誤。 - 這導致中游的 async_core 必須使用 try-catch 來攔截這些例外,再重新封裝回 Result 以供給 batch 處理。 - 後續改進預期:應將 Client 直接改為回傳 Result 物件,徹底移除 try-catch 的尷尬混合層級。
Negative (缺點)¶
- 不符合傳統 Python 開發習慣:習慣了大量 OOP 的 Python 工程師在初次看到這些模組時,可能會覺得缺乏封裝。
- 型別註記繁瑣:為了達成 Result 模式,我們大量使用了 Pydantic 模型與 Union 型別,這會拉長函數的簽名 (Signature)。