“面向对象就是一个错误!”

[转载出处:www.11jj.com]

【CSDN 编者按】面向对象编程的流行是计算机科学领域的不幸,它对现代经济造成了极大的破坏,造成了数万亿美元的间接损失。在过去的三十年中,几乎所有行业都因潜在的面向对象编程危机而受到影响。 [原创文章:www.11jj.com]

作者 | Ilya Suzdalnitski
编译 | 弯月   责编 | 张文
出品 | CSDN(ID:CSDNnews)
C++和 Java 可能是计算机科学领域最大的错误。就连面向对象的创建者 Alan Kay 都曾对这两门语言提出了严厉的批评。然而,C++和 Java 都是比较主流的面向对象语言。
面向对象编程的流行是计算机科学领域的不幸,它对现代经济造成了极大的破坏,造成了数万亿美元的间接损失。在过去的三十年中,几乎所有行业都因潜在的面向对象编程危机而受到影响。
为什么面向对象编程如此危险?下面我们一起来寻找答案。
2007 年 9 月,美国 Jean Bookout 驾驶的 2005 款凯美瑞突然失控,Bookout 尝试刹车但是失败,最终发生了碰撞事故,导致车内另一人身亡,Bookout 受伤。然而,此案只是丰田在美上百起车辆意外加速投诉的其中之一。
在 Bookout 事件调查的过程中,原告方聘请了两位软件专家,他们花了将近 18 个月的时间来研究丰田代码。最终,他们都形容丰田代码库为“面条式代码”(Spaghetticode),程序的流向就像一盘面条一样扭曲纠结在一起。
软件专家演示了大量丰田软件可能导致意外加速的情况。最终,丰田被迫召回 900 多万辆汽车,赔付款项高达 30 多亿美元。


面条式代码有什么问题?


然而,丰田并不是唯一一家有面条式代码问题的公司。曾经有两架波音 737 Max 飞机坠毁,造成 346 人死亡,损失超过 600 亿美元。这两起事件的原因也出在了软件 bug 上,而且都是由面条式代码引起的。
面条式代码困扰着全世界上许许多多的代码库,包括飞机、医疗设备以及核电站上运行的代码。
程序代码不是为机器编写的,而是为人类编写的。Martin Fowler 曾说过:“任何傻瓜都可以编写计算机能够理解的代码。但只有优秀的程序员可以编写人类能够理解的代码。
如果代码不能正常运行,那说明出了问题。但是,如果人们不理解代码,那么它肯定会出问题。迟早的事儿。
此处,我们来谈论一下人类的大脑。人脑是世界上最强大的机器。但是,它有其自身的局限性。我们的工作记忆是有限的,人脑一次最多只能思考 5 件事。这意味着,程序代码的编写方式不应该超出人脑的局限。
然而,面条式代码导致人类无法理解代码库。这就会埋下深远的祸根,因为我们不清楚某些代码变动是否会引发问题。我们无法运行详尽的测试,找出所有缺陷,甚至没有人知道这样的系统是否能正常工作。即便系统能够正常工作,我们也不明白为什么。


面条式代码的起因


为什么经过一段时间的发展之后,代码库会出现面条式代码?因为熵。宇宙中的一切都变得混乱无序。就像电缆终将乱如一团麻,我们的代码最终也将变得混乱不堪。除非我们施加足够的约束。
为什么高速公路有时速限制?这是为了防止我们撞车。为什么道路上有交通信号?为了防止人们走错路,为了防止事故发生。
编程也一样。这样的约束不应让程序员来决定,应该通过工具自动实现,或者理想情况下通过编程范例本身来实现。


为什么面向对象是万恶之源?


我们怎样才能施加足够的约束,防止面条式代码的出现?两个办法:手动或自动。手动很容易出错,人类难免会犯错。因此,我们理应自动执行此类约束。
然而,面向对象编程并不是我们一直在寻找的解决方案。它没有提供任何约束来帮忙解决代码扭曲纠缠的问题。一个人可以精通各种面向对象编程的最佳实践,例如依赖注入、测试驱动的开发、领域驱动的设计等(这些实践确实有帮助)。但是,这些都不是由编程范例本身来强制执行的(而且也没有相应的工具来强制执行最佳实践)。
面向对象编程内部没有任何功能可以帮助我们预防面条式代码,封装只是隐藏和打乱了程序的状态,只会让情况变得更糟。继承带来了更多的混乱。面向对象编程的多态性更是火上浇油,我们根本不知道程序运行时会采用哪种确切的执行路径。特别是在涉及多个继承级别时。

面向对象进一步加剧了面条式代码的问题

然而,面向对象的缺点可不止缺乏适当的约束。
在大多数面向对象编程语言中,默认情况下一切都是通过引用共享的。这实际上将一个程序变成了一个庞大的全局状态。这与面向对象原本的思想背道而驰。面向对象的创建者 Alan Kay 拥有生物学的背景。他想到了一种语言(Simula),可以让我们按照生物细胞的组织方式编写计算机程序。他希望有独立的程序(细胞)通过相互发送消息进行通信。独立程序的状态永远不会与外界共享(封装)。
AlanKay 从来也没想过让“细胞”直接进入其他细胞的内部做任何修改。但现代面向对象编程就这么干了,因为在现代面向对象编程中,默认情况下,一切都是通过引用共享的。这也意味着破坏正常功能的错误无法避免。修改程序的某一部分就会破坏其他功能(这在函数式编程等其他编程范例中很少见。)
我们可以清楚地看到,现代面向对象编程本质上就存在很大的缺陷。它不仅会让你在日常工作中痛苦不堪,而且还会让你夜不成寐。


可预测性


面条式代码是一个重大的问题。面向对象的代码特别容易形成面条式。
面条式代码导致软件无法维护,但这只是问题的一部分。此外,我们还希望软件具有可靠性,以及可预测性。
任何系统的用户都应该享受相同的、可预测的体验。踩下油门,汽车就会加速;相反,踩刹车,汽车就会减速。用计算机科学术语来说,我们希望汽车的行为是确定的。
我们非常不希望汽车表现出随机行为,例如加速器无法加速,或制动器不能减速(丰田的问题)。即使此类问题发生的概率非常低。
然而,大多数软件工程师的心态都是“我们的软件要足够好,才能让客户继续使用。”我们能做的只有这么多吗?当然不是,我们应该做得更好!然而,首先最起码应该解决程序的不确定性。


不确定性


在计算机科学中,确定性算法指的是针对相同的输入,算法始终能够表现出相同的行为。而不确定性算法恰恰相反,即便输入相同,每次运行算法也会表现出不同的行为。
举个例子:
console.log( 'result', computea(2) );console.log( 'result', computea(2) );console.log( 'result', computea(2) ); // output:// result 4// result 4// result 4
无需在意上述函数的具体功能,你只需要知道对于相同的输入,它总是会返回相同的输出。下面,我们看一看另一个函数 computeb:
console.log( 'result', computeb(2) );console.log( 'result', computeb(2) );console.log( 'result', computeb(2) );console.log( 'result', computeb(2) ); // output:// result 4// result 4// result 4// result 2    <=  not good
这一次,这个函数在面对相同的输入时,却给了不同的输出。这两个函数之间有什么区别?前者针对相同的输入,总是能给出相同的输出,就像数学函数一样。换句话说,这个函数是确定的。而后者则不一定会输出预期的值,换句话说,这个函数是不确定的。
如何判断某个函数是确定的,还是不确定的?
  • 不依赖外部状态的函数百分百都是确定的。

  • 只调用其他确定的函数的函数也是确定的。

function computea(x) {  return x * x;} function computeb(x) {  return Math.random()< 0.9          ? x * x          : x;}
在上述示例中,computea 是确定的,它总是能够针对相同的输入给出相同的输出。因为它的输入只取决于参数x。
而 computeb 是不确定的,因为它调用了另一个不确定的函数Math.random()。我们怎么知道 Math.random()是不确定的?因为这个函数会根据系统时间(外部状态)来计算随机值。而且,它也没有参数,只取决于外部状态。
确定性与可预测性之间有什么联系?确定的代码就是可预测的代码。不确定的代码就是不可预测的代码。


从确定的到不确定的

 
我们再来看一个函数。
function add(a, b) {  return a + b;};
我们可以确定,输入(2,2)的结果总是等于 4。我们为什么能确定?在大多数编程语言中,加法操作都是通过硬件实现的,换句话说,CPU 会负责计算结果始终保持不变。除非我们需要处理浮点数的比较(但这是另一个话题,与不确定性问题无关)。这里,我们只讨论整数。硬件非常可靠,因此我们可以放心加法的结果正确无误。
下面,我们给 2 加一个处理:
const box = value => ({ value }); const two = box(2);const twoPrime = box(2); function add(a, b) {  return a.value +b.value;} console.log("2 + 2' == " + add(two, twoPrime));console.log("2 + 2' == " + add(two, twoPrime));console.log("2 + 2' == " + add(two, twoPrime)); // output:// 2 + 2' == 4// 2 + 2' == 4// 2 + 2' == 4
到这里,这个函数依然是确定的!
下面,我们来稍微修改一下函数本身:
function add(a, b) {  a.value += b.value;  return a.value;} console.log("2 + 2' == " + add(two, twoPrime));console.log("2 + 2' == " + add(two, twoPrime));console.log("2 + 2' == " + add(two, twoPrime)); // output:// 2 + 2' == 4// 2 + 2' == 6// 2 + 2' == 8
发生了什么?突然间,函数的结果就不可预测了!第一次运行没有问题,但是后面每次运行得到的结果都是不可预测的。换句话说,这个函数不再具备确定性。
为什么突然变成不确定的?这是因为我们修改了函数作用域之外的一个值,函数出现了副作用。

总结一下

确定的程序可确保 2 + 2 == 4。换句话说,给定输入(2, 2),函数 add 必然会输出 4。无论这个函数被调用多少次,无论是否并行调用该函数,也无论函数外部是什么状况,它必然会输出 4。
不确定的程序则恰好相反,在大多数情况下,add(2, 2)将返回 4。但有时,该函数可能会返回 3、5,甚至 1004。程序中万万不能出现不确定性,我希望你明白为什么。
不确定的代码有什么后果?它们会引发软件缺陷,也就是常说的 bug。遇到 bug,开发人员需要浪费大量宝贵的时间来调试代码,如果将这类代码投入生产,则会大大降低客户体验。
为了让我们的程序更可靠,首先应该解决不确定性的问题。


副作用

 
这里,我们不得不谈一谈副作用。
什么是副作用?通常意义的副作用是指如果你因头痛而服用药物,但这种药物让你感到恶心,那么恶心就是一种副作用。简而言之,副作用就是不良反应。
想象一下,你买了一台计算器。带回家后却发现这不是一台简单的计算器。你输入 10 * 11,而它却输出了 110,同时在你耳边大叫“一百一十”。这就是副作用。接着,你输入 41+ 1,它输出了 42,但又喊道“四十二,死掉。”这也是副作用。你带着满脸的疑惑,打电话叫外卖,结果这台计算器偷听到了你的电话,大声说“好的”,然后还帮你下了订单。这也是副作用。
下面,我们继续来说 add 函数:
function add(a, b) {  a.value += b.value;  return a.value;}
这个函数执行了预期的操作,即 a 加 b。但是,它也有副作用。因为在执行a.value+ = b.value 后,对象 a 会发生变化。假设刚开始的是 a.value=1,则第一次调用完成后,a.value=2。而且第二次调用后,它的值会再次变化。


纯粹

 
在讨论了确定性和副作用后,我们再来看一看纯粹。纯函数是确定的,而且没有副作用。
纯函数有什么优点?它们是可预测的。因此,非常易于测试(无需编写模拟函数和桩函数)。理解纯函数非常容易,不需要将整个应用程序状态都装入大脑。你只需要考虑眼前的函数。
编写纯函数也很容易(因为它们不会修改任何范围之外的数据)。纯函数非常适合并发,因为函数之间没有共享状态。另外,重构纯函数也很简单,只需复制和粘贴,无需复杂的 IDE 工具。
简而言之,纯函数可以让我们快乐地编程。

面向对象编程是否是纯粹的?

为了举例说明,我们看两个面向对象的特性:getter和setter。
getter 的结果取决于外部状态,也就是对象状态。每次调用 getter,得到的结果都不相同,具体取决于系统的状态。因此,getter 本质上是不确定的。
setter会修改对象的状态,因此它们本质上就带有副作用。
这意味着面向对象所有的方法(除静态方法外)或者是不确定的,或者会带来副作用。因此,面向对象编程并不纯粹,它与纯粹背道而驰。

银弹

无知并不值得羞愧,无知又不学才让人羞愧。
—— 本杰明·富兰克林
虽然软件世界里困难重重,但我们仍存一线希望,即便无法解决所有的问题,至少也可以解决大多数问题。但是,只有当你愿意学习和应用,才能成功。
银弹的定义是什么?就是可以解决所有问题的灵丹妙药。经过千百年的努力,数学界也有银弹。
如果不确定性(即不可预测)成为现代科学的支柱,那你觉得我们的世界能走多远?可能不会太远,或许我们还停留在中世纪。医学界就经历过这样的情况,过去我们没有严格的试验来证实特定的治疗方法或药物的功效。
不幸的是,如今的软件行业与过去的医学太相似了。现代软件行业的基础非常脆弱,也就是所谓的面向对象编程。我们希望软件也能够像医学一样,找到坚实的基础。


坚实的基础

 
在编程世界中,我们也可以拥有像数学一样可靠的基础吗?可以!我们可以将许多数学概念直接转化为编程,并以此为基础,也就是我们所说的函数式编程。
函数式编程是编程界的数学,它是非常牢固且强大的基础,可用于构建可靠且健壮的程序。是什么让函数式编程如此强大?这种编程方式的基础是数学,尤其是 Lambda 演算。
做个比较,现代面向对象编程的基础是什么?Alan Kay 的面向对象编程基于的是生物细胞。但是,现代 Java/C#的基础是一套荒谬的思想,比如类、继承和封装等,这些并非源自 Alan Kay 最初的思想。
反观函数式编程,它的核心构件是函数,而且在大多数情况下是纯函数。纯函数是确定性的,因此它们是可预测的。这意味着由纯函数组成的程序将是可预测的。这倒不是说函数式编程没有 bug,但是如果程序中存在 bug,那也是确定的,即对于相同的输入始终会引发相同的 bug,因此非常容易修复。


代码是如何执行到这一步的?


以前,在过程式编程和函数式编程出现之前,goto 语句广泛用于编程语言中。goto 语句允许程序在执行的过程中跳至代码的任何部分。因此,开发人员很难回答:“代码是如何执行到这里的?”而且 goto 语句引发了大量 bug。
如今,面向对象编程也有这个问题。在面向对象编程中,一切都是通过引用传递的。从理论上讲,这意味着任何对象都有可能被其他对象修改(面向对象编程对此没有任何约束)。封装根本没有帮助,它只不过是调用一种方法来更改某些对象的字段。这意味着,程序中的依赖关系很快就会乱成一锅粥,整个程序都会成为一个大型的全局状态。
有什么办法可以解决这个问题吗?没错,就是采用函数式编程。
过去曾经许多人对于停止使用 goto 的建议都有抵触,就像如今很多人会反对函数式编程和不可变状态的思想。


面条式代码怎么办?

 
在面向对象编程中,“组合优于继承”被视为最佳实践。从理论上讲,这类的最佳实践有助于改善面条式代码。然而,这只是“最佳实践”,面向对象的编程范例本身没有任何约束,强制人们遵守这类最佳实践。团队中的初级开发人员是否遵循这类最佳实践,完全看个人,或者你在代码审查中强制实施。
函数式编程如何?在函数式编程中,函数组合(和分解)是构建程序的唯一方法。这意味着编程范例本身会强制执行组合。这正是我们一直在寻找的解决方案。
函数调用其他函数,大函数始终由小函数组成。组合在函数式编程中是很自然的选择。此外,在这种方式下,重构的难度也会降低,只需剪切代码,然后将其粘贴到新函数中即可。无需管理复杂的对象依赖项,也不需要复杂的工具(例如Resharper)。
我们可以看出,要想更好地组织代码,选择面向对象编程并不明智,函数式编程明显更胜一筹。


现在就开始行动


面向对象编程本身就是一个巨大的错误。
如果我知道我乘坐的汽车运行的软件是由面向对象编程语言编写的,我会感到害怕;知道我和家人乘坐的飞机使用了面向对象的代码,也会让我感到不安。
我们应该采取行动,认识到面向对象编程的危险,并努力学习函数式编程。我知道这个过程很漫长,至少需要十年才能做出转变。
但我相信在不久的将来,终有一天面向对象编程会退出这个舞台,就像如今的 COBOL 一样。
参考链接:https://suzdalnitski.medium.com/oop-will-make-you-suffer-846d072b4dce

☞ 乐视视频 App 图标改为“欠 122 亿”,网友:我在别家分红包,却在你家随份子!

计算机科学界至今未解决的四大难题

万字长文爆肝 DNS 协议!

自媒体微信号:11jj扫描二维码关注公众号
爱八卦,爱爆料。
小编推荐
  1. NO.1 AI技术下的娱乐圈女星全都.avi了

    这是娱乐圈行动派——去酱 的第32篇《我去》 今天,我看了好几个女星的小黄片(封面) 这两天,有手艺人把杨幂的五官换到了《射雕英雄传》的黄蓉

  2. NO.2 日本风俗放飞记(一)东京泡泡浴

    副标题#e# 这篇日本放飞的攻略质量还是很不错的,很值得一读,作者还在陆续写,之后的行程会包括东京和大阪的很多不同业态,很有意思,推荐给

  3. NO.3 2019每日胎神方位(每日胎神占什么位置)

    胎神占方 古老的传说里,一直有所谓的胎神存在,农历(黄历)上可见胎神的项目,民间习俗相信胎儿生命受胎神支配,于是诸多禁忌衍生,多半在限

  4. NO.4 2019年阿里巴巴最新股权结构图解曝光,来看看马云的股权有多少?

    阿里的股权结构与合伙人制度,一直都是各行各业的典范。 2019年7月30日,阿里巴巴重新更改股权结构。 报告显示: 阿里高管和董事合计持股降至

  5. NO.5 让子弹飞结局什么意思(让子弹飞弟兄七人暗喻)

    比如开场时一群马拉着一辆火车在飞驰,汤师爷一伙人在车上吃火锅,他到底想表达什么? 文|蟑教授的茶炉 张麻子和黄四郎早就认识 为什么用马拉

  6. NO.6 教你写出牛逼团队介绍文案(附:团队介绍创意简短50字范文)

    团队介绍(一) 我们的制作团队不光在技术上处于行业前端,我们更注重将客户的产品表达的清楚与准确,我们大多是理科出身,在机械、游戏、电子

  7. NO.7 剑三怎么抓马地点(剑三重制版马驹刷新点)

    龙子和麟驹天天的刷新纪律是 早上7点到11点是一轮 正午11点到15点是一轮 下昼15点到19点是一轮 晚上19点到23点是一轮 凌晨23点到03点是一轮 凌晨03点

  8. NO.8 单一窗口申报系统(单一窗口怎么打报关单)

    而使用单一窗口后,企业只需要在一个窗口、一次录入,就能办完所有申报流程,从申报到放行结关最快只需2小时。 国际贸易单一窗口标准版共包括

Copyright2018.依依自媒体资讯站,让大家及时掌握各行各业第一手资讯新闻!