第七章:信仰之战:面向对象(OOP) vs. 函数式编程(FP)
欢迎回来,时空旅行者。在上一部分,我们建立了“对象工厂”(C++/Java)1,甚至还拿到了23份标准“蓝图”(设计模式)1。我们以为自己已经驯服了“代码山”。但我们只是把混乱隐藏在了漂亮的盒子后面。
现在,我们将要见证一场长达三十年的宗教战争。这场战争的激烈程度堪比“Vim vs. Emacs”,但赌注要大得多。这是一场关于如何管理复杂性的根本性“信仰之战”。
为什么会有“战争”?因为面向对象(OOP)和函数式编程(FP)都是为了解决同一个“原罪”而诞生的。这个“原罪”,我们在第四章1见过,就是C语言那种“过程式编程”带来的混乱。在那个世界里,数据(变量)和操作数据的函数(流程)是分离的、不受约束的,它们在全局状态的“变量海”里野蛮生长,导致了灾难2。
这场战争的核心问题是:如何驯服“状态”(State)这个恶魔?
- OOP的方案: 把恶魔(状态)关进一个坚固的盒子里(对象),然后只给它开几个小窗户(方法)对外交流。
- FP的方案: 假装恶魔不存在。如果可能的话,直接一枪毙了它(不可变性)。
准备好你的爆米花。这场面曾经非常难看。
第一部分:原罪(以及为什么我们需要一位救世主)
1.1 过程式编程的原始汤
让我们先回到我们的“舒适区”:过程式编程(Procedural Programming)。在C和早期BASIC的世界里1,我们没有“范式”;我们只有一个闪烁的光标和一份即将到期的最后通牒。
这个世界的决定性特征就是“混乱”。代码的维护者,就像一个考古学家,试图从一堆纠缠不清的电线中理清思路。
“意大利面条”怪物
我们在第三章1谈论过GOTO如何制造了“意大利面条式代码”(Spaghetti Code)。但这只是问题的一半。GOTO制造了流程上的混乱。而一个更阴险的怪物,则在数据上制造混乱。
这个怪物,就是全局状态(Global State)。
在过程式编程中,数据是“全局”的,任何函数都可以随时随地读取和修改它2。这就像一个社区的公共冰箱。你放进去一个三明治,五分钟后回来,发现三明治没了,里面多了一只袜子。是谁干的?什么时候干的?你无从得知。
这就是维护一个大型过程式代码库的日常体验。你在一个函数里修改了一个全局变量 g_bIsUserLoggedIn,却神秘地导致了三千行代码之外的另一个模块(比如支付模块)彻底崩溃3。这种“幽灵般的远程行动”(Spooky Action at a Distance)让调试变成了一场噩梦。
“雪花”隐喻
Reddit上的一位天才诗人(或者说,是一个被伤透了心的程序员)4,用一个“雪花”的比喻完美地描述了这个问题:
“每个程序员,当夜深人静、独自一人的时候,都会写出一段代码。这段代码是完美的‘小雪花’4。它命名规范、逻辑简洁、优雅高效。
然后,星期五,老板告诉你,下周二之前要交付六百片雪花。所以你开始作弊,你复制粘贴,你把几片雪花粘在一起。接着,你的同事为了赶工,把你的雪花拿去‘融化’了一半。
最后,所有人的雪花都被倾倒在一个不知所谓的巨大土堆上,有人在上面靠了一幅毕加索的画,因为没人想看到那堆混杂着猫尿、在日光下融化的、破碎的雪花4。”
这就是规模化(Scale)的诅咒。你的“完美小雪花”(单个函数)在与数百个其他函数共享同一个混乱的“全局状态”时,必然会崩溃5。我们迫切需要一种新的哲学。
1.2 混乱的“双子恶魔”
我们所面对的这场混乱,有两个源头:
- 不可预测的流程: 以GOTO为首的无限制跳转,让我们无法在静态阅读代码时,预测它在运行时的执行路径1。
- 不可预测的数据: 以“全局变量”为首的共享可变状态,让我们无法在任何时刻,预测一个变量的当前值2。
一个程序的核心就是“流程控制 + 数据控制”。而在过程式世界里,这两者都处于无政府状态。这不再是“Bug谷”1;这是“Bug地狱”。
为了解决这个“双子恶魔”,人类的智慧分裂成了两个截然不同的宗教。
- 宗教A(OOP): 让我们通过建造高墙来驯服混乱。我们将数据(状态)锁在坚固的盒子里,严格控制谁可以访问它。
- 宗教B(FP): 让我们通过放逐来消除混乱。我们将彻底禁止“可变状态”的存在。如果数据永远不能被“改变”,那么“不可预测的改变”自然就消失了。
第二部分:OOP福音书(用“盒子”驯服混乱)
于是,面向对象编程(OOP)登场了。它闻起来像新车的塑料味,手里拿着一份企业销售合同。它向饱受折磨的C程序员们承诺了一个更有序、更文明的世界。它的“福音书”基于四大信条,或者说,“四大支柱”6。
2.1 新宗教的四大支柱(和它们的Meme版解释)
让我们用(从研究中扒出来的)现代Meme和滑稽类比,来翻译一下这些当年听起来无比庄严的术语。
-
封装 (Encapsulation)
-
抽象 (Abstraction)
-
继承 (Inheritance)
-
多态 (Polymorphism)
为了让你(我们这种老派程序员)快速理解,这里有一份“营销材料 vs. 现实”的速查表:
| 四大支柱 | 营销口号(Meme版) | 资深老鸟看到的“小字说明” |
|---|---|---|
| 封装 | “全自动咖啡机” 7 | “保证数据安全……直到某个不孝子类(subclass)从地基挖地道进来。” |
| 抽象 | “柱子后的人” 9 | “把烂摊子藏起来了……但烂摊子还在那里。” |
| 继承 | “Dog is-a Animal” 12 | “Square is-a Rectangle……直到它翻车。”(LSP警告) |
| 多态 | “万能遥控器” 12 | “唯一真正有用的支柱,但‘继承’总想抢它的功劳。” |
隐喻背后的致命缺陷
请注意,这些类比本身就暴露了OOP的致命缺陷。
“咖啡机”(封装)和“柱子后的人”(抽象)的类比是强大的。它们描述的是“边界”和“隐藏复杂性”,这是坚实的工程原则。
但“Dog is-a Animal”(继承)的类比是脆弱的。它描述的是“分类学”,一个生物学或哲学上的概念10。
在实践中,继承(分类学)恰恰是封装(边界)的头号敌人。子类(Dog)对于父类(Animal)来说,并不是一个“黑盒”。子类拥有“受保护的”(protected)访问权限,它实际上是在父类的边界之内的。
OOP的营销手册上写着四大支柱,但其中一个(继承)正积极地试图推倒另一个(封装)。这个根本性的内部矛盾,就是日后所有灾难——“脆弱的基类”14和“大猩猩-香蕉”15问题——的震中。
2.2 先知的哀叹(艾伦·凯到底在想什么?)
最讽刺的部分来了。那个发明了“面向对象编程”这个词的人——艾伦·凯(Alan Kay)——打心底里鄙视我们今天称之为“OOP”的东西(尤其是C++和Java)16。
这就像你发明了“摇滚乐”,结果全世界都以为你说的是“二人转”。
艾伦·凯的原始愿景,深受生物细胞的启发17,并在Smalltalk语言中实现18,根本不是关于“类”和“继承”的。他的核心思想是:
-
消息传递 (Messaging)
对象之间不“调用方法”。它们像生物细胞一样,互相传递异步消息17。一个对象(比如按钮)只是“通知”另一个对象(比如窗口):“嘿,我被点击了。”19 至于窗口收到这个消息后是关闭、变色还是爆炸,那是窗口自己的事。这是“通知”,而不是“命令”。 -
绝对封装 (Total Encapsulation)
不只是封装数据。艾伦·KAY想封装的是整个运行环境,包括编程语言和硬件20。他设想的每个“对象”,都像一台微型、独立的计算机。 -
极限后期绑定 (Extreme Late-Binding)
所有的决策都尽可能推迟到运行时再进行21。
我们得到了什么?
C++和Java彻底歪曲了他的意思。它们没有实现“消息传递”,而是实现了“早期绑定的方法调用”。它们把OOP变成了“类(Class)导向的编程”21,或者用艾伦·凯的原话说,只是“更智能的数据结构”22。
真正的OOP是“微服务”
这就是最精彩的“时空笑话”。
-
艾伦·凯的“真OOP”愿景是什么?
独立的实体(对象)、完全封装(不共享状态/数据库)、通过异步消息(他甚至提到了“URL”)20进行通信、与语言无关。 -
我们今天“时髦”的“微服务架构”是什么?
独立的服务(Service)、完全封装(每个服务有自己的数据库)、通过异步消息(HTTP/JSON/gRPC)进行通信、与语言无关。
它们是完全相同的概念。
整个行业采纳了“假OOP”(C++/Java巨石应用),制造了“假OOP”的史诗级灾难(紧耦合)。为了解决这些灾难,我们(在40年后)发明了“微服务”,却意外地“重新发明”了“真OOP”。
OOP的解药……竟然是OOP。这种宇宙级的黑色幽默,足以让我们这些老家伙笑出眼泪。
第三部分:大分裂(来自长老的咆哮与OOP的耻辱柱)
这个“假OOP”宗教的裂痕很快就出现了。计算科学的先知们(我们的老同事)开始(非常大声地)咆哮。
3.1 长老的咆哮(OOP审判大会的A、B、C号证物)
证物A:艾兹格·迪杰斯特拉 (Edsger Dijkstra) 的诅咒
就是那位在第三章1绞杀了GOTO的荷兰先知。他对OOP的评价同样刻薄:
“面向对象编程是一个极其糟糕的想法,它只可能起源于加利福尼亚。” 23
(更有趣的是,这个笑话本身就是错的。OOP起源于挪威(Simula语言)24,但加州(Xerox PARC)确实将其发扬光大了。)
迪杰斯特拉的真正愤怒在于,他认为OOP是一种“蛇油”(snake oil)25,一种分散注意力的营销噱头,它让程序员们沉迷于构建拙劣的模型,而忽视了真正严肃的、数学化的“形式化验证”。
证物B:莱纳斯·托瓦兹 (Linus Torvalds) 的暴怒
Linux和Git的创造者,一个以“脾气好”而闻名的芬兰绅士(不)。当被问及为什么Git不用C++时,他回复了一封堪称“咆哮艺术”的邮件:
“你(提问者)才充满了狗屎(bullshit)。C++是一门糟糕的语言……它吸引了大量不入流的程序员,以至于用它制造出彻头彻尾的垃圾变得轻而易举……所以对于Git这样以效率为首要目标的项目,C++的‘优势’就是个巨大的错误。而我们能顺便惹毛那些看不清这一点的人,则是一个巨大的额外优势。” 26
他的理由非常务实:C++“在背后隐藏了内存分配”27,“异常处理机制从根本上就是坏的”(尤其对内核代码而言)27,它“只是在C语言之上添加了一些渣滓”28。
证物C:乔·阿姆斯特朗 (Joe Armstrong) 的“香蕉”
Erlang语言的创造者乔·阿姆斯特朗,贡献了编程史上最伟大的隐喻。当被问及OOP的缺点时,他说:
“面向对象语言的问题在于,它们总是携带着一堆隐式的环境。你想要一根香蕉,但你得到的却是一只拿着香蕉的大猩猩,以及大猩猩身后的整片丛林。” 15
这个比喻完美地控诉了“紧耦合”(Tight Coupling)和“继承”的失败。
你只是想在你的小程序里用一下Banana类。但不幸的是,Banana类 extends Gorilla(继承自大猩猩类),而Gorilla类又 extends Jungle(继承自丛林类)。现在,你那个只想显示“Hello World”的小程序,为了得到这根香蕉,被迫 import (导入)了整个热带雨林生态系统15。
3.2 OOP耻辱柱(他们到底在咆哮什么?)
这些长老们不是在无能狂怒。他们是在回应OOP范式中真实存在的、教科书级别的设计缺陷。
耻辱柱一:“正方形‘是’一个矩形”的谎言(里氏替换原则, LSP)
这是对“is-a”继承的经典处刑。它揭示了“里氏替换原则”(Liskov Substitution Principle, LSP)的违反29。
- 逻辑是这样的:
- 一个
Rectangle(矩形)类的“契约”是:它的setWidth(10)和setHeight(5)方法是独立的29。 - 从数学上讲,一个
Square(正方形)is-aRectangle(是一个矩形)30。所以我们让Square继承Rectangle。 - 但是,为了维持自己“正方形”的特性,
Square类必须违反父类的契约。当你调用setHeight(5)时,它必须同时强制将Width也改为5 29。 - 灾难发生: 一个函数开心地接收了一个
Rectangle(它以为的)。但你传进去一个Square。函数调用rect.setWidth(10),然后调用rect.setHeight(5)。它期望rect.getArea()返回50 (10 * 5)。 - 它得到了25 (5 * 5)。 29
- 程序崩溃,客户尖叫,项目经理秃顶。
- 一个
- 结论: 基于“分类学”(它是什么)的继承是一个陷阱。继承必须基于“行为学”(它做什么)31。
耻辱柱二:脆弱的基类(The Fragile Base Class)
这是证明“大猩猩-香蕉”问题的噩梦场景14。它证明了封装(在继承面前)就是个笑话。
- 逻辑是这样的:
- V1.0版本:
Base(基类)有两个方法:inc1() { counter++ }和inc2() { counter++ }。 - 你写了一个
Derived(派生类),重写了inc2(),使其“更高效”:inc2() { inc1() }。没问题,代码运行良好。 - V2.0版本: 几个月后,
Base类的作者决定“重构”代码,遵循“DRY”(Don't Repeat Yourself)原则。他“安全地”修改了Base类,让inc1()去调用inc2():inc1() { inc2() }。他认为这只是一个内部实现细节的修改。 - 灾难发生: 你的
Derived类(你根本没碰过)现在在调用inc2()时,会触发无限递归:Derived.inc2()调用Base.inc1()Base.inc1()调用Base.inc2()Base.inc2()被动态派发(dynamic dispatch)到你重写的Derived.inc2()Derived.inc2()再次调用Base.inc1()...
- StackOverflow(栈溢出)。14
- V1.0版本:
- 结论: 父类的一个“安全的”、“内部的”修改,摧毁了子类。OOP的“封装”承诺(“你不需要知道内部实现”)在继承面前碎了一地。基类是“脆弱的”14。
耻辱柱三:企业级笑话(AbstractSingletonProxyFactoryBean)
这是OOP在“文化”上的彻底失败。
“设计模式”1本应是大师的蓝图,最后却沦为新手的教条32。尤其是在Java企业界,“工厂模式”(Factory)1泛滥成灾,导致了“工厂的工厂,无处不在”的Meme 33。
34提供了一个完美的例子:在Java里,你可能需要10行(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse()...)令人作呕的“工厂”样板代码,只为了解析一个XML文件。而C#(具有讽刺意味的是,也是OOP语言)只需要2行34。
这就是我们为了创建对象,而创建FactoryFactory(工厂的工厂)35的官僚主义编程。这就是莱纳斯27所鄙视的“低效的抽象模型”。
连接所有线索:继承是万恶之源
现在,把这三根耻辱柱连起来看。
LSP(正方形问题)29是“is-a”继承的问题。
脆弱的基类14是实现继承的问题。
大猩猩-香蕉15是继承导致的紧耦合问题。
你发现了吗?OOP的四大支柱中,有一个支柱(继承)是腐烂的。
那么,“设计模式”和“组合优于继承”11的口号是怎么来的?它们就是变相的道歉!它们是OOP社区为了绕过“继承”这个破碎的柱子而发明出来的“脚手架”。
OOP社区(心照不宣地)承认了它的核心机制之一已经破产,并花了二十年时间发明各种“模式”来避免使用它。
一场“宗教改革”已经不可避免。
第四部分:函数式(FP)宗教改革(回归冰冷、美丽的数学)
如果说OOP是一个充满了潜规则、密室政治和官僚主义的臃肿帝国,那么函数式编程(FP)就是一座寂静、极简、人人平等的修道院。
它不是什么新思想。它是一个非常古老的思想(源自20世纪30年代的“Lambda演算”36和50年代的LISP语言37),只是在OOP帝国即将崩溃时,它才作为“革命纲领”被重新发现。
4.1 新教规:汝不可“改变”任何事物
FP的教义建立在两条“铁律”之上,旨在从根源上铲除“不可预测性”。
铁律一:不可变性 (Immutability)
- 教义: “数据皆为花岗岩”38。
- 解说: 你永远不能“修改”一个变量。永远。
- 问: 那我怎么给变量x加1?
- 答: 你不能。你只能创建一个全新的变量y,它的值等于x+139。旧的x(值是5)永远是5,直到宇宙毁灭。
- 意义: 这听起来效率低下得令人发指,但它用一种极其野蛮(也极其优雅)的方式,彻底解决了“全局状态”问题40。如果任何数据都不能被改变,那么“幽灵般的远程行动”就无从谈起。这也顺便让“并发编程”(第八章1)从地狱难度瞬间降到了简单模式38。
铁律二:纯函数 (Pure Functions)
- 教义: “你的函数必须是一只‘好狗’”41。
- 解说: “纯函数”有两个严格的条件:
- 只吃自己的狗粮: 它所有的计算只依赖于它的输入参数。
- 不随地大小便: 它不会对函数外部的世界造成任何“副作用”(Side Effects)42。
- 举例:
- 纯函数:
Math.cos(x)。给定x=0,它永远返回1。它不读写全局变量,不联网,不打印到屏幕。 - 不纯函数:
FileReader.read("config.txt")。给定相同的输入("config.txt"),它今天可能返回"A",明天可能返回"B"(如果文件被修改了)。而且它有一个可怕的“副作用”:它移动了文件系统的“文件指针”。
- 纯函数:
- 43中有一个绝妙的比喻:“水本身不是幼虫的孵化器,停滞不动(stagnant)的水才是。副作用也是如此。” FP不禁止副作用,但要求你把它们(比如数据库写入)严格“隔离”和“控制”起来。
4.2 神圣三位一体:Map, Filter, Reduce
OOP有它的“四大支柱”(名词),FP也有它的“三大支柱”(动词)。它们是函数式程序员的“圣三位一体”,是处理列表(Collection)的万能工具。
让我们用44和44中那个简单、强大的“形状”类比来翻译它们:
- map (映射)
- filter (过滤)
- reduce (规约)
“圣三位一体”的真相:它们只是for循环的“马甲”
这“三大法术”听起来很神奇,但对我们这些老家伙来说,这玩意儿一点都不新鲜。它们只是我们用了三十年的三种最常见的for循环的抽象马甲46。
-
map 是什么?
它是“转换型for循环”:for (i=0; i<len; i++) { newList[i] = transform(oldList[i]); } -
filter 是什么?
它是“筛选型forB循环”:for (i=0; i<len; i++) { if (test(oldList[i])) { newList.add(oldList[i]); } } -
reduce 是什么?
它是“累加型for循环”:for (i=0; i<len; i++) { accumulator = combine(accumulator, oldList[i]); }
FP的“圣三位一体”并不是什么外星科技。它只是用一种更干净、更声明式(Declarative)的方式,来命名我们早已在命令式(Imperative)世界中重复了无数次的三种模式。
4.3 驯服上古巨兽(那些吓跑新手的术语)
FP改革之所以花了这么久才成功,因为它带来了一群“学术神棍”。他们不说人话,满口都是“范畴论”、“幺半群”和“函子”。让我们来戳穿他们。
巨兽一:Lambda 演算
这是FP的“汇编语言”。我们不需要懂数学。我们只需要知道那个关于“鳄鱼蛋”的搞笑故事就够了47。
这个类比把“Lambda演算”描绘成一个游戏:
- “饥饿的鳄鱼” 代表 “Lambda抽象”(即一个函数定义)。
- 它们会“吃掉” 旁边的 “鳄鱼家族”(即函数参数)。
- 当鳄鱼吃掉参数后,它家族里和它同色的“鳄鱼蛋”(即变量)就会“孵化”成它刚刚吃掉的东西(即“参数代换”或“Beta-reduction”)。
这个荒诞的故事,比任何教科书都能更好地解释FP的底层计算模型47。
巨兽二:Monad (单子)
啊,Monad。编程界最大的智商过滤器。
道格拉斯·克罗克福德(JSON的推广者)曾定义过“Monad的诅咒”:“一旦你顿悟了它是什么,你就立刻失去了向别人解释它的能力”48。
让我们来打破这个诅咒。
- 最简单的解释: Monad是一个“包装盒”48。它是个“墨西哥卷饼”49。它是一个“可编程的句号”50。
- 它到底有什么用? 它是一个标准化的“包装盒”,用来安全地“容纳”那些“不纯洁”或“有危险”的值,以便让你的其他代码可以保持“纯洁”。
- 杀手级范例:Maybe Monad
我们都写过这种防御性代码:48
if (user!= null && user.getAddress()!= null && user.getAddress().getStreet()!= null) {... }
Maybe(或称Optional)48 Monad就是为了消灭这种代码而生的。它是一个“盒子”,这个盒子里要么装着Just(value)(“有东西”),要么装着Nothing(“啥也没”)。
你不需要再写if (user!= null)。你直接在“盒子”上“链接”你的操作。如果盒子里是Nothing,“盒子”的map方法会自动跳过你的操作,直接返回Nothing。
Maybe(user).map(u => u.getAddress()).map(a => a.getStreet())
这个Maybe“盒子”,就是一种用来安全处理“空值”(null)这种不洁物的标准化容器。这就是Monad的全部意义:用一个“纯洁的”包装盒,去隔离和控制“不纯洁的”副作用(如null、异常、I/O等)。
FP的终极讽刺:Monad就是个“好对象”
现在,请你退后一步,用我们“恐龙”的眼睛审视一下这个叫Maybe的“Monad”:
- 它是一个“包装盒”48。
- 它封装了一个“状态”(
Just(value)或Nothing)。 - 它提供了一套公共的方法(如
.map(),.bind())来让你与这个状态交互。
一个封装了状态、并提供了公共方法来操作这个状态的“盒子”……
老天爷,这不就是“对象”(Object)的定义吗?!
这就是最大的讽刺。FP的死忠粉们,在他们逃离“邪恶的”C++/Java OOP的漫长征途中,最终(再次)意外地“重新发明”了“纯正的”OOP——艾伦·凯最初设想的那个“基于消息传递的、完全封装的”对象模型。
他们只是给它起了个吓人的数学名字。
第五部分:混合主义的胜利(战争结束,人人有糖吃)
事实证明,OOP vs. FP 并不是一场你死我活的战争,而是一场长达三十年的“观念融合”51。火焰节(flame war)结束了,灰烬之中站起来的是“多范式”(Multi-paradigm)的现代程序员52。
我们(老家伙们)赢了,因为我们现在有了两套工具。
5.1 停战协定A:C# 和 LINQ“特洛伊木马”
C# 是微软对标Java的、终极的、企业级的OOP语言。然而,FP就是通过这门语言,对企业界发动了最成功的“特洛伊木马”攻击。
这个“木马”的名字叫LINQ (Language-Integrated Query) 53。
微软的天才们把FP的“圣三位一体”伪装成了C#的方法,无缝地植入了这门OOP语言54:
| FP“圣三位一体” | C# LINQ“特洛伊木马” | “恐龙”翻译手册(它在干啥) |
|---|---|---|
| map (映射) | .Select() 55 |
“把这堆方块挨个变成圆圈。” |
| filter (过滤) | .Where() 56 |
“把不是三角形的都给我扔了。” |
| reduce (规约) | .Aggregate() 57 |
“滚雪球。把这一堆玩意儿给我融成一个。” |
LINQ的真正革命性,甚至不在于这些方法。而在于它的声明式语法(Declarative Syntax)58。
命令式 (Imperative) for循环(我们过去的写法):
“嘿,CPU。创建一个新列表。然后从0循环到100。检查customers[i].City是不是等于"London"。如果是,就把customers[i].Name加到新列表里。循环完了告诉我。”
声明式 (Declarative) LINQ(FP的写法):58
from c in customers where c.City == "London" select c.Name
这看起来就像SQL58。你没有告诉计算机“如何”去做。你只是声明了你“想要什么”(What)59。
LINQ是一个伪装成OOP功能的FP引擎。它在十年间,不知不觉地把数百万企业级Java/.NET程序员,训练成了半个函数式程序员。
5.2 停战协定B:JavaScript/React 的悖论
JavaScript是多范式语言的终极体现——一个塞满了OOP、过程式和函数式思想的、混乱而又充满活力的工具箱60。
然而,当今世界上最流行的UI库——React——在骨子里是极度函数式的。
- 组件即纯函数: React要求你的UI组件必须是“纯函数”61。给它相同的props和state(输入),它必须返回完全相同的UI(JSX输出)62。
- 副作用被严格隔离: 你不准在渲染(render)过程中搞小动作(比如API请求或修改localStorage)。所有的“不纯”行为都必须被关在
useEffect这个“隔离病房”里63。 - 不可变性即法律: 你不准直接修改状态(
this.state.name = "Bob")。React强制你使用useState或useReducer64。你必须调用setTodos()64。这正是FP的“不可变性”铁律。
React用实践证明:在处理UI这种极度复杂的、不可预测的“状态”时,FP的原则(纯洁、不可变)远比OOP(封装的可变状态)更健壮、更易于推理。
5.3 停战协定C:Python,光荣的实用主义者
如果说C#是“披着OOP外衣的FP”,React是“藏在UI里的FP”,那么Python就是那个光荣的、坦诚的“大杂烩”。
Python自豪地宣称自己是“多范式”语言65。它有强大的class(OOP),但它也“在LISP的传统中支持函数式编程”59。
- 想用OOP?没问题,
class关键字随你用。 - 想用FP?也没问题。列表推导式(
[x*x for x in list])就是map。lambda(匿名函数)是一等公民。 - Python不强迫你加入任何一个宗教。它只是给了你一个装满工具的院子,让你自己挑顺手的用66。
结论:欢迎回来,旅行者。你现在是房间里唯一的大人。
从过程式的GOTO地狱,到OOP的“工厂”官僚主义,再到FP的“数学修道院”,我们终于绕了一圈回来了。
这场长达50年的“信仰之战”,最终揭示了一个比OOP或FP更深刻的真相:
真正的战争,一直是“声明式” vs. “命令式”。67
- 我们的“恐龙”代码(BASIC, C, 汇编)是100%的命令式(Imperative):我们必须告诉CPU“如何”一步一步地执行任务。
- OOP试图创建抽象(对象)来隐藏“如何”的细节。
- FP试图创建抽象(Monad, map)来隐藏“如何”的细节。
- 而SQL1、LINQ58、React61,它们是声明式(Declarative):我们只告诉系统我们“想要什么”,让系统自己去弄清“如何”做。
OOP和FP,只是两条通往同一个“声明式”罗马的、截然不同的道路。
作为“复活”的程序员,我们是这个房间里唯一看过整部史诗的人。我们见证了“原罪”,我们目睹了“OOP福音书”的失败,我们理解了“FP宗教改革”的动机。
我们不再是任何一个范式的狂热信徒。我们是掌握了所有范式的实用主义大师。
欢迎回来,旅行者。我们不再是恐龙。我们是导游。
引用的著作
-
What problems of procedural programming does OOP solve in practice?, 访问时间为 十月 27, 2025, https://cs.stackexchange.com/questions/22867/what-problems-of-procedural-programming-does-oop-solve-in-practice ↩︎ ↩︎ ↩︎
-
Global variables Memes - ProgrammerHumor.io, 访问时间为 十月 27, 2025, https://programmerhumor.io/memes/global-variables ↩︎
-
Can you give examples of spaghetti code and non-spaghetti code ..., 访问时间为 十月 27, 2025, https://www.reddit.com/r/learnprogramming/comments/a8b6e7/can_you_give_examples_of_spaghetti_code_and/ ↩︎ ↩︎ ↩︎
-
Software Quality: The Horror of Spaghetti Code - Teklibri, 访问时间为 十月 27, 2025, https://www.teklibri.com/software-quality-spaghetti-code/ ↩︎
-
Exploring the Four Pillars of OOP with Fun Real-Life Examples | by Nozibul Islam, 访问时间为 十月 27, 2025, https://medium.com/@nozibulislamspi/exploring-the-four-pillars-of-oop-with-fun-real-life-examples-9750504bb972 ↩︎
-
Object Oriented Programming in Memes - DEV Community, 访问时间为 十月 27, 2025, https://dev.to/abbeyperini/object-oriented-programming-in-memes-4kck ↩︎ ↩︎ ↩︎
-
Encapsulation. : r/ProgrammerHumor - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ProgrammerHumor/comments/707ql4/encapsulation/ ↩︎
-
Abstraction Memes · ProgrammerHumor.io, 访问时间为 十月 27, 2025, https://programmerhumor.io/memes/abstraction ↩︎ ↩︎ ↩︎
-
A good Object-Oriented analogy - oop - Stack Overflow, 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/3141957/a-good-object-oriented-analogy ↩︎ ↩︎ ↩︎
-
The problem with Object Oriented Programming and Deep Inheritance - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/programming/comments/1nty75l/the_problem_with_object_oriented_programming_and/ ↩︎ ↩︎
-
OOP Pillar #4: Polymorphism For Dummies - Sloth Bytes, 访问时间为 十月 27, 2025, https://slothbytes.beehiiv.com/p/polymorphism ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
[Help] can some quickly explain polymorphism like i'm 5? : r/csharp - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/csharp/comments/8qlxdr/help_can_some_quickly_explain_polymorphism_like/ ↩︎
-
Fragile base class - Wikipedia, 访问时间为 十月 27, 2025, https://en.wikipedia.org/wiki/Fragile_base_class ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
Banana Gorilla Jungle — OOP. From the famous quote, | by tanut ..., 访问时间为 十月 27, 2025, https://medium.com/codemonday/banana-gorilla-jungle-oop-5052b2e4d588 ↩︎ ↩︎ ↩︎ ↩︎
-
Alan Kay gave his thoughts on C++ for OOP. | by Ianjoyner | Medium, 访问时间为 十月 27, 2025, https://medium.com/@ianjoyner/alan-kay-gave-his-thoughts-on-c-for-oop-1321660f04a9 ↩︎
-
Alan Kay's Vision of OOP - DEV Community, 访问时间为 十月 27, 2025, https://dev.to/muhammad_salem/alan-kays-vision-of-oop-k5h ↩︎ ↩︎
-
Smalltalk - Wikipedia, 访问时间为 十月 27, 2025, https://en.wikipedia.org/wiki/Smalltalk ↩︎
-
object oriented - Alan Kay: "The Big Idea is Messaging" - Software ..., 访问时间为 十月 27, 2025, https://softwareengineering.stackexchange.com/questions/264697/alan-kay-the-big-idea-is-messaging ↩︎
-
Alan Kay on his original thoughts when he came up with the term ..., 访问时间为 十月 27, 2025, https://www.reddit.com/r/programming/comments/bpb5v6/alan_kay_on_his_original_thoughts_when_he_came_up/ ↩︎ ↩︎
-
What did Alan Kay mean by, 'I made up the term object-oriented, and I can tell you I did not have C++ in mind.'? - Quora, 访问时间为 十月 27, 2025, https://www.quora.com/What-did-Alan-Kay-mean-by-I-made-up-the-term-object-oriented-and-I-can-tell-you-I-did-not-have-C++-in-mind ↩︎ ↩︎
-
Alan Kay on why he created object-oriented programming - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/programming/comments/googys/alan_kay_on_why_he_created_objectoriented/ ↩︎
-
Memorable Edsger Dijkstra Quotes, 访问时间为 十月 27, 2025, https://www.scranton.edu/faculty/mccloskey/dijkstra_quotes.html ↩︎
-
Ew Dijkstra Quotes - C2 wiki, 访问时间为 十月 27, 2025, https://wiki.c2.com/?EwDijkstraQuotes ↩︎
-
Why did Dijkstra say that “Object-oriented programming is an exceptionally bad idea which could only have originated in California.”? - Quora, 访问时间为 十月 27, 2025, https://www.quora.com/Why-did-Dijkstra-say-that-%E2%80%9CObject-oriented-programming-is-an-exceptionally-bad-idea-which-could-only-have-originated-in-California-%E2%80%9D ↩︎
-
c++ - YOU are full of bullshit. C++ is a horrible language ... - devRant, 访问时间为 十月 27, 2025, https://devrant.com/rants/1494462/you-are-full-of-bullshit-c-is-a-horrible-language-it-s-made-more-horrible-by-the ↩︎
-
Linus Torvalds on C++ : r/learnprogramming - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/learnprogramming/comments/1dss1jo/linus_torvalds_on_c/ ↩︎ ↩︎ ↩︎
-
Linus Torvalds: “C++ is really a terrible language!” | by Shingai Zivuku | Nerd For Tech, 访问时间为 十月 27, 2025, https://medium.com/nerd-for-tech/linus-torvalds-c-is-really-a-terrible-language-2248b839bee3 ↩︎
-
The Square-Rectangle Problem: A Classic Example of Liskov ..., 访问时间为 十月 27, 2025, https://medium.com/@ikirezidivine21/the-square-rectangle-problem-a-classic-example-of-liskov-substitution-principle-violation-80ae95326407 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
Square, Rectangle and the Liskov Substitution Principle | by Alexandre Dutertre - Medium, 访问时间为 十月 27, 2025, https://medium.com/@alex24dutertre/square-rectangle-and-the-liskov-substitution-principle-ee1eb8433106 ↩︎
-
Liskov Substitution: The Real Meaning of Inheritance - cekrem.github.io, 访问时间为 十月 27, 2025, https://cekrem.github.io/posts/liskov-substitution-the-real-meaning-of-inheritance/ ↩︎
-
Why do some people say OOP is bad? : r/learnprogramming - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/learnprogramming/comments/x6rvk9/why_do_some_people_say_oop_is_bad/ ↩︎
-
Why is '_____Factory' a meme when it comes to criticisms of Java ..., 访问时间为 十月 27, 2025, https://www.reddit.com/r/AskProgramming/comments/f4tj8a/why_is_factory_a_meme_when_it_comes_to_criticisms/ ↩︎
-
How would another popular language avoid having to use the factory pattern while managing similar complexity as in Java/Java EE?, 访问时间为 十月 27, 2025, https://softwareengineering.stackexchange.com/questions/236105/how-would-another-popular-language-avoid-having-to-use-the-factory-pattern-while ↩︎ ↩︎
-
Introducing the "Factory Factory" design pattern, tell me what's wrong, wrong answers only : r/ProgrammerHumor - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ProgrammerHumor/comments/yl9fp1/introducing_the_factory_factory_design_pattern/ ↩︎
-
Lambda Calculus and Lisp, part 1 - The Neo-Babbage Files ❚, 访问时间为 十月 27, 2025, https://babbagefiles.xyz/lambda-calculus-and-lisp-01/ ↩︎
-
Fun with Lambda Calculus : r/programming - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/programming/comments/jdu26y/fun_with_lambda_calculus/ ↩︎
-
At a low level, what is immutability, really? : r/ProgrammingLanguages - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ProgrammingLanguages/comments/1cumxdd/at_a_low_level_what_is_immutability_really/ ↩︎ ↩︎
-
Functional Programming in React: Key Concepts, Examples, and Best Practices - Medium, 访问时间为 十月 27, 2025, https://medium.com/@sobhanyazdanjoo/functional-programming-in-react-key-concepts-examples-and-best-practices-cb592335fcec ↩︎
-
How is state handled differently between OOP and procedural programming? - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/learnprogramming/comments/1e5oh5i/how-is-state-handled-differently-between-oop-and/ ↩︎
-
What are pure functions? - Eric Normand, 访问时间为 十月 27, 2025, https://ericnormand.me/podcast/what-are-pure-functions ↩︎
-
What are Side-Effects in Programming? | by Saravanan M | Nerd For Tech - Medium, 访问时间为 十月 27, 2025, https://medium.com/nerd-for-tech/what-are-side-effects-in-programming-51f7ef340f98 ↩︎
-
Chapter 03: Pure Happiness with Pure Functions - mostly-adequate-guide - GitBook, 访问时间为 十月 27, 2025, https://mostly-adequate.gitbook.io/mostly-adequate-guide/ch03 ↩︎
-
Another Map, Filter, Reduce Article? Yes! Deep Dive — Part 1 | by ..., 访问时间为 十月 27, 2025, https://medium.com/@clocksmith/another-map-filter-reduce-article-yes-deep-dive-part-1-7ccc57425739 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
Main difference between map and reduce - javascript - Stack Overflow, 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/49934992/main-difference-between-map-and-reduce ↩︎ ↩︎ ↩︎
-
Reading 25: Map, Filter, Reduce - MIT, 访问时间为 十月 27, 2025, https://web.mit.edu/6.005/www/fa15/classes/25-map-filter-reduce/ ↩︎
-
CS 463 - Lambda Calculus Resources, 访问时间为 十月 27, 2025, https://cs.gmu.edu/~marks/463/slides/1.lambda_calculus/lambda_intros.html ↩︎ ↩︎
-
Explained in 5 minutes: Monads in plain JavaScript | by Piotr Jaworski, 访问时间为 十月 27, 2025, https://piotrjaworski.medium.com/explained-in-5-minutes-monads-89d54d230baf ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
What is MONAD? : r/functionalprogramming - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/functionalprogramming/comments/13cnx5e/what_is_monad/ ↩︎
-
Monad (functional programming) - Wikipedia, 访问时间为 十月 27, 2025, https://en.wikipedia.org/wiki/Monad_(functional_programming) ↩︎
-
r/ProgrammerHumor on Reddit: memeProudlyPresentedToYouByTheFunctionalProgrammingGang, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ProgrammerHumor/comments/1j0alim/memeproudlypresentedtoyoubythefunctionalprogrammin/ ↩︎
-
Is it bad practice to mix OOP and procedural programming in Python (or to mix programming styles in general) [closed] - Stack Overflow, 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/47441017/is-it-bad-practice-to-mix-oop-and-procedural-programming-in-python-or-to-mix-pr ↩︎
-
Is anyone doing functional programming in C# : r/dotnet - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/dotnet/comments/1i6nrwg/is_anyone_doing_functional_programming_in_c/ ↩︎
-
c# - Map and Reduce in .NET - Stack Overflow, 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/428798/map-and-reduce-in-net ↩︎
-
Functional Linq tips for senior C# developers | by Dimitris Papadimitriou - Medium, 访问时间为 十月 27, 2025, https://functionalprogramming.medium.com/functional-linq-tips-for-senior-c-developers-bfb869547610 ↩︎
-
Enumerable.Where Method (System.Linq) - Microsoft Learn, 访问时间为 十月 27, 2025, https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.where?view=net-9.0 ↩︎
-
Functional programming with Linq - IEnumerable.Aggregate | theburningmonk.com, 访问时间为 十月 27, 2025, https://theburningmonk.com/2010/02/functional-programming-with-linq-ienumerable-aggregate/ ↩︎
-
Language Integrated Query (LINQ) - C# | Microsoft Learn, 访问时间为 十月 27, 2025, https://learn.microsoft.com/en-us/dotnet/csharp/linq/ ↩︎ ↩︎ ↩︎ ↩︎
-
Functional Programming HOWTO — Python 3.14.0 documentation, 访问时间为 十月 27, 2025, https://docs.python.org/3/howto/functional.html ↩︎ ↩︎
-
JavaScript language overview - MDN Web Docs, 访问时间为 十月 27, 2025, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Language_overview ↩︎
-
Keeping Components Pure – React, 访问时间为 十月 27, 2025, https://react.dev/learn/keeping-components-pure ↩︎ ↩︎
-
What Is a Pure Function? Why Do React Function Components Need to Be Pure?, 访问时间为 十月 27, 2025, https://www.explainthis.io/en/swe/react-pure-function ↩︎
-
Components and Hooks must be pure - React, 访问时间为 十月 27, 2025, https://react.dev/reference/rules/components-and-hooks-must-be-pure ↩︎
-
Functional Programming In React | Saeloun Blog, 访问时间为 十月 27, 2025, https://blog.saeloun.com/2024/07/25/functional-programming-in-react/ ↩︎ ↩︎
-
Exploring Three Major Programming Paradigms in Python | by Pickl.AI - Medium, 访问时间为 十月 27, 2025, https://medium.com/@deepanshi.pal/exploring-three-major-programming-paradigms-in-python-b39c712c507c ↩︎
-
Is Python a functional programming language or an object oriented language?, 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/63889627/is-python-a-functional-programming-language-or-an-object-oriented-language ↩︎
-
Functional programming vs. imperative programming - LINQ to XML - .NET - Microsoft Learn, 访问时间为 十月 27, 2025, https://learn.microsoft.com/en-us/dotnet/standard/linq/functional-vs-imperative-programming ↩︎