Home > misc > [转]为什么OO很恶心

[转]为什么OO很恶心

October 10th, 2008 :: jackyz

Why OO Sucks

原文:http://www.sics.se/~joe/bluetail/vol1/v1_oo.html
作者:Joe Armstrong
原载:http://www.javaeye.com/topic/230760
译者:hideto

当我第一次知道 OOP 的概念时,我非常疑惑,但是不知道为啥——它仅仅在感觉上“不对”。在 OOP 问世之后变得粉流行(稍后解释为什么),而批评 OOP 就像“在教堂里咒骂”。 OO 成为了每个受尊敬的语言必须具备的一部分。

而当 Erlang 变得越来越流行时,我们经常被问到同一个问题——“ Erlang 是 OO 的吗?”。当然正确的答案是“当然不是”——但是我们没有大肆宣扬——我们只是换了种精心设计的说法,Erlang 是某种 OO 但不是真的是。

这时我想起在法国巴黎时 IBM 的老板在 7th IEEE 逻辑编程大会上的演讲。IBM prolog 添加了许多 OO 扩展,当人们问起时他说:“我们的客户想要 OO 的 prolog ,所以我们构建了 OO 的 prolog ”。我想到了“多么简单,没有良心的疑虑,没有灵魂的搜索,没有‘这是正确的事情’的问题……。”

为什么 OO 很恶心?

我对 OOP 的反对原则源自一些基本的概念,我将概述其中一些反对意见。

反对之一——数据类型和方法应该绑定在一起

对象将方法和数据结构绑定在一起成为不可分割的单元。我认为这是基本的错误,因为方法和数据结构属于完全不同的世界。为啥哩?

  1. 方法做事情。它们是输入和输出。输入和输出的是方法所改变的数据结构。在大部分编程语言里,方法由命令式语句顺序构建:“做这件事然后那件事……”。理解方法首先得理解做事情的顺序(在惰性求值的函数编程语言和逻辑语言中这个限制被放宽了)。
  2. 数据结构是结构。它们不做任何事情。它们本质上是声明。“理解”数据结构比“理解”方法简单多了。

方法作为黑盒子来转换输入和输出。如果我理解输入和输出,这样我就理解了方法。这并不意味着我可以写这个方法。方法通常理解为在一个计算系统里用来将数据结构 T1 转换为数据结构 T2 的东西。既然方法和数据结构是完全不同类型的动物,那么将它们锁在一个笼子里就是完全错误的。

反对之二——任何东西都必须为对象

考虑“时间”。在 OO 语言里“时间”也必须是对象。但是在非 OO 语言里一个“时间”是一个数据结构的实例。例如,在 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()}.
...

注意这些定义不属于任何特殊的对象。它们很普遍,并且数据结构表示的时间可以被系统中的任何方法处理。没有相关联的方法。

反对之三——在一个 OOP 语言里数据类型定义散布到任意位置

在 OOP 语言里数据类型定义属于对象。这样我就不能在一个地方找到所有的数据类型定义。在 Erlang 或者 C 里我可以在一个单独的 include 文件或数据字典里定义我所有的数据类型。在一个 OOP 语言里我不能——数据类型定义散布到任意位置。

让我举一个例子。假设我想定义一个通用的数据结构。通用数据类型是一个数据类型,它在系统中的任意位置出现。

lisp 程序员知道,拥有一个较小数量的通用数据类型和在它上面的大量的小方法会更好。通用数据类型就比如 linked list 或者一个 array 或者一个 hash table 或者更高级的对象如 time 或者 date 或者 filename。

在一个 OOP 语言里我不得不选择一些 base 对象来在里面定义通用的数据结构,所有其他想使用这些数据结构的对象必须继承该对象。假设现在我想创建一些 “ time ” 对象,那么它应该属于哪个对象呢……。

反对之四——对象拥有私有状态

状态是所有罪恶的根源。特别是有副作用的方法应该避免。在编程语言里状态是令人讨厌的,而真实世界里状态却千奇百怪的存在着。我对我的银行账户的状态很感兴趣,当我从我的账户存钱或取钱时我希望我的银行账户状态成功更新。既然状态在真实世界里存在,那么编程语言应该提供什么能力来处理状态呢?

  1. OOP语言说“将状态隐藏”。状态仅仅通过访问方法来隐藏和可见。
  2. 传统编程语言(C,Pascal)说状态的可见度由语言的 scope 规则来决定。
  3. 纯声明式语言说没有状态。系统的全局状态转移到方法里然后从方法里出来。

类似于 monad (函数式编程语言)和 DCG (逻辑语言)等机制被用来隐藏状态,这样它们可以像“有没有状态无所谓”一样来编程,但是对系统状态的完全访问是必需的。 OOP 语言所选择的“隐藏状态”可能是最坏的选择。它们不是将状态显示出来并寻找减少状态的坏处的方式,而是将状态隐藏起来。

为什么 OO 粉流行?

  1. 原因1——它被认为很容易学
  2. 原因2——它被认为让代码更易重用
  3. 原因3——它被大肆宣传
  4. 原因4——它创建了一个新的软件工业

我看不到原因1和原因2的证据。原因4看起来像是技术背后的驱动力。如果一个编程语言技术如此之差,然后它创建了一个新的工业来解决它自己本身的问题,则它会成为想从中牟利的人的好工具。这就是 OOP 背后真正的驱动力。

此外,本文还有一篇回复:Why “Why OO Sucks” Sucks

转载注:转载时对原文分段和用词略作了调整。

译文在 javaeye 发表后引起发了空前热烈的讨论,却因为陷入口水,未能进行更深入的探讨,殊为遗憾。虽如此,大量的回复之中仍有很多精彩言论,比如 Trustno1 说:

……
Why OO Sucks?
Because Von Neumann Architecture sucks.
……
赋值是一个与时序相关的操作,也就是具有并发性的 IO (以运算器的视角).
赋值不是绝对不能用的,相反是绝对必需的.
赋值不是 FP 能够解决的问题,赋值与 FP 是互相独立没有交集的,因为 lambda 演算和图林机一样从来不关心 IO 问题.
赋值也不是十恶不赦的,十恶不赦的只是那些企图掩盖他并发性的方法,比如 Von Nnumann 结构.

只要正确地引入并发性,那么所有原先由赋值操作来完成的功能,都能找到对应的方案。
……

为进一步阐明观点 TrustNo1 另外发表了《关于两个世界体系的对话》,讲述了 Von Nnumann 体系的假设前提以及隐含在其中的根本性问题。其中提到:

……
有一个传说流传甚广——冯诺依曼制造了第一台计算机ENIAC。其实冯诺依曼本人未曾参与ENIAC的制造,那篇奠定冯诺依曼机结构的101页报告也是在 ENIAC运行了一年后才发表的,更重要的是ENIAC是一台并行计算机。冯诺依曼对计算机的兴趣,来自于曼哈顿工程中大量的数值计算。1944年冯诺依曼在火车站上偶遇了ENIAC总设计师戈尔斯坦后才参观了当时ENIAC的研制小组。当时冯诺依曼发现,这些机器的并行操作的能力使得程序编制极为复杂。这一事实让冯诺依曼更多的关注于顺序指令代码,企图以此来保证并行操作决不会出现。
……
冯诺依曼机的程序指令是按照CPU的时序顺序执行的,它的数据流由控制流驱动。比如 x86 CPU 中,数据是在 CPU 取指以后在 SC 周期读取的。这点在大多数人看来是不言自明无足轻重的。但是他们却涉及到了传统编程语言中最为本质的问题。Fortran 之父 John Backus 在它图林奖领奖演说《Can Programming Be Liberated from the von Neumann Style?》中, 毫不讳言的道出了这一点:冯诺依曼型的语言,使用变量来模仿计算机的存储单元;控制语句来描述跳转和测试指令;赋值语句模仿数据的获取,存储……这个世界最核心的语句就是赋值语句本身。所有其它语句的存在,只是为了能使那些根植于赋值语句的计算结构可以正确地运行
……
将计算构建在隐含着并发性IO的赋值语句之上,会发生什么?John Backus 在同一篇演说里,反复强调了这种困扰:更重要的是,赋值语句将程序割裂为两个世界。第一个世界是赋值语句右边的世界。这是一个有序的表达式世界,这个世界有许多有用的代数特性…….。最有用的计算都发生在这里。第二个世界是语句的世界,这是一个无序的世界,在那里找不到什么有用的数学特性。结构化编程一定层度上为这个混乱的世界带来一些秩序,但是它那些原始的循环,子函数,分支流程技术从未击中过冯诺依曼型语言的本质问题——一次一条指令的控制流模式。
……
无论如何,冯诺依曼的设计的确为后世的计算机工业带上了一个难以解套的紧箍咒。今日无数天才程序员为之殚精竭虑的并行计算,其实是一个非常幽默的问题——如何在一台原本设计用来杜绝并行计算的机器上进行并行编程?
……

谈及此处,回归标题 ——“ Why OO Sucks ” —— 似乎为了回答这个问题,我们回溯了一个又一个的时代,直指 “现代计算机” 诞生之初的那个时刻。而实际上 OO 也并不是凭空出现的,在 Von Neumann 体系统治计算工业的 60 年间,从 Von Neumann 体系到 Von Neumann 语言再到 SP 进而 OOP ,整个“进化”的过程确实也是一脉相承。从这个角度来说 Joe 老爷爷的文章虽说没有击中要害,却也似乎并未完全脱靶。

misc

  1. 刘典
    October 10th, 2008 at 14:09 | #1

    说的非常好,但是我觉得更准确的说法是“Erlang才是OO”,其他语言都不是,可以参考这个这篇文章:The Birth of Object Orientation: the Simula Languages (http://heim.ifi.uio.no/~olejohan/papers/Birth-of-S.pdf)

    古代的大师们最开始提出的就是类似Erlang这种编程模型, 不知道被哪个万恶的商人给偷换了概念,今天的OO完全就是商业炒作。

  2. October 10th, 2008 at 16:17 | #2

    这个是世界观的问题

  3. October 10th, 2008 at 16:20 | #3

    人到底是把对世界的想法,映射到计算机上,我觉得没有谁对谁错,也没有谁更好更差,只有在不同情况下谁更合适

  4. October 10th, 2008 at 16:54 | #4

    也不该这样大肆抹黑OO的~~只能说大家都各有长短~~如何取彼之长补己之短才是正道~~

  5. jackyz
    October 10th, 2008 at 18:06 | #5

    个人认为 TrustNo1 的这篇文章,其要点在于:Von Neumann 体系(根植于赋值语句的计算结构)“一次一条指令的控制流模式”,根本就是为了避免并行的情况出现而设计的(对,它就是这么对“计算”进行简化的)。所以,构建在这个体系之上的语言,结构化编程乃至面向对象编程,在面对并行问题时才会如此地困难。这是这个问题无法回避的部分。

    至于在语法层面或者语义层面的反对,一方面是因为它们并非根本,而且很容易就可以兼得;另一方面也是因为它们更多的接近于个人喜好,说实话这种东西是很难分出高下的。

  6. Magicloud
    October 12th, 2008 at 11:23 | #6

    太多人讨论oo只停留在语言的层面,仅停留在有没有这个语法、这个概念的层面。
    但反过来却有另一个问题。就是我们的思维收冯氏机的影响有多少。
    从抽象一点的角度讲,物理硬件的结构糟糕,不会完全影响上层语言所体现出来的思想。虚拟机的存在不是只为了“一次编译,四处调试”。
    可惜的是绝大部分人受冯氏机体系的禁锢,不能跳开来思考,以至于考虑oo时,不能“真的OO”。
    不过呢……能跳出一个世界思考者,圣人也。

  7. October 14th, 2008 at 15:05 | #7

    瞎了眼,上了OO的当?哈哈。
    我学的时候怎么没想起来。

  8. sily
    October 29th, 2008 at 15:30 | #8

    面向对象有面向对象的好处,不能把人家的优点给忽略了。

  9. April 2nd, 2009 at 17:06 | #9

    个人认为oo在语法上比较符合人类语言,i.love(you) [ruby or scala: i love you]

    没有细想oo在reuse上是否真的有优势,但是曾经考虑过用erlang来实现eclipse那样的微内核插件架构(O什么什么,忘记名字了。)真要弄一堆xml什么的,也不是不能玩,不过是有些问题比较麻烦。
    再看看gen_server template,很多代码无论用模板、copy还是别的方法,不都是必须写吗。当然,也可以把gen_server封装到别的module。

    erlang代码比较难读,因为效率太高了。看看couchdb各个部分代码文件数就知道,真正内核不超过50文件(30多个记得,0.9的没数,也不会多)。

    20%技术80%政治吗,java和.net的一堆库,用起来就是爽吗。要是这些库都有erlang版,life will be easier

    关于hyper oo和fun,看来路也不是特别好走。互有优缺点,wish哪天哪位踏着七色云彩,彻底让我们看到美好。

  1. No trackbacks yet.