Why OO Sucks

April 25, 2019
FP

原文为英文,由 Joe Armstrong 撰写。我的翻译如下。

当我第一次接触OOP(Object Oriented Programming, 面向对象编程)概念的时候,就感到十分怀疑,但是又不知道为什么,就是感觉"不对”。当OOP变得非常流行(下面会解释原因),批判OOP就像”在教堂诅咒“一样会被排斥。OO逐渐成为了主流编程语言必须要有的特性。

随着Erlang变得流行,我们经常被问到"Erlang是面向对象的吗?"。真正的答案当然是"不支持”,但是我们并不敢对外这么说,为此我们发明了一系列巧妙的方式来回答这个问题,给大家营造一种Erlang在某种程度上是OO,但又不真的是OO(如果仔细听我们说的,看小字印刷的内容就知道了)。

这个时候,我想到了 IBM 那时的老板在巴黎第七届IEEE Logic 编程会议时, 被问及IBM Prolog为什么添加了一系列面向对象的扩展,回答是:

我们的客户希望使用面向对象的Prolog,所以我们就创造了面向对象的Prolog。

我当时的想法是"这么简单粗暴的回答,没有任何良心上的不安,没有灵魂深处的拷问,也没有想过这是不是正确的事情 ……”

面向对象为什么糟糕

对面向对象的主要反对理由可以回归到面向对象的基本理念,在这里我会简述其中一些基本理念以及我对它们的反对意见。

反对1. 数据结构和函数不应该捆绑在一起

对象将数据和函数绑定在了一个不可分割的单元。我认为这是一个根本性的错误,因为函数和数据属于完全不同的两个世界。为什么这么说呢?

  • 函数的作用是处理一些事情。它们有输入和输出。输入和输出是数据,数据可以被函数所改变。在大多数编程语言里,函数由一系列指令所构建:“先干这个,之后再干那个……"。为了理解函数,你必须理解这些事情以何种顺序完成。(在支持延迟计算的函数式编程语言中,这个限制并不严格。)
  • 而数据就静静的待在那里。它们什么都不做。它们本身是声明性的。“理解"数据远比"理解"函数简单的多。

为了便于理解,函数通常被当作"黑盒"处理,用来把输入转换成输出。如果我理解了输入和输出,那么我就理解了这个函数。但这不意味着我可以写出这个函数。

通常可以认为函数是计算系统的一些"部件”,它们的职责是从一种数据结构转换成另一种数据结构。

函数和数据是完全不同的两类"动物”,强行把它们锁在同一个"笼子"里,本身就是不正确的。

反对2. 一切事物都必须是一个对象

想想**“时间”**,在面向对象编程语言中,“时间"必须是一个对象。(在Smalltalk中,甚至"3"都是一个对象)。但是在非面向对象的语言中,“时间"只是某种数据类型的实例。例如,在Erlang中,存在各种各样的时间表示形式,通过使用类型声明可以清晰明确地进行指定,如:

-deftype day()     = 1..31.
-deftype month()   = 1..12.
-deftype year()    = int().
-deftype hour()    = 1..24.
-deftype minute()  = 1..60.
-deftype second()  = 1..60.
-deftype abstime() = {abstime,year(),month(),day(),hour(),min(),sec()}.
-deftype hms()     = {hms,hour(),min(),sec()}.

注意:上面的定义不属于任意特定的对象。它们是通用的,系统中的任意的函数(译者:这里的函数并不指代已有签名的那些函数,而是对函数的概指)都可以处理以上数据结构所表示的时间。

这里也没有任何关联的方法。

反对3. 在OOP中,到处都是数据类型定义

在面向对象编程语言中,数据类型的定义属于对象的范畴。因此我没有办法在一个地方找到所有的数据类型定义。在Erlang或者C语言中,我可以把所有数据类型的定义放在同一个依赖文件或数据字典中。在面向对象里,我办不到,不得不把数据类型定义散播的到处都是

举一个例子,假设我想定义一个通用数据类型。(通用数据类型是指在系统中各个地方都可能用到的数据类型)。

Lisp软件工程师应该早就知道,好的软件设计应该有少量的通用数据类型,和大量精简的函数处理这些类型,而不是拥有大量的数据类型和少量的函数。

通用数据类型有,链表、数组、哈希表,或者更高级的事物如时间、日期或者文件名等等。

在面向对象编程语言中,我必须选择某个基本对象,才能定义通用数据类型。所有其它希望使用这一数据类型的对象必须继承这个基本对象。假设我打算创建一些表示"时间"的实例,它应该属于哪个对象呢?

反对4. 对象存在私有状态

状态是罪恶之源。尤其是伴有副作用的函数更应当避免保存状态。

在编程语言中状态是不利因素,但在现实世界中状态确是个好东西。比如我对自己银行账户状态的变化就十分感兴趣,当我从银行存取钱时,我期望银行账户的状态被正确更新。

鉴于现实世界中存在状态,那么编程语言应当提供哪些基础设施来处理状态呢?

  • 面向对象编程语言说,“向程序员隐藏状态”。结果就是状态被隐藏起来,只有通过访问函数来获取。
  • 面向过程的编程语言如C、Pascal说,状态变量的可见性应该由语言本身的作用域规则来控制。
  • 纯粹的声明性语言说,就不该有状态。系统的全局状态传递给函数,函数处理完成之后再返回。一些机制如函数式编程中Monad和逻辑语言中的DCG,被用来向程序员隐藏状态,这样他们就可以假想自己在"状态无关紧要"的情况下编程,但事实是仍然可以获取系统的所有状态。

面向对象编程语言所采用的"向程序员隐藏状态"的做法是最糟糕的。它们没有暴露状态,也没有试图寻找方法来最小化状态所带来的麻烦,只是把状态藏了起来。

面向对象为什么流行?

  • 原因1:被认为更容易学习。
  • 原因2:被认为更容易实现代码复用。
  • 原因3:天花乱坠的广告宣传。
  • 原因4:它创造了一套新的软件开发产业

我没有找到1和2的证据。3和4似乎是这项技术背后的直接驱动力。如果一种编程语言技术设计如此糟糕,以至于它需要创造一个新的产业来解决自己制造的问题,那么对于那些想通过这项技术赚钱的人来说,这的确是个好主意。

这就是面向对象编程流行的真正原因。

参见 The Third Manifesto

[本节英文原文由 R. A. O'Keefe 于2000年添加。]

由Date 和 Darwen 著作的《The Third Manifesto》是关于如何"合理"设计对象数据库。在附录中,他们对待SQL3 相当不温和,但比它应得的还算好。他们甚至对ODMG 也做了批判。如果在花费了大量时间和财力的情况下,不能产出一个逻辑上一致的对象模型来匹配面向对象数据库系统,那么,也许OO就不会像看上去那么简单和好用了。

欢迎关注我的公众号: 沐风自语

Imgur

原文链接:

http://www.cs.otago.ac.nz/staffpriv/ok/Joe-Hates-OO.htm

comments powered by Disqus