通过创建账户享受更多免费内容和福利
保存文章以供以后阅读需要一个IEEE Spectrum帐户亚博真人yabo.at
研究所的内容仅供会员使用
下载完整的PDF刊物是IEEE会员的独家权利
访问光谱的数字版本是IEEE会员的独家
以下主题是IEEE成员的独家特性
在文章中添加回复需要IEEE Spectrum帐户亚博真人yabo.at
创建一个帐户以访问更多内容和功能亚博真人yabo.at包括保存文章以供以后阅读,下载Spectrum Collections,以及参与与读者和编辑的对话。有关更多独家内容和功能,请考虑加入IEEE.
加入世界上最大的致力于工程和应用科学的专业组织,并获得Spectrum的所有文章、档案、PDF下载和其他福利。了解更多→
你期望人生中最长、最昂贵的阶段一个软件产品的周期是系统的初始开发,当所有这些伟大的功能都是第一次想象,然后创建。事实上,最难的部分出现在后面的维护阶段。这时程序员就会为他们在开发过程中走的捷径付出代价。
那么,他们为什么要走捷径呢?也许他们没有意识到自己在偷工减料。只有当他们的代码被大量用户部署和使用时,隐藏的缺陷才会暴露出来。也许开发人员太匆忙了。推向市场的时间压力几乎保证了他们的软件会包含更多的bug。
大多数公司在维护代码时遇到的困难导致了第二个问题:脆弱性。添加到代码中的每一个新特性都增加了代码的复杂性,从而增加了某些东西崩溃的可能性。软件变得如此复杂,以至于开发人员避免对其进行不必要的更改,以免破坏某些东西,这是很常见的。在许多公司,整个开发团队的工作不是开发新东西,而是维持现有系统的运行。你可能会说他们运行的是软件版本的红皇后的比赛他们尽可能快地跑,只是为了呆在同一个地方。
这是一个令人遗憾的情况。然而,当前软件行业的发展趋势是越来越复杂,产品开发时间越来越长,生产系统越来越脆弱。为了解决这类问题,公司通常只是让更多的人来解决问题:更多的开发人员、更多的测试人员和更多的技术人员在系统出现故障时进行干预。
肯定有更好的办法。我是越来越多的开发人员中的一员,他们认为答案可能是函数式编程。在这里,我将描述函数式编程是什么,为什么使用它会有帮助,以及为什么我对它如此热情。
使用函数式编程,少即是多
一个很好的理解方式函数式编程的基本原理是考虑半个多世纪前发生的事情。在20世纪60年代后期,出现了一种旨在提高代码质量同时减少所需开发时间的编程范式。它叫做结构化程序设计.
出现了各种各样的语言来促进结构化编程,一些现有的语言被修改以更好地支持它。这些结构化编程语言最显著的特性之一根本不是一个特性:它缺少一些已经存在很长时间的东西GOTO声明.
GOTO语句用于重定向程序执行。程序流不是按顺序执行下一个语句,而是重定向到另一个语句,即GOTO行中指定的语句,通常在满足某些条件时执行。
取消GOTO是基于程序员从使用它中学到的东西——它使程序非常难以理解。带有goto的程序通常被称为意大利面条代码,因为执行的指令序列可能就像一碗意大利面条中的单条指令一样难以遵循。
Shira Inbar
这些开发人员无法理解他们的代码是如何工作的,或者为什么它有时不起作用,这是一个复杂的问题。那个时代的软件专家相信那些GOTO声明正在制造不必要的复杂性GOTO不得不,嗯,离开。
在当时,这是一个激进的想法,许多程序员抵制失去他们已经逐渐依赖的语句。这场争论持续了十多年,但最终,GOTO灭绝了,今天没有人会主张它的回归。这是因为从高级编程语言中消除了它,大大降低了复杂性,提高了所生产软件的可靠性。它通过限制程序员可以做的事情来做到这一点,这最终使他们更容易推理他们正在编写的代码。
尽管软件行业已经从现代高级语言中剔除了GOTO,但软件的复杂性和脆弱性仍在继续增长。为了寻找其他可以修改这些编程语言以避免一些常见陷阱的方法,软件设计人员可以从硬件方面的同行那里找到灵感,这很奇怪。
用空引用使问题无效
在硬件设计中对于一台电脑,你不能有一个电阻共享由说,键盘和显示器的电路.但是程序员在他们的软件中一直在做这种共享。它被称为共享全局状态:变量不属于任何一个进程,但可以被任意数量的进程更改,甚至可以同时更改。
现在,想象一下,每次你打开微波炉,你的洗碗机的设置就从“正常循环”变成了“锅碗瓢盆”。当然,这在现实世界中不会发生,但在软件中,这种事情一直在发生。程序员编写调用函数的代码,期望它执行单个任务。但许多函数都有副作用,会改变共享全局状态,引起意外后果的.
在硬件领域,这种情况不会发生,因为物理定律限制了可能性。当然,硬件工程师可能会搞砸,但不像软件工程师那样,有太多的事情是可能的,无论是好是坏。
潜伏在软件泥潭中的另一个复杂性怪物叫做空引用,这意味着对记忆中某个地方的引用根本没有指向任何东西。如果尝试使用此引用,则会出现错误。因此,程序员必须记得在试图读取或更改它引用的内容之前检查它是否为空。
今天几乎每一种流行的语言都有这个缺陷。开创性的计算机科学家Tony Hoare中引入了空引用大陵五早在1965年就出现了这种语言,后来它被纳入了许多其他语言。霍尔解释说,他这样做“只是因为它很容易实施”,但今天他认为这是一个“价值数十亿美元的错误”。这是因为当程序员期望有效的引用实际上是空引用时,它已经导致了无数的错误。
软件开发人员需要非常严格的纪律来避免这样的陷阱,有时他们没有采取足够的预防措施。结构化编程的架构师知道这对于GOTO语句是正确的,因此没有给开发人员留下任何逃生的舱口。为了保证无goto代码所承诺的清晰度的提高,他们知道他们必须从结构化编程语言中完全消除goto。
历史证明,删除一个危险的特性可以极大地提高代码的质量。今天,我们有大量危害软件健壮性和可维护性的危险实践。几乎所有的现代编程语言都有某种形式的空引用、共享的全局状态和带有副作用的函数——这些东西比GOTO要糟糕得多。
怎样才能消除这些缺陷呢?结果是已经存在几十年了:纯函数式编程语言。
第一个流行起来的纯函数式语言叫做Haskell,创建于1990年。因此,到20世纪90年代中期,软件开发领域真正找到了解决仍然面临的棘手问题的方法。遗憾的是,当时的硬件通常不够强大,无法使用该解决方案。但是今天的处理器可以很容易地满足Haskell和其他纯函数式语言的需求。
事实上,基于纯功能的软件特别适合现代多核cpu.这是因为纯函数只对它们的输入参数起作用,使得不同函数之间不可能有任何交互。这使得编译器可以被优化,以生成高效、轻松地运行在多核上的代码。
顾名思义,使用纯函数式编程,开发人员只能编写纯函数,根据定义,它不会有副作用。有了这个限制,就增加了稳定性,为编译器优化打开了大门,最终得到的代码更容易理解。
但是如果一个函数需要知道或者需要操纵系统的状态呢?在这种情况下,状态通过一个被称为组合函数的长链传递,这些函数将它们的输出传递给链中下一个函数的输入。通过将状态从一个函数传递到另一个函数,每个函数都可以访问它,并且没有机会让另一个并发编程线程修改该状态——这是在太多程序中发现的常见且代价高昂的脆弱性。
避免空引用意外
Javascript和Purescript的比较表明后者可以帮助程序员避免错误。
函数式编程也有一个解决Hoare“十亿美元错误”的方法,即空引用。它通过禁止空值来解决这个问题。相反,有一个构造通常称为也许(或选项在某些语言中)。一个也许可以没有什么或只是一些价值。处理也许年代迫使开发人员总是考虑这两种情况。在这件事上他们别无选择。他们必须处理没有什么每次他们遇到也许.这样做可以消除空引用可能产生的许多错误。
函数式编程还要求数据是不可变的,这意味着一旦将变量设置为某个值,它就永远是那个值。变量更像是数学中的变量。例如,要计算一个公式,y=x2+ 2x- 11,你选择一个值x在计算的过程中y做x接受不同的价值。同样的值x用于计算x2As在计算2时使用x.在大多数编程语言中,没有这样的限制。你可以计算x2用一个值,然后改变的值x计算前2x.通过禁止开发人员更改(突变)值,他们可以使用在中学代数课上所做的相同推理。
与大多数语言不同,函数式编程语言深深植根于数学。正是这种高度规范的数学领域的血统赋予了函数式语言最大的优势。
为什么呢?这是因为人们研究数学已经有几千年了。它很结实。大多数编程范式,比如面向对象编程,背后最多有6年的工作经验。相比之下,他们是粗糙和不成熟的。
想象一下,如果你每次打开微波炉,你的洗碗机的设置就从“正常循环”变成了“锅碗瓢盆”。在软件行业,这种事情一直在发生。
让我分享一个与数学相比,编程是多么草率的例子。我们通常教新程序员,当他们第一次遇到这个语句时,忘记他们在数学课上学到的东西X = X + 1.在数学中,这个方程有0个解。但在今天的大多数编程语言中,X = X + 1不是方程。这是一个声明命令计算机取x,给它加1,然后把它放回一个叫x.
在函数式编程中,没有语句,只有表达式.我们在中学学到的数学思维现在可以在用函数式语言编写代码时使用。
由于函数的纯粹性,您可以使用代数替换来推理代码,以帮助降低代码的复杂性,就像您在代数课上降低方程的复杂性一样。在非函数式语言(命令式语言)中,没有等效的机制来推理代码如何工作。
函数式编程有一个陡峭的学习曲线
纯函数式编程通过从语言中移除危险的特性来解决我们行业中许多最大的问题,使开发人员更难搬起石头砸自己的脚。起初,这些限制可能看起来很激烈,我相信20世纪60年代的开发者对移除《GOTO》的感受也是如此。但事实是,在这些语言中工作既解放又强大——以至于几乎所有当今最流行的语言都包含了函数式特性,尽管它们仍然是基本的命令式语言。
这种混合方法的最大问题是,它仍然允许开发人员忽略语言的功能方面。如果我们在50年前就把GOTO作为一个选项,我们今天可能还在为面条式的代码而挣扎。
为了获得纯函数式编程语言的全部好处,您不能妥协。您需要使用从一开始就按照这些原则设计的语言。只有采用它们,你才能得到我在这里概述的许多好处。
但是函数式编程并不是一帆风顺的。这是有代价的。根据这个函数式范式学习编程几乎就像从头开始学习编程一样。在许多情况下,开发人员必须熟悉他们在学校没有学过的数学。所要求的数学并不难——它只是新的,对数学恐惧症患者来说是可怕的。
更重要的是,开发人员需要学习一种新的思维方式。起初,这将是一个负担,因为他们不习惯。但随着时间的推移,这种新的思维方式变成了第二天性,与旧的思维方式相比,最终减少了认知负担。其结果是效率的巨大提高。
但是过渡到函数式编程可能很困难。几年前我自己的经历就很能说明问题。
我决定学习haskell——而且需要在业务时间轴上学习。这是我40年职业生涯中最困难的学习经历,很大程度上是因为没有明确的来源来帮助开发人员过渡到函数式编程。事实上,在之前的30年里,没有人写过关于函数式编程的非常全面的东西。
为了获得纯函数式编程语言的全部好处,您不能妥协。您需要使用从一开始就按照这些原则设计的语言。
我只能在这里,那里,到处捡零碎的东西。我可以证明这一过程是非常低效的。我花了三个月的时间日日夜夜和周末和哈斯克尔生活在一起。但最后,我发现用它写的代码比其他任何东西都好。
当我决定我们的公司应该改用函数式语言时,我不想让我的开发人员经历同样的噩梦。因此,我开始为他们建立一个课程,这成为一本旨在帮助开发人员过渡到函数式程序员的书的基础。在我的书,我提供了获得熟练使用函数式语言的指导PureScript它窃取了Haskell的所有优点,并改进了它的许多缺点。此外,它能够在浏览器和后端服务器中运行,使其成为当今许多软件需求的出色解决方案。
虽然这样的学习资源只能有所帮助,但要让这种转变更广泛地发生,基于软件的企业必须在他们最大的资产——开发人员——上投入更多。在我的公司,全景软件在美国,我是首席技术官,我们已经进行了投资,所有的新工作都是在PureScript或Haskell中完成的。
三年前,我们开始采用函数式语言,从另一种纯函数式语言开始榆树因为它是一种更简单的语言。(我们根本不知道,我们最终会超越它。)我们花了大约一年的时间才开始受益。但自从我们渡过难关后,一切都很美好。我们没有生产运行时的错误,这在我们以前使用的产品中是很常见的,JavaScript前端是Java,后面是Java。这种改进允许团队花费更多的时间向系统添加新功能。现在,我们几乎不用花时间调试生产问题。
但是,当使用一种很少有人使用的语言时,仍然存在一些挑战——特别是缺乏在线帮助、文档和示例代码。而且很难雇佣到有这些语言经验的开发人员。正因为如此,我的公司聘请了专门寻找函数式程序员的招聘人员。当我们雇佣一个没有函数式编程背景的人时,我们会在头几个月对他们进行培训,让他们跟上进度。
函数式编程的未来
我的公司很小。它向政府机构提供软件,使他们能够帮助退伍军人从政府部门获得福利美国退伍军人事务部.这是非常有益的工作,但不是一个有利可图的领域。由于利润微薄,我们必须使用所有可用的工具,用更少的开发人员做更多的事情。为此,函数式编程就是门票。
像我们这样不起眼的企业很难吸引到开发者,这是很常见的。但我们现在能够雇佣顶级人才,因为他们想要开发功能代码库。在这一趋势中处于领先地位,我们可以获得大多数像我们这样规模的公司梦寐以求的人才。
我预计,采用纯函数式语言将提高整个软件行业的质量和健壮性,同时大大减少在用函数式编程根本无法产生的错误上浪费的时间。它并不神奇,但有时感觉是这样,每当我被迫使用非功能性代码库时,我就会想起它有多好。
软件行业正在为范式转变做准备的一个迹象是,函数性特性出现在越来越多的主流语言中。整个行业要完全实现转型还需要做更多的工作,但这样做的好处是显而易见的,毫无疑问,这是事情的发展方向。
本文发表在2022年12月的印刷版上,题为“消灭虫子的新方法”。