C++、Rust 不堪重负,搞编程语言设计不要太复杂!

作者 | Alan Wu       译者 | 弯月

出品 | CSDN(ID:CSDNnews)

在本文中,我们来谈一谈为什么在设计编程语言时,我们应该采取极简主义的思想。

在我看来,在设计编程语言时,有意保持简约的思想被高度低估了。大多数现代编程语言的设计更加偏向于极繁主义。快速添加新功能有助于提高竞争优势。似乎大家普遍认为,如果你的语言没有特性X,那么人们就会选择使用另一种语言;还有人认为,添加更多特性是展示进步的一种手段。这种思想过于简单,忽略了编程语言要想取得成功和进步所必需的许多其他关键方面,比如容易学习、稳定性、工具支持以及性能等。

变化和流失

我认为,我们设计的编程语言应该拥有更少的功能,并且随着时间的推移功能的变化也不应太快,这本身就是一个强大的特性。编程语言经常变化,必然会出现问题,并导致用户流失。工具会过时,代码需要更新,库会遭到破坏,同时也会造成用户流失。

我第一次使用C++编写代码大约是在1998年左右。我已经有很多年没有接触该语言了,不得不说,我感到无所适从。C++添加了如此多的新功能,感觉完全是另一种语言。去年,我想在新项目中使用C++20模块,结果发现G++和Clang的支持根本不完善,以至于这些模块根本无法使用。当时,我的总体印象是,可能是由于开发人手不足,所以C++编译器迟迟跟不上最新的步伐。该语言变得如此复杂,而且还添加了如此多的新功能,编译器开发人员有点忙不过来。在我看来,长此以往,C++必将不堪重负,被自身压垮。

CSDN付费下载自视觉中国

许多人忘记了一点:一门编程语言要想取得成功,必须拥有良好的工具支持。如果语言及其功能不断变化,那么相关的工具就需要不断更新。C++有很多问题,其中之一便是语法很难解析。这个问题早在1998年就已显现。而如今C++的语法每过一两年就会变化,变得越来越复杂,你觉得这会产生什么影响?维护C++工具的开发人员会另寻出路,这些工具的用户也会离开。

容易学习和人的因素

最近,我和同事们决定将C代码库移植到Rust。总的来说,我对Rust的核心特性很满意,而且我觉得较之C与C++,Rust在很多方面都有很大的改进。然而,在我看来,Rust的主要缺点之一就是过于复杂。无论是句法还是语义,Rust都是一种非常复杂的语言。它的语法非常冗长,需要掌握的知识很多,而且有很多规则和微妙之处,让人搞不清楚哪些可以实现,而哪些不行。Rust的学习曲线陡峭,认知负荷很高。

CSDN付费下载自东方IC

上周,我和一位同事结对编程时,他说:“我觉得Rust 编译器总在嘲笑我太愚蠢了。”这句话让我觉得很惊讶,因为我也有同样的感受。不知为何,Rust总让人感觉不太舒服,该语言的高度复杂性感觉对用户充满了敌意。它会打破你的直觉,似乎编译器总在告诉你代码写错了。在与同事的这番对话后,我看到一篇文章也谈到了Rust的复杂性,文中的描述与我们的感受遥相呼应。

我认为,从很多方面来看,编程语言都应该保持简约,概念越少越好,只选择可以很好地结合在一起的原语,这样才更容易学习。编程语言的概念越少,我们需要学习的知识就越少,掌握的速度就会越快。用更简约的语言编写的代码,读起来也更容易。想一想C++代码,该语言拥有如此多的冗余特性,所以一般我们只用C++的子集编写代码,而且某些语言特性被明确禁止。这可能意味着,不同项目编写的C++代码很难被其他人读懂,在外人看来这些C++代码就像是另外一种语言。

我觉得,从某些方面来看,将编程语言的复杂性和功能数量降到最低,是一种更好地尊重程序员的方式。程序员的生活很忙碌,有许多工作要做,他们可能没有时间阅读数百页的文档来学习我们的语言,我们必须理解他们。编程语言是用户接口,因此应该遵循最小惊讶原则。将复杂性降到最低也是减少认知负担和尊重人类局限性的一种方式。人类是非常有能力的生物,但从本质上说我们也只是会说话的聪明猴子。我们能记住的工作很有限,我们能考虑到的设计约束很有限,我们的专注时间也很有限。尽管我们有人类的局限性,但设计良好的编程语言可以帮助我们取得成功。

归根结底,我认为语言的复杂性和简单性会影响吸引和留住新用户的能力。在我看来,对于Python最初的成功和迅速普及,Python创造者在降低学习难度上付出的辛劳功不可没。坦白来说,我认为,Python 2升级到Python 3为Python生态系统增加的复杂性,以及引入 := 海象运算符等决策,都让许多人感到很失望。

极简主义

到目前为止,我已多次提到极简主义,而且我还提到了最小惊讶原则。对于编程语言来说,极简主义意味着拥有的功能集更小,需要学习的概念也更少。然而,除了最低限度的功能集之外,极简主义还意味着仔细选择可以无缝组合在一起的功能。如果我们设计一种具有大量特征集的语言,不同特征之间的组合与交互的可能性无限多,这意味着最终很有可能遇到一些语言特征的交互出现问题的状况。

CSDN付费下载自视觉中国

对于命令式编程语言,语句和表达式的语法通常都有一定的区别。而函数式编程语言的结构则更趋向于一切都是表达式。因此,函数式编程语言更简约,对程序员的约束也更少。某些语言会区分编译时的代码和程序执行时的代码。这种区分往往会增加语言的复杂性,因为部分语言功能往往会重复,而且对于编译器能够在编译时运行的代码也有一定的限制。

最小惊讶原则指的是,我们希望避免引入仅在某些情况下出现的极端情况。另一个需要避免的陷阱是,引入程序员可能没有预料到的隐藏行为。举个例子,JavaScript中的等号(==)运算符实际上包含了到字符串类型的隐式转换,这意味着1 == “1” 的计算结果为真。由于这种不受欢迎的隐藏行为,JavaScript还有一个非常严格的等号运算符(===),它不会执行字符串转换。在我看来,JavaScript应该只有一个严格的等号运算符,如果你想在执行相等比较之前将值转换为字符串,那么就应该明确写出来。

实现的复杂性

设计编程语言很困难,因为编程语言的设计空间是无限的,因此我们必须做出妥协。另一方面,我们很难利用数据来量化哪些因素导致一种设计比另一种更好。但有些方面可以在某种程度上量化,比如语言实现的复杂性以及特定语言实现的表现。

我的博士论文谈到了JavaScript ES5的JIT编译器的实现。因此,我非常熟悉JavaScript的语义,而且很清楚为了提高JavaScript代码的运行速度,我们需要在背后采取哪些措施。但这次经历并不愉快,我发现,JavaScript以及许多其他语言中的很多复杂性和隐藏行为对任何人都没有好处。

CSDN付费下载自东方IC

语言中存在不必要的复杂性,对于学习该语言的人来说这是个坏消息,因为会增加学习的难度;对于每天使用该语言的程序员来说这是个坏消息,因为会增加他们的认知负担,并使得理解代码变得更加困难;对于语言实现者和工具维护者来说这也是个坏消息,因为会导致他们的工作更加困难;对于最终用户来说,这更加不是好消息,因为会导致软件出现更多错误,性能更差。

举一个不必要的实现复杂性的例子,许多面向对象的语言都有一个从Smalltalk借来的概念,即一切都应该是一个对象,包括布尔值和整数值。同时,这些语言的实现必须在幕后做大量工作,才能更高效地表示整数,同时向用户呈现类似于对象的接口。然而,向用户呈现整数对象的抽象通常与普通的OOP对象的抽象并不完全相同,这是一种有漏洞的抽象,因为能够重新定义整数值没有意义,因为整数值必须是独一无二的。此外,能够在整数上添加属性是非常糟糕的想法,会影响到性能,因此通常是不允许的。

归根结底,整数不是面向对象意义上的对象。它们是一种具有特殊含义的原子值。“一切都应该是一个对象”是一个错误的概念,在实践中也没有任何积极的意义。这种做法只会让语言的实现变得更加复杂,同时也会加重程序员的负担。

建议

虽然我在本文中提出了很多批评意见,但我也会尝试给出一些建议。

第一条建议,编程语言的初始设计不宜过大。编程语言是用户接口,是人们用来与机器交互的API。API的范围越小,引入意外复杂性和细微设计错误的风险就越小。

第二条建议,编程语言应尽可能保持简约。功能越少,出现重复的可能性就越小,同时也意味着你可以挑选能够为程序员提供最具表现力和最大价值的功能。如果想发展编程语言,那就慢慢来。花一些时间用你的语言编写代码,并弄清楚你所做的设计更改的潜在影响。

慢慢添加新功能很容易,但是如果你添加了新功能,而且已经有人开始使用,那么就很难甚至不可能撤销这些功能,因此一定要慎重选择。请记住,你不必取悦所有人,也无需接受每个功能请求。没有任何一种语言或工具可以满足所有人的需求。

最后,,就像用户接口设计一样。Brainfuck是一种非常小而且概念很少的语言,但没有人会称它为富有表现力或优雅的语言。Lisp被许多人认为是现存最美丽、最优雅的语言之一,但我的导师习惯于用单字母命名变量,而且代码中的注释很少。优雅的语言不会自动生成优雅的代码,但你可以鼓励大家遵循良好的编程实践。

本文文字及图片出自 CSDN

余下全文(1/3)
分享这篇文章:

请关注我们:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注