面向对象编程是一场代价高昂的灾难,必须结束

2020-06-15 05:26:46

(由劳伦斯·克鲁纳撰写,不过缩进的段落通常是引号)。你可以联系劳伦斯:[email protected],或者在Twitter上关注我。

因此,个人A受到保护,不受新信息的有害影响。新信息是危险的,因为它可能会导致某人改变主意。只需将新信息声明为无效,即可使其变得安全。A相信所有的苏格兰人都是勇敢和可敬的,你不能说服他们相反的,因为你提到的任何反例都是一些堕落的不真实的苏格兰人,这与他们对真正的苏格兰人的想法没有任何关系。每当我反对面向对象编程(OOP)时,这就是我的经验:无论我提出什么证据供考虑,都会被认为是无关紧要的。如果我抱怨Java太冗长,我会被告知True OOP程序员让IDE来处理一些样板文件,或者可能有人告诉我Scala更好。如果我抱怨Scala涉及太多仪式,我就会被告知Ruby缺乏仪式。如果我抱怨Ruby中修补程序的危险,我会被告知真正的OOP程序员知道如何将元编程用于他们的优势,如果我不能做到这一点,那么我就是无能的。我应该使用更纯粹的语言,或者更实用的语言,我应该使用具有编译时静态数据类型检查的语言,或者我应该使用允许我自由动态输入的语言。如果我抱怨bug,我被告知这些特定的bug已经在新版本中修复了,为什么我还没有升级,或者我被告知有一个常见的解决方法,如果我不知道这一点,我就是个白痴。如果我抱怨最流行的框架过于臃肿,我会被告知没有人再使用该框架。没有真正的OOP程序员会做我抱怨的事情。

人们会将许多美好的想法与OOP联系在一起。我将展示两个东西:

1)。与其他语言(LISP、函数式语言等)相比,OOP语言没有独特的优势。

2.)。与其他语言(LISP、函数式语言等)相比,OOP语言带来了不必要的复杂性的沉重负担。

那些潜在的好特性(数据隐藏、契约执行、多态性)并不是OOP独有的,事实上,这些特性的更强版本在非OOP语言中是可用的。那些OOP独有的特性(依赖注入、实例化)非常糟糕,它们之所以存在,只是因为OOP很糟糕。

我正在采取一种普世的、普世主义的方法来处理面向对象的程序设计(OOP)。下面我将把所有这些语言称为OOP:C++、Java、Scala、PHP、Ruby和Javascript。这公平吗?我知道,根据个人经验,Java的一些支持者会抱怨Ruby和PHP缺乏编译时数据类型检查,因此不应该被认为是面向对象的。我知道,根据个人经验,Ruby的一些支持者会争辩说,在Ruby中,一切都是对象,而Java仍然有非对象原语(如整数),因此Ruby更像是一种面向对象的语言,而不是Java。我知道PHP的一些批评者会争辩说,OOP特性是固定在PHP上的,不应该把它作为一种OOP语言来认真对待。我知道有些人会指出Scala是多范式的,在Scala中使用“函数范式”就像使用面向对象范式一样容易。

鉴于语言的多样性,以及缺乏标准定义,谈论面向对象编程有意义吗?我会说是的。这是非常迫切的需要。OOP可能是一个定义不明确、模棱两可的概念,但它绝对主导着科技行业。许多软件开发人员和许多公司都认为OOP是当今唯一合理的软件开发方式。任何反对OOP的人都会立即意识到他们是在反对业界的“传统智慧”。

我去面试的时候遇到了这个问题。不管我面试的是Ruby职位、Java职位还是PHP职位,面试官都会问我是否知道什么是OOP。他们让我定义“封装”和“多态分派”。这些都是标准问题,我应该给出标准答案。当他们问我“OOP有什么好处?”我发现自己想要给出一个尴尬的长答案,它包括“这12件事应该是OOP的好处,但实际上OOP没有独特的优点。”所以我正在写这篇文章,将来,当我在求职面试中被问到这样的问题时,我会简单地把人们引向我在这里写的东西。

这些真的重要吗?你可能会说我是在浪费时间,我正在写一篇很长的文章,只会涉及一堆对任何人都没有好处的语义上的吹毛求疵。但我认为,混乱的定义会导致混乱的思维,正如奥威尔所描述的那样:

一个男人可能因为他觉得自己是个失败者而开始喝酒,然后因为他喝酒而更加彻底地失败。这与发生在英语上的事情非常相似。它变得丑陋和不准确是因为我们的想法是愚蠢的,但是我们语言的懒散使我们更容易产生愚蠢的想法。关键是这个过程是可逆的。现代英语,尤其是书面语,充满了通过模仿传播的坏习惯,只要愿意付出必要的努力,这些坏习惯是可以避免的。

在此基础上,我愿意认为我做了一些有益的事情,只要我能够接受与OOP相关的广泛的想法。

这篇文章很长,如果我仔细限定关于OOP的每一句话,它会更长。请注意,下面,当我将多范例语言(如Scala)称为OOP语言时,我专门指的是该语言中的OOP特性。我想让您问问自己,如果您使用一种多范例语言来编写“函数式”范例,您是否真的从该语言的面向对象程序设计(OOP)特性中获得了什么?您是否可以使用一种从根本上是“函数式”的语言,而不是面向对象的语言,更容易地实现相同的功能?

在科技博客和论坛上,有很多人为OOP辩护,他们确信自己知道自己在捍卫什么,尽管没有任何标准的定义。想想“米尔斯通”在黑客新闻上的这句话:

这篇文章和许多鼓吹函数式编程的文章一样,存在一定的认知偏差,这使它看不到OO擅长什么。

OO擅长什么?显然,米尔斯通认为OO擅长动态。然后,米尔斯通批评了Haskel具有静态类型检查的事实,忽略了Java、C++、C#和许多其他面向对象语言都具有静态类型检查的事实:

Alan Kay写道:“制造伟大的、可扩展的系统的关键更多的是设计其模块如何通信,而不是它们的内部属性和行为应该是什么。”

要开始了解这意味着什么,请考虑Haskell中令人讨厌的字符串/Data.Text拆分。String在很大程度上处于“不考虑数据”的思维模式中,它赤裸裸地充当[Char]。现在您陷入了困境:不能更改其表示形式,不能轻松引入Unicode等等。事实证明,这是如此严格,以至于必须引入一个全新的字符串类型,而我们仍在处理其后果。

伟大的、可生长的系统!我们软件的大规模结构,被分解成模块,不仅在时间上冻结,而且在未来也是如此。我们的任务是思考关系和沟通。

米尔斯通随后引用了原文,并明确表示他们真正在谈论的是“真正的动态语言”,而不是面向对象的程序设计(OOP):

为了提出更好的[分派]解决方案,Haskell和Clojure采用了截然不同的方法,但都优于任何OO程序员通常使用的方法。

“有面向对象的程序员吗?”不行!。在真正的动态语言中实现的OO不仅公开了固定的分派机制,还公开了分派本身的机制,即元对象协议。

有很多OOP语言具有静态数据类型检查,还有一些非OOP语言是动态的,所以米尔斯通根本不是在谈论OOP,但是米尔斯通确信他们知道什么是OOP。这是我经常遇到的一个问题:一个OOP的狂热拥护者,他使用了OOP的某种特殊定义,这让我完全措手不及。

请注意这里的讽刺:米尔斯通引用了一段批评Java的文章,而米尔斯通却声称这段关于“后期绑定”的文章展示了面向对象的长处。根据这个定义,Java不是一种面向对象的语言,这肯定会让Java程序员大吃一惊。

同样,有些OOP语言没有后期绑定,有些非OOP语言有后期绑定。但对于米尔斯通来说,没有哪个真正的OOP程序员会使用带有静态数据类型检查的语言。在米尔斯通发明了一个完全独特的OOP定义的程度上,他们对OOP的评论与任何其他想要谈论OOP的人完全无关。然而,在某种意义上,米尔斯通是非常常见的:我经常遇到软件开发人员,他们对OOP有着完全独特的定义。这会让我们很难进行有意义的对话。

我们应该如何谈论像OOP这样无定形的概念呢?没有标准的定义,所以我们所能做的就是调查几个不同的来源,并收集主要的想法。我们应该如何进行这项调查?有两种方式:首先是历史之旅,听Alan Kay描述OOP的根源,然后看看目前的一些入门材料在向初学者传授OOP的核心思想。

人们将使用[笔记本]“Dynabook”以在共享大型机上无法实现的方式进行计算;数以百万计的潜在用户意味着用户界面将不得不成为蒙特梭利和布鲁纳那样的学习环境;对于大范围、降低复杂性和最终用户识字的需求,将需要废除数据和控制结构,取而代之的是一种更具生物性的计划,即受保护的通用细胞只通过能够模拟任何所需行为的消息进行交互。

…。在这一切中,我意识到通向基于对象的系统的桥梁可能是将每个对象作为发送给它的消息的语法制导解释器。这将一举将面向对象的语义与完全可扩展语言的理想统一起来。脑海中的形象是一台独立的计算机向其他计算机发送请求,这些请求必须被接收者接受和理解,然后才能发生任何事情。在今天的术语中,每个对象都是一个提供服务的服务器,其部署和判断完全取决于服务器与被服务者之间的关系概念。

这是一个美好的愿景,但这与我们现在所说的“面向对象编程”是一样的吗?在上面的引述中,艾伦·凯似乎在描述一种接近我们现在所说的演员模型的东西。

参与者是执行函数的进程。…。参与者从不共享状态,因此永远不需要争用锁来访问共享数据。相反,参与者通过发送不可变的消息来共享数据。不能修改不可变数据,因此读取不需要锁定。消息以异步方式发送,并缓存在参与者的邮箱中。邮箱本质上是具有多个生产者(其他参与者)和单个消费者的队列。通过基于模式匹配从邮箱接收消息来驱动特定参与者。

艾伦·凯(Alan Kay)对面向对象程序设计(OOP)的美丽描述与我在现实世界中所见过的任何事情都没有关系。每当我指出我们最终得到的结果与承诺的相去甚远时,我经常会得到一个不是真正的苏格兰人的辩护:如果我做得更好,或者使用更纯粹的语言,那么我就会体验到启蒙,突然之间我就会获得真正的面向对象编程的所有好处。然而,即使是艾伦·凯似乎也意识到这样一个事实,我们最终得到的远不是他一开始的样子。当然,他意识到自己的母语Smalltalk从未流行起来。但是他如何看待普及面向对象编程的语言呢?

Sun Microsystems有合适的人将Java变成一种一流的语言,我相信是Sun的营销人员在它应该发布之前匆忙推出了它。

…。如果Sun的专业人员有机会修复Java,世界将会变得更加愉快。这不是秘密信息。这只是这种流行文化的秘密。

艾伦·凯自己从来都不是OOP的盲目的意识形态捍卫者。他借鉴了Lisp的很多想法,并坦率地表达了他对Lisp的钦佩:

凯将SIMULA描述为一套伟大的过渡思想。有了SIMULA,ALGOL块可以作为独立的东西使用,可以承载数据和行为。1966年,当凯读到尼加德和达尔关于SIMULA的早期论文时,他刚刚学会了Sketchpad。凯对他后来所说的面向对象编程进行了生物学上的扭曲。“一切都是一个细胞,”他解释说。“我补充的主要一点是,任何东西都可以是对象。有一种接口代数,今天可以称之为多态。出现了这些观念的碰撞和口齿不清。“。Kay钦佩LISP提供的一组伟大的思想,并将其称为“有史以来设计的最伟大的单一编程语言”。

我自己使用OOP的经验是开了很长时间的会议,讨论一些毫无价值的琐事,比如如何处理Ruby on rails中的胖模型类,把代码重构成更小的部分,每个部分都是一些实用程序代码,尽管我们不允许称它为实用程序代码,因为实用程序代码在OOP下被认为是不好的东西。我见过超智能的人浪费了无数的时间来讨论如何将依赖项注入系统连接在一起,从而使我们能够正确地实例化我们的对象。对我来说,这就是OOP的最大悲哀:这么多才华横溢的头脑被浪费在一个无用的教条上,它带来了很多痛苦,却没有带来任何好处。最糟糕的是,由于OOP未能提供结束我们软件困境的灵丹妙药,每一两年我们就会受到一种新的正统观念的欢迎,每个正统观念都承诺最终使OOP按照最初承诺的方式工作。

SG:嗯,正如它在名称中所说,SOAP是用来访问远程对象的。

SG:和CORBA完全一样,只是更简单。我们使用HTTP,而不是任何人都不允许穿越防火墙的复杂传输协议。我们使用的不是某种二进制消息格式,而是XML。

SG:当然可以。首先是肥皂信封。这相当简单。它只是一个由头和正文组成的XML文档。在身体中,您可以进行RPC调用。

SG:当然可以。正如我所说的,您可以通过将方法名及其参数放入主体中来进行RPC调用。方法名是最外层的元素,每个子元素都是一个参数。所有参数都可以按照规范第5节中的规定键入。

SG:端点,服务的地址。您可以将SOAP信封发送到端点的URL。

戴夫:嗯。如果我将服务移动到不同的端点,会发生什么情况呢?我能拿回301吗?

Dev:那么,当您说SOAP使用HTTP时,您的意思是说HTTP上的SOAP隧道。

SG:嗯,“隧道”这个词太难听了。我们更愿意说SOAP是交通不可知的。

Dev:但是HTTP不是一种传输协议,它是一种应用程序协议。不管怎样,SOAP还支持哪些其他“传输”呢?

SG:嗯,官方没有。但你可能会支持他们中的任何一个。而且有很多平台支持JMS、FTP和SMTP。

戴夫:还有这些“演员”和“必须理解”的属性,有人用过吗?

Dev:嗯,我基本上可以让事情运转起来,但前提是我必须坚持使用一个SOAP堆栈。此外,我不能说我喜欢远程过程调用和序列化对象的想法。

SG:远程过程调用!序列化对象!您是从哪里得到SOAP是关于RPC的印象的?SOAP完全是基于文档的消息传递,我的朋友。

SG:忘了我说的话吧。从现在开始,我们将传递粗粒度的消息--您喜欢“粗粒度”这个词吗?符合XML架构的消息。我们将新样式称为文档/文字,将旧样式称为rpc/encode。

dev:(读取XML Schema规范)。圣徒保佑我们!亚历山大大帝无法解开这一点。

SG:别担心。您的工具将为您创建架构。真的,都是工装的问题。

SG:嗯,它们会反映您的代码(如果可能的话),并自动生成一个兼容的模式。

Dev:反思我的代码?我以为这一切都是关于文档的,而不是序列化的对象。

SG:你没听到我说的吗?这都是工具的问题。无论如何,我们不能指望您手动编写XML Schema和WSDL。再说了,这只是个管道工程。你不需要看的。

SG:哦,我没提到WSDL吗?W-S-D-L。Web服务描述语言。它是如何指定数据类型、参数列表、操作名、传输绑定和端点URI的,以便客户端开发人员可以访问您的服务。快看啊。

dev:(读取WSDL规范)。我相信写这封信的人已经被枪杀了。这甚至不是内部一致的。

围绕WSDL等行业标准发展起来的文化导致詹姆斯·刘易斯(James Lewis)和马丁·福勒(Martin Fowler)抱怨“坦率地说,复杂性令人惊叹”:

当然,微服务社区中使用的许多技术都是从开发人员在大型组织中集成服务的经验中发展而来的。容错读取器模式就是一个这样的例子。使用网络的努力起到了作用,使用简单的协议是从这些经历中衍生出来的另一种方法-这是对中央标准的一种反应,这种反应已经达到了令人惊叹的复杂性,坦率地说,这是一种令人惊叹的反应。(任何时候您需要本体来管理您的本体,您都知道您有很大的麻烦。)。

当然,尽管在这些系统上投入了无数的资金,举行了成千上万的会议和聚会,出版了几十本书,无数的科技公司像Croesus一样致富,但当我提到这些昂贵的灾难时,我被告知没有真正的OOP程序员再做这些无稽之谈了。但是,他们当然会这样做:维护这些脆弱、冗长的遗留系统占OOP程序员所做工作的很大比例。

Smalltalk不仅仅是它的语法或类库,它甚至与类无关。很抱歉,我很久以前就为这个话题创造了“对象”这个词,因为它让很多人把注意力放在次要的想法上。最重要的想法是“消息传递”…。制造伟大的、可扩展的系统的关键更多的是设计其模块如何通信,而不是设计它们的内部属性和行为应该是什么。想想互联网-为了生存,它(A)必须允许超过任何单一标准的许多不同类型的想法和实现,以及(B)允许这些想法之间不同程度的安全互操作性。如果你只关注消息传递-并且意识到一个好的元系统可以后期绑定对象中使用的各种二级架构-那么关于这个主题的很多基于语言、UI和操作系统的讨论都是没有实际意义的。

有人真的认为OOP是给我们“消息传递”的最好方式吗?目前,我们有许多技术可以帮助传递消息。函数范式的倡导者可能会说这样的话:“纯函数与参与者模型的结合更进一步地为我们提供了Alan Kay似乎认为最好的东西”,但我要指出的是,您可以将自己限制在使用PHP,并且只使用那些早在2004年PHP4中提供的特性,然后添加一种现代成分f。

..