Joe Armstrong,Erlang语言的创造者,在2010年的QCon大会上说了句狠话,说自己写过一篇文章喷面向对象编程,目的就是想激怒那些OOP的支持者。这话听着挺冲,但仔细想想,他到底在骂什么?是单纯看不惯OOP,还是发现了什么更深的问题?
我刚开始学编程的时候,也觉得OOP就是真理。老师教继承、多态,说这样代码能复用,结构清晰。但后来写多了,发现有些东西就是别扭。比如写个“猫狗吃”的例子,得先搞个抽象类Animal,定义个eat()方法,然后让Cat和Dog去继承重写。可如果换用函数式,直接写个函数eat(cat)和eat(dog)就行了,哪来那么多弯弯绕?
Armstrong早就看透了这点。他说继承会导致脆弱的基类层次,你改个父类,子类可能全崩。多态更麻烦,隐式的动态绑定让你根本猜不到运行时到底调了哪个方法。说白了,这些所谓“特性”包装出来的复杂度,其实并不比直接写函数更省事。
OOP为什么能火起来?Armstrong觉得跟图形用户界面(GUI)的爆发有很大关系。八十年代、九十年代,Windows、Mac这些系统流行起来,窗口、按钮、菜单这些控件天然就像“对象”,每个都有状态、有行为。用OOP来建模特别顺手。但问题在于,大家把这种局部成功经验当成了万能钥匙,直接推广到了后端服务、分布式系统这些完全不搭边的领域。
他举了个Java的例子,说Java的封装强调private、public这些语法限制,但一个对象如果崩溃了,内存泄漏、死锁照样会传染给其他对象。真正的隔离应该是运行时层面的,比如Erlang里的进程,一个进程挂了,其他进程完全不受影响,这就是“let it crash”哲学的基础。Java那种封装,只是语法层面的护身符,根本扛不住高可靠系统的考验。
Armstrong心中的真正OOP,其实更接近Alan Kay(OOP概念的提出者)最初的想法。Alan Kay说过,消息传递才是唯一重要的东西。Armstrong完全认同这一点。他认为真正的OOP应该包含三个要素:消息传递(通过Mailbox异步处理)、隔离(每个进程独立内存)、多态(不同进程对相同消息作出不同响应)。而Erlang的send和receive就是这种消息传递的完美实现,Java的方法调用本质上是同步的过程调用,根本不算消息传递。
Erlang的设计原则也很有意思。Armstrong说,要从真实世界的活动中识别出并发活动,比如每个用户就是一个进程。然后识别活动之间的消息通道,也就是协议。再写下通道里流通的消息,也就是数据结构。最后让问题的结构和程序的结构保持一一对应,避免用单线程硬去控制多个活动,搞得一团乱。
在Erlang里,进程是“活的”活动,函数只是处理数据的工具,这跟OOP里数据变成有方法的对象完全相反。OOP让状态和方法耦合在一起,而Erlang更像是古典范式——函数操作数据,数据是被动的。Armstrong觉得这样才更符合并发活动的自然模型。
他博士论文里总结了九条容错系统设计的原则,核心就是隔离错误、监督树自动重启崩溃的进程、让一切可观察。Erlang的继承其实也存在,比如behaviour机制,但从来不是核心。代码复用靠高阶函数、模块、行为模式(比如gen_server),而不是继承层次。gen_server的callback模块提供标准接口,具体实现由用户函数完成,根本不需要继承父类。
关于并发和并行,Armstrong认为Erlang的进程模型天然支持并行,多核分发很容易,而且消息传递避免了锁和共享状态的问题。这跟Haskell那种并发语言的观点不太一样,但Erlang的做法确实在实际应用中证明了它的可靠性。
回过头来看,OOP真的就一无是处吗?当然不是。GUI开发、游戏里的对象模拟,这些场景OOP依然很顺手。继承和多态在小规模项目、快速原型里也有它的便利性。Armstrong的批判,本质上是提醒我们不要搞“范式独裁”。一种范式因为历史偶然因素兴起了,不代表它就是绝对真理。
选择编程范式应该看问题领域。高并发、高可靠系统,Actor模型和函数式思维更优;状态密集、交互复杂的UI,OOP更自然。不管OOP还是函数式,核心价值其实就三样:封装(隔离)、消息传递(通信)、多态(适应性)。语言只是实现这些价值的工具,重要的是理解本质,而不是盲目跟着语法糖跑。
Armstrong在2010年承认自己当时是想激怒OOP支持者,但后来他也觉得Erlang其实可以被看作另一种OOP。他的批判不是全盘否定,而是推动我们重新审视“封装”“继承”“多态”到底是什么意思。好的程序员应该掌握多种范式,理解背后的哲学,而不是被一种语言或语法捆住手脚。
热门跟贴