Functional Programming 近年逐漸受到重視,尤其 ECMAScript 本身具有 Functional 特質 (First-class Function、Closure、Higher-order Function),再加上 React、Redux、RxJS、Ramda … 等推波助瀾,FP 從理論走向實務,但也由於 FP 與 OOP 思維迥異,因此很多人視 FP 為異端而裹足不前,本文整理出 FP 三個最重要心法幫助大家學習。
Introduction
FP 特色之一就是 專有名詞
很多,如 currying、partial application、functor、monad … 等,光搞懂一個名詞就要花很多時間,但這些性質其實都是從 FP 三個最基本特性所衍伸,也就是要學好 FP,應該先從最基本觀念建立起,然後再逐步向外學習其他衍生特性。
FP 本質有三:
- Decouple Data and Function
- No Side Effect
- Function Composition
Decouple Data and Function
傳統 OOP 教我們 資料
與 功能
合一,以 職責
的角度,將 資料
與 功能
封裝在 class 內,在實務上很容易遇到以下問題:
- 由於資料與功能合一,但功能不斷增加,很容易單一 class 有太多功能,但由於這些功能又與該資料有關,放在其他 class 又不適合,導致 class 爆炸
- 若不同資料有相同功能,放在哪一個 class 都很尷尬
FP 則是強調 資料
與 功能
分離:
資料
僅以 array 與 object literal 表示,不另外建立型別功能
僅以 function 表示,可自行將 function 放在合適 module,與資料
無關
也由於 FP 的資料與功能分離:
- 功能不斷增加,只要不斷新增 function,再重構到適合的 module 即可,因此沒有單一 class 爆炸問題
- 由於 module 與 data 是獨立的,因此容易重複使用,也不會有該功能該放在哪一個 class 的尷尬
No Side Effect
傳統 OOP 最小顆粒是 class,class 對外而言是封裝,但對內則無,因此 class 內部 method 以 this
去存取 field,在實務上很容易遇到以下問題:
- Class 並不見得由同一個人寫,也就是由團隊不同人維護不同 method,因此要維護 method 時,必須很清楚 field 的來龍去脈,因為 bug 通常就是來自於其他 method 修改了 field
- 由於需求改變,功能不斷增加,單一 class 很容易 method 過多而爆炸,因此會想要重構到新 class,但由於各 method 共用 field 盤根錯節,要重構到其他 class 並不容易
- 有些 method 並沒有回傳值,其目的只是修改 field,但這些 field 可能根本沒有 getter,導致 unit test 困難
FP 的 function 都是 pure function,也就是 function 只跟 argument 有關,且一定要有回傳值:
- 儘管一個 module 由團隊不同人維護不同 function,但因為 function 只跟 parameter 有關,只要搞懂該 function 即可,也不用擔心被其他 function 影響
- 當單一 module 的 fuction 過多時,只要建立新 module,再將 function 剪下貼上即可,不用擔心共用 field 問題,非常容易重構
- Pure function 只跟 parameter 有關,又一定有回傳值,因此 unit test 非常容易,只要提供 argument 的 stub,並且驗證結果即可
Function Composition
程式只要寫大,就會有重複程式碼問題要解決,傳統 OOP 使用 繼承
,這會造成 子類別
與 父類別
的 強耦合
,父類別所新增的 method 會讓子類別強迫概括承受。
後來 OOP 建議改用 class composition,也就是將重複的部分抽成新 class,使用 DI 注入,在實務上很容易遇到以下問題:
- 為了讓 class 單一職責,我們希望 class 顆粒盡量小,但這又會造成 DI 注入爆炸
- 若要將 class 單一職責到極致,常常會看到一個 class / interface 只有一個 method,造成檔案數目爆炸
FP 則是使用 function composition,將重複部分抽成小小 function,再以 higher-order function 組合成新 function:
- Function 顆粒遠比 class 小,徹底實踐單一職責,重複使用率超高
- 不會造成 DI 爆炸,只是 import 很多而已,但 import 很多也沒什麼關係
- 不會再有一個 class / interface 只有一個 method 的窘境,檔案數目大幅減少
對於常用的 higher order function,如 RxJS、Ramda、Crocks 已經提供很多,我們只需寫與商業邏輯相關的小 function,然後組合 library 所提供的 function 即可
Conclusion
- 初學者在學習 FP 時,只要時時想到這三點,要求自己這樣寫程式,FP 功力就會慢慢進步
- 儘管某些 framework 還是使用 OOP 設計,但盡量將這三點融合在 OOP 內,也能使程式更容易維護
- FP 關鍵在於 function composition,因此熟悉 Ramda 或 RxJS 的 function 是必要的