iT邦幫忙

2025 iThome 鐵人賽

DAY 4
0
Modern Web

用 Effect 實現產品級軟體系列 第 4

[學習 Effect Day4] 為什麼需要 Effect

  • 分享至 

  • xImage
  •  

程式中的副作用

在程式開發中,「副作用」指會影響外部世界的操作,例如記錄日誌、發送網路請求、存取資料庫、寫入檔案,甚至 console.log。相對地,在記憶體中計算 1 + 1 則屬於不影響外界的純計算。

因此,程式若要真正「有用」,就離不開副作用;沒有輸出、沒有持久化、沒有畫面,軟體就毫無價值。

副作用的麻煩事

副作用讓系統「能做事」,但也最容易失控:

  • 容易出現未處理的錯誤。
  • 非同步流程難管理(取消、超時、重試)。
  • 資源釋放常被忽略(例如遺漏資料庫連線釋放)。
  • 邏輯分散,導致難以測試與維護。

真正棘手的,往往不是程式本身是否正確運行,而是副作用伴隨而來的錯誤、併發與資源管理問題。

我們需要 Effect 的原因

要打造生產等級(production‑grade)的軟體,妥善處理副作用是不可避免的;而 Effect 提供一種宣告式(declarative)的方法來處理它。

在程式中,我們用泛型外觀 Effect<Success, Error, Requirements> 來描述一段計算:它在某個需求環境 (Requirements) 中執行,成功時產生 Success,失敗時產生 Error。要注意的是,Effect 是 effect library 定義的一種函數式資料型別 (data type)。它不是 TypeScript 內建的型別,而是用來抽象描述「副作用運算」的型別結構。所以雖然在 TypeScript 裡 Effect 確實還是個「型別」,但在函數式編程(FP)的語境,會特別稱它為「資料型別 (data type)」,因為它表達的不只是靜態結構,而是一個「可組合、帶行為的抽象」。

此外,Effect 是
1.「惰性(lazy)」的,只描繪要執行的過程,不直接執行。
2. 具有組合性,可以與其他 Effect 進行組合,組合後的結果也會是一個 Effect。
3. 具有 immutable(不可變)的特性,一旦被定義,程式要如何運行就被確定了,不會再改變。
因為上面三個特性,讓我們可以先以宣告式的方式組裝流程,之後再透過 Effect 模組提供的各種 run function 來執行。這也讓我們在錯誤處理和依賴資源的管理上,可以更輕鬆準確的控制。

傳統作法的侷限

傳統的 Promise 與 async/await 確實改善了非同步的可讀性,但仍有明顯限制:

  • 資源釋放無保證:等待結果容易,但難保過程失敗時一定釋放資源(例如檔案/連線)。
  • 錯誤型別不明確:try/catch 能攔錯,但型別系統不會告訴你「可能發生哪些錯誤、哪些需要處理」。
  • 缺乏一致模型:難以在型別/介面層面描述需求(設定、連線、權限),也難預告潛在失敗點。
  • 跨功能的一致作法難落地:像取消、超時、重試、觀測和資源釋放,常散落在各處、寫法各一套,難以統一與重用。

Effect 是什麼

Effect 的設計理念是:既然副作用無法避免,就把它變成「可描述的管線節點」來組裝起來,讓副作用以可描述、可推理、可組合的方式存在。

Effect 的型別哲學

Effect 提供三個泛型型別 Effect<Success, Error, Requirements>

  • Success:成功回傳的結果(成功通道)
  • Error:可能失敗的錯誤類型(錯誤通道)
  • Requirements:這個副作用需要什麼(環境 / 依賴)

小詞彙表(例子):

  • Success 範例:Usernumbervoid
  • Error 範例:NotFoundErrorTimeoutErrorPermissionError
  • Requirements 範例:記錄器(Logger)、資料庫連線、環境設定(Config)

把副作用封裝成 Effect<Success, Error, Requirements> 後,你可以在型別層面精準描述需求與風險,並用一組可重用的操作來安全地串接、並行、重試、超時,並確保資源取得與釋放。程式從「滿地亂飛的副作用」變成「一段段可描述、可組裝的生產線」。

五個關鍵價值

  • 錯誤處理(雙通道與型別化):成功與失敗被清楚分開、同等對待;錯誤也會被標示在型別裡。當流程由多個步驟組成時,所有可能的錯誤會被彙整並向上標示,讓開發者在編譯期就能看見並決定如何處理。
  • Lazy evaluation 與 Aspect-Oriented Programming (AOP):Effect 延遲執行,使「重試、退避、超時、熔斷、快取、批次」等橫切關注(cross-cutting concerns)能在「執行之前」以組合方式掛載,達到類 AOP 的效果而不污染商業邏輯。
  • 依賴注入(Dependency Injection, DI):以 requirements 顯式表達需求,呼叫端不必了解底層實作或連線細節,只要聲明需要的服務即可。
  • 上下文傳遞(Context as dependency):像 requestIdLogger 這類每個請求才會有的資訊,不需要依賴 AsyncLocalStorage(Node.js 提供的全域非同步儲存 API)。Effect 採用 service 抽象來管理這些依賴,你只需在請求的入口設定一次,內層邏輯需要時可直接從 Context 取出,而不用把參數一路往下傳遞。
  • 測試友善:可直接注入「函數」或最小替身,避免為了 stub 而攜帶整個類別;亦可用 Layer 在建立服務時一次性提供替身,讓單元測試與整合測試更輕量且可預測。

更好的非同步錯誤處理機制與資源管理機制(這裡講的比較抽象,未來會深度講這個問題)

  • Promise:像是一條沒有標準作業流程的生產線,產品能推出來就算完成;但一出狀況,很難在過程中觀察、控制與介入,呼叫方也很難從介面看出需要哪些前置條件(如設定、連線、權限),或預測可能的失敗點。
  • Effect:像是一條有標準作業流程與檢查點的生產線,每個站點都明確記錄「需要的前置條件、可能的失敗、與輸出的結果」,還能加上開關(取消)、計時(超時)、備援(重試/回退),並確保收尾時會做清場(資源釋放)。

何時不需要 Effect

不是每個情境都值得導入 Effect。當你追求的是最短開發時間,或問題本身非常單純時,Effect 反而可能成為額外負擔。幾個常見情況如下。

首先,如果你的程式只涉及純計算或單純資料轉換(沒有任何 I/O),像是字串與日期處理、資料驗證或演算法實作,Effect 帶來的抽象就未必合算。這類邏輯本來就容易以純函式表達,可靠性問題也有限。

其次,在一次性腳本、極簡 demo、或短命的 POC 階段,目標是「快速得到回饋」。此時長期的可靠性、觀測性與可維護性不是優先,導入 Effect 的學習與設置成本通常超過收益。

再者,若問題空間很簡單,或外部平台已提供你需要的穩定機制(如自動重試、超時、熔斷,就不必再自建一套 Effect 化的可靠性模型;把握邊際效益即可。有一種常見情境是「邊界很薄,且由框架/SDK 接管副作用」。所謂接管,指的是 SDK 內部已處理了 HTTP/連線池、認證、重試、超時與錯誤分類,並以一致的回傳格式暴露結果。你的程式只負責把請求參數交給 SDK,再把回傳值往上層傳遞。例如呼叫雲端儲存或金流的官方 SDK:你通常只需 await sdk.doSomething(),其間的網路可靠性、資源管理與錯誤分類多半已被 SDK 標準化。若你的業務邏輯在這層幾乎不加工,於此處再以 Effect 建模,就可能沒有太多實質收益。

最後,還有組織面的考量:若團隊目前不具備 Effect 經驗,或專案處於高度趕工階段,學習與遷移成本可能壓過短期回報。此時維持現狀,並在關鍵路徑做最小增量優化,通常更務實。

實務上也可以採「混合導入」:把 Effect 用在 I/O 密集、需要取消/超時/重試/資源安全保障的核心路徑;其他輕量路徑就維持現有的 Promise/async 寫法,避免全面改寫的風險。

簡易判斷清單:

  • 這段程式是否包含 I/O,且需要取消、超時、重試或嚴格的資源釋放?
  • 是否希望在編譯期就看到並統一處理錯誤型別(而非 unknown)?
  • 是否需要跨層/跨請求的上下文傳遞(如 LoggerrequestId、權限)?
  • 是否需要更容易的替身注入與可組合的 DI?
  • 若以上多數回答為「否」,此處先不導入 Effect 也可。

Effect 的成長趨勢

在學習一個新工具時,了解趨勢能幫助我們保持動力。下圖為 GitHub 上 Effect 的星標數趨勢;自 2023 年下半年起成長明顯,值得關注與評估。

effect-star-history

下一步

接下來我們會正式開始講解 Effect 的語法。

參考資料:


上一篇
[學習 Effect Day3] 從 POC 到 Production(二)
下一篇
[學習 Effect Day5] 建立 Effect 專案 (一)
系列文
用 Effect 實現產品級軟體10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言