閒聊:Functional Programming 崛起,OOP 物件導向開發正在失寵?
| | | 5 | |
這些年來,Functional Programming (FP,函數式程式設計) 受到愈來愈多開發者青睞,成為不少新世代程式設計的首選,OOP (Object Oriented Programming 物件導向程式開發) 稱霸的榮景不再。
最震驚的莫過於 C++/Java/C# 開發者們,打從程式語言學步階段就不斷被灌輸:要以物件為中心,貫徹封裝、抽象、繼承與多型精神,實現高模組化、可重用與可擴展的程式結構,才能做出偉大的系統。結果幾十年過去,一眨眼世界變了,彷彿 FP 才是王道。
在 Java 領域有 Scala、在 .NET 則有 F#,加上這些年被呼籲用以取代 C/C++,講求速度與安全的 Rust,都是原生支援 FP 的代表。而即使是 Python/C#/JavaScript/Java 等傳統偏向 OOP 的程式語言,也紛紛加入 FP 思維的設計,例如: Lambda Expression、map/reduce/filter、不可變容器... FP 已成顯學。
練拳一輩子的老武師,猛一抬頭,發現大家都在踢跆拳道... 登楞!

為了解答這個疑問,我花了點時間研究這股趨勢的成因,試著了解為何 FP 會被狂推。
FP 之所以能取代 OOP,自然是瞄準了 OOP 的痛點,會如此發展則與時代演進有關。
其實 FP 語言的歷史很早,1930年代數學家 Alonzo Church 提出了 λ 演算(Lambda Calculus),是一種用「函數抽象與套用」來表達計算的純數學模型(所以 Lambda 這個名字是這麼來的,長知識了)。1950–60 年代電腦出現後,數學家試著把 λ 演算思想變成程式語言,LISP (1958)是第一個成功的函數式語言,但後來命令式 / 結構化程式設計 (如 C、Pascal、Fortran) 因為更符合 CPU 的運算特性,成為程式語言的主流,此段時間 FP 只能默默發展,期間出現 ML (1973)、Scheme (1975)、Miranda (1985) 等語言,學界推出的 Haskell (1990) 成為「純函數式語言」的代表。
1990 年後展開 Internet、雲端時代,軟體系統日益複雜,為應付高流量情境需要大量並行運算,傳統命令式的 OOP 設計,在多執行緒環境需解決資料存取衝突問題,開發難度驟增。另一方面,企業級與雲端系統講求可測試、可組合、可推理性,使用 OOP 實踐也會遇上不少困難。
OOP 主打封裝資料及行為,保存狀態並集中商業邏輯,藉此降低耦合度與提升可維護性,有利於大型專案分工與邊界定義。然而,隨著分散式架構、多核平行運算成為日常,共享可變狀態在高並行情境很容易衍生同步化與 Race Condition 問題,常需複雜的額外鎖定與協議控制克服。在高並行情境,處理這類問題的複雜度常超乎想像,於是講求資料不可變的 FP 成了解藥。
FP 的主要特色如下:(註:我試著補上 C# 的 FP 實踐範例方便理解)
- 純函數(Pure Functions):效法數學函式 (例如:f(x) = x * 2) 精神,相同輸入必得相同輸出,不產生任何副作用(不修改外部狀態、不做 I/O 動作),易於推理與測試。
例:Func<int, int, int> add = (a, b) => a + b;,add(1, 2) 只會等於 3,沒有其他可能。 - 不可變(Immutability):要求資料不可變,不共享可變狀態,如此能大幅降低並行情境的 Race Condition 與同步成本。
C# 9推出了 Record 用來封裝不可變資料(Immutable Data),主要也在呼應 FP 應用。 - 高階函數(Higher-Order):函數是一等公民,可作為參數或回傳值,支持組合應用與抽象化。
Lambda 當參數用在 LINQ 已是日常:Func<int, bool> ge5 = (i) => i >= 5; (new [] { 1,2,3,4,5,6,7 }).Where(ge5);。 - 遞迴與組合(Recursion & Composition):以小函數組合大功能,透過函數串接合成取代命令式流程控制。
LINQ 寫法是最簡單的實例:collection.Where(o => o.Count > 3).OrderBy(o => o.Price)...。 - 強型別與型別推導(Type Systems):使用嚴謹強型別系統與推導,在編譯期即可攔截型別錯誤。
- 宣告式(Declarative):描述要算什麼而非怎麼算,貼近數學定義,便於等式推導與重構。
var add = int (int a, int b) => a + b;(C# 10+ Lambda 可明確宣告傳回型別,用 var 取代 Func<int, int, int>),運算邏輯類似數學函式,一望便知。 - 惰性求值(Lazy Evaluation):要用到時才評估計算,提高效率並方便彈性組合。
LINQ 的延遲執行便是一例,忘記了小心踩坑。
而這帶來一些好處:
- 適合平行處理:由於不共享可變狀態,不用擔心平行處理時程式打架,Thread-Safe 渾然天成!
- 測試超級好寫:純函數保證輸入傳什麼不可變資料一定得到什麼輸出,自動測試不需動用複雜 Mocking 模擬物件行為及狀態。
- 易於了解推理:宣告式特性讓邏輯透明,重構與驗證行為更直觀。
在講求高並行性大量平行處理的場景,FP 的特色與好處格外珍貴,於是在金融、電信、雲原生、大數據、人工智慧等領域,許多人會選擇 FP 語言取代傳統 OOP 架構。
至此,我們對 OOP 的缺點與 FP 的好處已有粗淺認識。不過,OOP 與 FP 倒也不是非黑即白漢賊不兩位。傳統 OOP 語言如 Python、C#、JavaScript/TypeScript、Java 在傳統 OOP 主軸外,也開始支援 FP 概念 (最簡單鑑別方法可看是否支援 Lambda 寫法),把程式寫成 FP 風格絕對沒問題。而以 FP 為主的 Scala/Kotlin/F#/OCaml/Swift/Rust 也加減能支援 OOP 的部分特性(不過對於「繼承」,有些語言倒是傾向不支援,例如:Rust、Golang)。
從學術一點的角度來看,OOP 或 FP 可視為程式設計的兩種典範(Paradigm),而所謂的 Paradigm(典範/範式)指的是一個特定時期內,一個群體(如科學社群、學術領域)所認同的基本世界觀、信念、價值觀或方法論,為該領域的成員提供一個共同遵循的模式和模型,一套被廣泛接受的思維方式或研究框架,指導人們如何觀察、思考、提問和解決問題。
科學哲學家托馬斯·S·庫恩提出「典範轉移(Paradigm Shift)」的概念,指一個舊典範不再能解釋現有現象或被新發現取代時,會發生一種根本性的變革或科學革命,整個研究群體的思維方式和方法論隨之改變。例如,從地平說走向地圓說。
在程式設計領域,從最早一路到底的程序式寫法到結構化設計,從結構化到 OOP,再從 OOP 到 FP,這些演進涉及對問題解決方式的重新思考,例如,從以動作為中心轉為以物件為中心,便可視為一種典範轉移。FP 強調不可變性、純函數與數學函數評估,而 OOP 著重封裝、繼承與多型,兩種方法對於狀態管理、程式結構有著截然不同的設計哲學,絕對也是一種典範轉移。
不過程式開發領域的典範轉移,不同於庫恩聚焦的單一性典範(例如:地平說與地圓說只能二選一),「多典範共存」是常態。OOP 物件邊界清晰,能直覺化映對領域實體,易於思考設計,繼承、多型與介面則有利於結構化重用與替換,在複雜領域模型、長生命周期、替換與擴展需求強烈情境仍有壓倒性優勢。當可維護擴充性比高並行、可測試性更重要,堅持用 FP 硬幹便不是個好主意。目前的發展也走向二者共存,像是大部分 OOP 語言都能實現 FP (寫過 LINQ 就等於有在用 FP 了),而 FP 語言也會實做部分 OOP 功能。
一個可行策略是「大範圍 OOP,小範圍 FP」的混合策略,在高層次介面使用物件導向設計維持低耦合易於維護擴充,邏輯實作則視需求採用函數技術滿足高並行需求,OOP 與 FP 摻在一起做成瀨尿牛丸,應該是最好的做法吧。

【延伸閱讀 for .NET 開發者】
- Functional Programming in C# (影片)
- Functional Programming 簡介 by 點燈坊
- The Essential Cheat Sheet: Functional Programming with C# 7.1
This article explain why functional programming offers safer concurrency and easier testing by emphasizing immutability, pure functions, and declarative code—making it popular for modern, scalable systems beyond OOP with C# exmaples.
Comments
# by cash
FP 的話大推這本,我覺得很淺顯易懂,缺點是太大本了 XD https://www.tenlong.com.tw/products/9789863128090
# by YT
是Race Condition嗎?
# by yoyo
OOP常看到有那種明明不是繼承關係的,卻實作成繼承,全部耦合在一起難以修改 例如:多個Web API Request / Response 有些重複的欄位用繼承放一起.. "Favor composition over inheritance" 大多數情況應該用組合而非繼承 OOP適合用在實體物件抽象化,不同的Paradigm,有不同的適合場景
# by Jeffrey
to YT, Yes, 打錯囉~ (錯字人正常發揮)
# by ChrisTorng
我 2021 年有研究過 F#,對於 C# 能手而言,強烈建議試著學習看看! 我是由 https://fsharpforfunandprofit.com/ 學習的,官網應該也不錯 https://learn.microsoft.com/zh-tw/dotnet/fsharp/what-is-fsharp 。有很多 functional 的招式,真是大出 OOP 人的意料之外,沒想到一個 function 可以玩出這麼多花樣。 不過覺得為了保持純粹,不能有副作用,需要額外動腦筋,相較 OOP 就覺得麻煩。另若導入 F#,對一起工作的團隊也是很大的額外負擔,最後還是選擇在 C# 中多導入 FP 的寫法。 我把研究結果整理在 https://github.com/ChrisTorng/FSharpLearning ,裡面大量使用 .dib,就是 https://blog.darkthread.net/blog/dotnet-interactive/ 中介紹的 Polyglot Notebooks。 在其中 3. Domain 資料夾內有我當時如何使用 C# 來撰寫 FP 樣式程式的範例。 不過後來 C# 新版陸續有導入更多功能,像我學過 F# 的人就會覺得很熟悉,認出它們就是 FP 所需要的特性。 雖然我沒有真的把 F# 學起來,沒有真正用它寫什麼程式,但寫 C# 時,會採用 F# 中的一些方法,讓程式更純粹,容易理解及測試。