如果搜索“最佳编程语言”,结果会罗列一堆文章。这些文章涵盖各主流语言,并且大多对各语言优缺点的表述模棱两可,表述不到位,缺少实战借鉴意义。本文概述了当前在用的现代编程语言,按推荐程度从低到高依次列出。希望本文有助于读者选择合适的工具完成工作,降低开发工作量。原文篇幅过长。译文按设计用于命令式编程的 C 语言家族,以及设计用于响应式编程的 ML 语言家族,分为上下两篇提供。本文是上篇。
如何了解某种编程语言的优缺点?某种编程语言是否适用于我的项目?面对此类问题,如果求助于搜索引擎,输入“最佳编程语言”,结果会罗列一堆文章,涵盖 Python、Java、JavaScript、C#、C++、PHP 等,并且大多对各语言的优缺点表述得模棱两可。我本人很不喜欢去读此类文章,因为其中许多部分内容的表述并不到位,缺少实战借鉴意义,而且行文生硬。因此我试撰本文来做一次深入总结,去伪存真。
本文概述了当前广为使用乃至可能小众的现代编程语言,力图做到尽量客观、值得一读,并不带个人偏见。各语言按推荐程度从低到高依次列出。
谨记,并不存在所谓完美的编程语言。有的语言非常适用于后端/API 开发,而有的语言则非常适用于系统编程。
文中按当今两大通用语言家族分类,即C衍生语言家族和元语言(Meta Language,ML)衍生语言家族。
对于开发人员而言,编程语言只是工具箱中的工具,更重要的是如何选择合适的工具去完成工作。我衷心希望本文有助于读者选取适合自身项目的编程语言。做出正确的选择,可降低数月甚至数年的开发工作量。
哪些编程语言特性值得关注?
很多编程语言排行榜文章,主要对比的是语言使用广泛度、开发人员收入期望值等因素。在软件领域,虽然大规模的社区和生态系统的确有所裨益,但语言的使用情况并非好的排行指标。本文另辟蹊径,采用的评判依据主要考虑语言的强大之处和不足之处。
为表示所列语言的推荐程度,文中使用“赞”(👍)、“否”(👎)和“尚可”(👌,即不赞也不否)三种 emoji。
那么应该比较哪些特性?换句话说,除了语言使用广泛性,还有哪些特性更能代表语言的受欢迎程度?
类型系统(Type System)
类型系统倍受大量开发人员的青睐,这也是为什么 TypeScript 之类的语言日渐大行其道。在我看来,类型系统去除了大量的程序错误,更容易实现重构。但是否具有类型系统,只是本文考虑的部分评判因素。
支持类型系统的编程语言,最好同时具备类型推断(type inference)。一个好的类型系统,不用明确地标出函数签名(function signature),也能支持对大部分类型的推断。不幸的是,大多数编程语言提供的仅是基本的类型推断功能。
更进一步,支持代数数据类型(Algebraic Data Types,ADT)的类型系统评分更高。
强大的类型系统,还应支持高级类类型(higher-kinded types)。高级类类型是对泛型(generics)的更高阶抽象,支持编程人员在更高的抽象层上编程。
尽管大家对类型系统寄予厚望,但还有一些比静态类型(static typing)更重要的特性。因此在选择一门编程语言时,不能只看是否支持类型系统,
学习难度
即便编程语言是完美无瑕的,如果一位新手上船需要前期投入数月甚至是数年的精力,那么又会有多少人使用呢?另一方面,很多编程范式需要数年的时间才能逐渐完善。
好的编程语言需对新手友好,掌握它们不应花费大量学习时间。
空值
我将 1965 年创建的空值引用(null reference)称为“亿万美元错误”。当时,我正设计首个完全类型系统,用于面向对象语言中的引用。目标是确保所有对引用的使用是绝对安全的,并由编译器自动执行检查。我无法克制添加空值引用的诱惑,完全因为空值引用非常易于实现。近四十年来,这一设计导致了不计其数的错误、漏洞和系统崩溃,可能造成了数十亿美元的痛心损失。
— 空值引用的创立者 Tony Hoare
为什么说空值引用是不好的?因为空值引用破坏了类型系统。一旦默认为空值,那么就不能依靠编译器检查代码的有效性。任何空值都是一枚随时可能引爆的炸弹。如果没能想到所使用的值的确为空值,那么会产生什么后果?会出现运行时错误。
为确保所处理的值并非空值,开发人员必须对运行时做手工检查。即使是静态类型语言,空值引用也破坏了类型系统的很多优点。
运行时检查也称为“空值防护”(null guards),在现实中可归为一种不良的编程语言设计。一方面,引入样板代码破坏了编程风格。更糟的是,它并不能确保我们是否检查了空值。
好的编程语言,应在编译时做类型检查,判断值的存在与否。
因此,支持空值检查机制的编程语言应加分。
错误处理
捕获异常并不是一种好的错误处理方式。抛出异常本身没有问题,但仅适用于程序没有办法恢复而必须崩溃这类异常情况。异常和空值一样,会破坏类型系统。
如果将异常作为错误处理的首选方式,那么就无法获知函数是返回了期望值,还是发生了故障。抛出异常的函数也无法实现复合(Compose)。
无法获取部分数据而导致整个程序崩溃,这显然并非一种好的做法。尽管我们不希望发生这种情况,但它的确会发生。
一种做法是手工检查是否生成异常,但是在编程过程中可能会忘记对异常做检查,因此这种做法是非常不可靠的,而且会在代码中添加大量额外处理。
目前已有更好的错误处理机制,支持在编译时对潜在错误做类型检查。因此,默认无需采用异常处理的编程语言也应加分。
并发
当前业界正处于摩尔定律的末端,即处理器不会再大规模提速。我们身处多核 CPU 时代,所有的现代应用必须能很好地利用多核技术。
不幸的是,大多数当前在用的编程语言都是设计用于单核计算时代的,本质上并不能有效地支持多核处理。
一种亡羊补牢的设计,是在后期提供支持并发的软件库。但这只是给语言打了补丁,并非从根本上就针对并发设计,不能称为良好的开发体验。一些现代语言内建了对并发的支持,例如 Go、Erlang 和 Elixir 等。
不可变性
我认为大型的面向对象程序,需要解决由于大规模可变对象间关联所导致的复杂图结构。否则在调用方法时,必须得把握并牢记该方法的功能和副作用。
—— Rich Hickey,Clojure 创建者。
当前的编程工作中,使用不可变值越来越常见。即便是 React 这样的现代 UI 软件库,也考虑使用不可变值。对支持不可变数值提供一等支持的编程语言,我们会给出更高的评判。这完全是因为不可变性避免了编程中出现许多软件缺陷。
什么是不可变状态?简而言之,就是数据不会发生改变。例如,大多数编程语言中的字符串。字符串转为大写,并不会去改变原始的字符串,而是返回一个新的字符串。
为确保任何事情都不发生改变,不可变性对上述理念做了进一步扩展。更改不可变数组,总是会返回一个新的数组,而非原始数组。更新用户名,将返回一个包含更新后用户名的新用户对象,并不改变原始对象。
不可变状态不做任何共享,因此无需操心线程安全所导致的复杂性。不可变性使得代码更易于并行化。
不对状态做任何更改的函数,称为“纯函数”(Pure)。纯函数更易于测试和推断。使用纯函数,无需操心函数体之外事情,可聚焦于函数本身。不用像面向对象编程中那样必须牢记整个对象图,这样极大地简化了编程开发。
生态系统和工具链
一种编程语言可能本身并没有多少亮点,但如果其具有大型的生态系统,这会令语言更具吸引力。具备良好的软件库,可以节省数月乃至数年的开发工作。
显著的例子就是 JavaScript 和 Python。
速度
语言的编译速度如何?程序的启动速度如何?运行时的性能如何?所有这些都是影响评判中的考虑因素。
诞生年代
尽管并非绝对,通常新推出的语言要比原先的语言更好。只是因为新语言会吸取了前辈的经验教训。
C++
下面从最糟糕、也可能是计算机科学中最大错误的 C++语言开始。当然,我并不认为 C++是一种很好的现代编程语言。但 C++当前依然得到广泛应用,在此必须提及。
语言家族:C
👎 语言特性
C++可称为糟糕透顶的语言……如果项目局限于 C,意味着不会有任何机会被 C++愚蠢的“对象模型”搞砸。
—— Linux 创立者 Linus Torvalds
C++中填充了各种特性,力图无所不能,但在在其中任何一项上都不能说出色。C++支持 goto、指针、引用、面向对象编程、操作符重载,以及各种非生产特性。
为什么说 C++不好?在我看来,最大问题在于 C++颇具年头了。C++是在 1979 年设计的。在当时设计者缺少经验,关注点发散,虽然所添加的特性在当时看来是似乎好的做法。C++得到了非常广泛的使用,这意味着为其中支持各种用例而添加了更多特性,导致特性成堆。
👎 速度
C++的编译时间出奇的慢,甚至比 Java 慢很多,尽管与 Scala 不相上下。
但在运行时性能和启动时间上,C++程序表现非常优秀。
👎 生态系统和工具
上图的推文给出了很好的解释。C++编译器的错误信息对新手并不友好。通常并未指出导致错误的确切原因,需要开发人员花时间查找。
👎👎 垃圾回收
我曾希望在 C++0x 标准中至少考虑可选地支持垃圾回收,但这在技术上存在问题。
—— C++的创建者 Bjarne Stroustrup
垃圾回收从未添加到 C++中,而手工内存管理非常易于出错。开发人员必须操心如何手工释放和分配内存。我对使用非垃圾回收语言的经历记忆深刻,其中大量的缺陷在当前支持垃圾回收语言中可轻易避免。
👎 面向对象编程的失败尝试
我提出了“面向对象”一词,但并没有没有顾及 C++。
—— 面向对象编程的创建者 Alan Kay
面向对象编程是一项很好的技术,出现于上世纪六十年代后期,当时 C++刚出现。不幸的是,不同于 Smalltalk 等语言,C++在实现面向对象编程中出现了几个致命错误,导致好的理念变成噩梦。
好的一方面是,不同于 Java,至少在 C++中面向对象是可选的。
👎👎 学习难度
C++是一种复杂的低层(low level)语言,不具备任何自动内存管理机制。由于特性纷杂,初学者必须花费大量时间学习。
👎 并发
C++设计用于单核计算时代,只支持简单的并发机制,这还是在近十年中添加的。
👎 错误处理
抛出并捕获错误是 C++的首选错误处理机制。
👎 不可变性
未内置对不可变数据结构的支持。
👎 空值
C++中所有引用均可为空值。
评判
C++的初衷是成为更好的 C 语言,但这一初衷并未实现。
系统编程是 C++的最适合使用场景。但考虑到已具有 Rust 和 Go 等更好、更现代的替代语言,系统完全可以不用 C++实现。不管读者同意与否,我不认为 C++具有任何优点。
是该终结 C++的时候了。
Java
Java 是自 MS-DOS 以来计算机领域中最令人困扰的事情。
—— 面向对象编程创始人Alan Kay
Java 出现在 1995 年,比 C++晚了 16 年。Java 是更简单的编程语言,由此得到广泛使用。
语言家族:C。
👍 垃圾回收
相比 C++,Java 的最大优点是具有垃圾回收,这极大地消除了各类软件缺陷。
👍 生态系统
Java 已经存在很长时间,在后端开发领域形成了大型生态系统,极大地降低了开发负担。
👎 面向对象语言
本文不会深入探讨面向对象编程的不足。详细分析可阅读本文作者的另一篇文章,“面向对象编程:亿万美元灾难”。
在此给出计算机科学中一些最为杰出人士的看法:
抱歉,我多年前使用了“对象”一词。该词使得很多人聚焦于一个更狭义的理念,虽然更广义的理念是消息传递。
—— 面向对象编程的创始人 Alan Kay
Alan Kay 是对的,许多主流面向对象编程语言并未找准关注点。它们聚焦于类和对象,而忽视了消息传递。幸运的是,Erlang 和 Elixir 等一些现代编程语言找准了方向。
受面向对象编程影响的编程语言,会导致计算机软件冗长、可读性不好、描述性差、难修改和维护。
所有使用 Java、C#等面向对象编程语言的开发人员,如果曾具有使用非面向对象编程语言的经验,对此应深有体会。
👌 速度
大家都知道,Java 运行在 JVM 之上,而 JVM 的启动速度是出名的慢。我曾看到有运行在 JVM 上的程序耗时 30 多秒才启动起来。对于现代云原生程序,这是不可接受的。
一个大型项目,如果编译速度慢,就会对开发人员的生产效率产生显著影响。Java、Scala 等 JVM 语言存在同样的问题。
但从好的一面说,JVM Runtime 的性能还算不错。
👎 学习难度
尽管 Java 是一种相当简单的语言,但 Java 以面向对象编程为主,这使得 Java 很难做到优秀。编写一个简单的 Java 程序可信手拈来,但是掌握如何编写可靠、可维护的面向对象代码,则需要十数年的 Java 功力。
👎 并发
Java 设计于单核计算时代,和 C++一样,仅支持基本的并发特性。
👎 空值
Java 中,所有引用均可为空值。
👎 错误处理
抛出并捕获错误是 Java 的首选错误处理机制。
👎 不可变性
未内置对不可变数据结构的支持。
判定
Java 在刚推出时,的确是一种很好的编程语言。但遗憾的是不同于 Scala 等语言,Java 始终专注于面向对象编程。 Java 编程严重受模板代码的影响,冗余代码多。
Java 应该退居二线了。
C#
C#和 Java 并没有本质上的差异。C#的早期版本,就是微软的 Java 实现。
C#具有 Java 的大部分优点。C#于 2000 年推出,比 Java 晚 5 年,借鉴了 Java 的经验教训。
语言家族:C
👌 语法
C#在语法上一直保持略微领先 Java。尽管是一种面向对象语言,但 C#在解决模板代码问题上比 Java 有所改进。很高兴看到 C#每个新版本都能改进语法。例如,添加了表达体函数成员(expression-bodied function members)、模式匹配、元组等特性。
👎 面向对象语言
和 Java 一样,C#主要针对面向对象编程。面向对象编程的缺点如上所列,在此不再详述。下面列出一些知名人士的观点。
我认为相比函数式语言,面向对象语言中缺失可重用性。问题在于,面向对象语言需要处理其所提供的所有隐含(implicit)环境。尽管我们想要的只是一根香蕉,但却得到了一只握着香蕉的大猩猩,甚至是整个丛林。
—— Erlang 的创建者 Joe Armstrong
我完全同意这个说法,相比函数式编程,命令式编程非常难以重用面向对象代码。
面向对象编程提供了对正确做法的一个反面教材……
—— 计算机科学先驱 Edsger W. Dijkstra
从我自己使用面向对象和非面向对象编程的经验看,我完全同意面向对象代码更难以正确实现功能。
👎 多范式(Multi-paradigm)
C#声称是一种多范式语言,尤其是声称支持函数式编程,但我并不同意。对函数提供一流支持(first-class functions),并不足以称之为函数式语言。
那么什么语言可称为具备函数式特性?应至少内置支持不可变数据结构、模式识别、组合函数的管道操作符、代数数据类型(ADT)等特性。
👎 并发
和 Java 一样,C#创立于单核计算时代,仅提供基本的并发支持。
👎 空值 Nulls
C#中,所有引用均可为空。
👎 错误处理
抛出并捕获错误是 C#的首选错误处理机制。
👎 不可变性
未内置对不可变数据结构的支持。
评判
尽管我本人的职业生涯中主要使用的是 C#,但还是对这种语言评价不高。与对 Java 的评判一样,我建议读者寻找更现代的替代语言。C#在本质上依然是 Java,只是具有更现代的语法。
不幸的是,C#本身并不“sharp”。
Python
Python 早在 1991 年提出,和 JavaScript 并称当前使用最广的两种语言。
语言家族:C
👍 生态系统
Python 软件库几乎无所不能。不同于 JavaScript,Python 不能用于 Web 前端开发,但大规模的数据科学软件库弥补了这方面的不足。
👍 学习难度
Python 语言非常简单,初学者数周就能上手。
👎 类型系统
Python 是动态类型的,因此谈不上需要类型系统。
👎 速度
Python 是一种解释性语言,性能慢。对性能有严格要求的程序,可使用 Cython 替代原生的 Python。
相对于原生语言,Python 的启动也相当慢。
👎 工具
对比其他的现代编程语言,难免会对 Python 的依赖管理颇为失望。目前存在 pip、pipenv、virtualenv、pip freeze 等工具。相比之下,JavaScript 只需要 NPM 这一种工具。
👎 并发
Python 在创建时并未全面考虑并发,仅提供基本的并发特性。
👎 空值
Python 中所有引用均可为空。
👎 错误处理
抛出并捕获错误是 Python 的首选错误处理机制。
👎 不可变性
未内置对不可变数据结构的支持。
评判
很不幸,Python 并不提供对函数式编程的支持。函数式编程非常适合处理数据科学所面对的问题。即便是在 Python 擅长的 Web 爬虫领域,Elixir 等函数式语言表现更好。
我并不推荐使用 Python 完成大型项目,该语言在构建中并未充分地考虑软件工程。
如果有更好的选择,不推荐在数据科学之外使用 Python。在数据科学领域,Julia 可能是 Python 的很好替代,尽管相比 Python 而言,Julia 的生态系统近乎不存在。
Rust
Rust 是一种现代低层语言,最初设计用于替代 C++。
语言家族:C
👍 速度
运行快速是 Rust 设计所秉持的初衷。在编译性能上,Rust 程序要慢于 Go 程序,但运行时性能比 Go 稍快。
👍 空值
至此,本文推荐列表中终于出现支持现代空值的语言了。Rust 中没有 null 或 nil 值,开发人员使用 Option 模式。
👍 错误处理
Rust 的错误处理引入了现代函数式方法,使用特定的 Result 类型,声明可能会产生失败的操作。Result 模式非常类似于 Option 模式,只是在 None 的情况下依然有值。
👎 内存管理
在本文列出的现代编程语言中,Rust 是唯一不提供垃圾回收的。Rust 迫使开发人员去考虑如何实现底层的内存管理,这影响了开发人员的效率。
👎 并发
由于 Rust 中缺少垃圾回收,因此实现并发是相当困难的。开发人员必须考虑“装箱”(boxing)和“钉住”(Pinning)。这在具有垃圾回收机制的语言中,通常是自动完成的。
👎 不可变性
未内置对不可变数据结构的支持。
👎 低层语言
作为一种低层语言,开发人员的生产效率无法其他高层语言相比。同时,语言的学习难度明显增大。
评判
Rust 非常适合系统编程。尽管比 Go 更复杂,但 Rust 提供了强大的类型系统。Rust 提供了现代的空值替换和错误处理方法。
为什么本文将 Rust 排在 TypeScript 和 JavaScript 之后?Rust 是一种设计用于系统编程的低层语言,并非后端和 Web API 开发的最适合选项。Rust 缺少垃圾回收机制,未内置对不可变数据结构的支持。
TypeScript
TypeScript 语言编译为 JavaScript,通过对 JavaScript 添加静态类型,意在成为一种“更好的 JavaScript”。类似于 JavaScript,TypeScript 同样用于前端和后端开发。
TypeScript 由同是 C#设计者的 Anders Hejlsberg 设计的,因此代码看上去非常类似 C#,可认为是一种用于浏览器的 C#。
语言家族:C。
👎 JavaScript 的超集
TypeScript 将自己定位为 JavaScript 的超集,这有助于人们采用。毕竟大多数人对 JavaScript 耳熟能详。
但作为 JavaScript 的超集,更多程度上是一种缺点。这意味着 TypeScript 继承了 JavaScript 的全部问题,局限于 JavaScript 所有的不良设计决策。
例如,应该没有开发人员喜欢 this 关键词吧。但 TypeScript 依然刻意原封照搬。
再有,其类型系统时常令人感到奇怪。
换句话说,TypeScript 具有 JavaScript 的所有缺点。一种糟糕语言的超集并不会变身成为一种优秀的语言。
👍 生态系统
TypeScript 完全分享了 JavaScript 庞大的生态系统。这是其最大优点。特别是相比 Python 等语言,NPM 非常好用。
缺点在于,并非所有的 JavaScript 软件库都可在 TypeScript 中使用,例如 Rambda/Immutable.js 等。
👌 类型系统
个人感觉,TypeScript 的类型系统毫无亮点。
好的一面是甚至提供对 ADT 的支持。例如下面给出的差别联合(discriminated union)类型:
下面是使用 ReasonML 实现的同样代码:
差别联合类型是在 TypeScript 2.0 中增添的,TypeScript 的语法尚未企及函数式语言的高度。例如,在 switch 中的字符串匹配易于出错,编译器无法在大小写错误时给出警告。
TypeScript 仅提供基本的类型推断。此外在使用 TypeScript 时,any 关键字的出现频次难免过高。
👌 空值
TypeScript 2.0 添加了对不可为空(non-nullable)类型的支持,使用编译器选项--strictNullChecks 启用。但使用不可为空类型并非编程默认,也并非 TypeScript 的惯用做法。
👎 错误处理
TypeScript 中,使用抛出和捕获异常处理错误。
👎 新的 JavaScript 特性
新酷特性首先在 JavaScript 中得到支持,然后才是 TypeScript。实验特性可使用 Babel 在 JavaScript 中得到支持,而在 TypeScript 中则无此功能。
👎 不可变性
TypeScript 对不可变数据结构的处理,要显著劣于 JavaScript。JavaScript 开发人员可使用支持不可变性处理的软件库,但 TypeScript 开发人员通常必须依赖原始数组或对象展开操作符(spread operator),即写入时复制(copy-on-write)。
正如上面代码所示,原生扩展操作符并不支持深拷贝(deep copy),而手工扩展深度对象非常繁琐。大型数组和对象的拷贝的性能也非常不好。
但 TypeScript 中,readonly 关键字非常好用,用于定义属性是不可变的。虽然如此,TypeScript 要对不可变数据结构提供很好支持,依然需要很多工作。
JavaScript 提供了一些操作不可变数据的很好软件库,例如 Rambda/Immutable.js。但是,实现此类软件库对 TypeScript 的支持并非易事。
👎 TypeScript 对比 React
相比Clojure等从设计上考虑到不可变数据处理的语言,在 JavaScript 和 TypeScript 中不可变数据的处理相对更为困难。
—— 原文引用自React官方文档
继续说缺点。前端 Web 开发推荐使用 React。
React 并未针对 TypeScript 设计。最初,React 是针对函数式语言设计的,本文稍后会详细介绍。二者在编程范式上存在冲突,TypeScript 是面向对象编程优先的,而 React 是函数优先的。
React 中,函数参数 props 是不可变的;而 TypeScript 中,没有内置提供适用的不可变数据结构支持。
在开发中,TypeScript 相比 JavaScript、React 的唯一优点是,无需操心 PropTypes。
TypeScript 是否是 JavaScript 的超集?
这取决于开发人员的认识。至少我认为是的。做为超集的最大优点,是可接入整个 JavaScript 生态系统。
为什么 JavaScript 的超集语言备受关注?这与 Java、C#广为采用是同样的原因,是因为背后有市场营销预算充足的大厂在提供支持。
评判
尽管 TypeScript 常被认为是“更好的 JavaScript”,但我依然评判其劣于 JavaScript。TypeScript 相比 JavaScript 的优点被夸大了,尤其是对于使用 React 做前端 Web 开发。
TypeScript 保留了 JavaScript 的所有不足,实际上也继承了 JavaScript 中数十年积累不良设计决策,的确并非一种成功的交付,
Go
Go 设计上主要考虑了提高多核处理器和大规模代码库的编程效率。Go 的设计者们当时任职于谷歌,因对 C++的共同不喜而得到灵感。
语言家族:C。
👍 并发
并发是 Go 的杀手级特性。Go 从本质上就是为并发而构建。和 Erlang/Elixir 一样,Go 使用邮箱模型(Mailbox)实现并发。不幸的是,goroutines 并未提供 Erlang/Elixir 进程那样的统一容错特性。换句话说,goroutine 中的异常将导致整个程序宕机,而 Elixir 进程中的异常只会导致当前进程终止。
👍 👍 速度
编译速度是谷歌创建 Go 的一个重要考虑。有个笑话,谷歌利用 C++编译代码的时间就创建出了 Go。
Go 是一种高效的语言。Go 程序的启动时间非常快。Go 编译为原生代码,所以运行时速度也非常快。
👍 学习难度
Go 是一种简单的语言,如果得到有经验前辈的指导,新手能在一个月内掌握。
👍 错误处理
Go 并不支持异常,由开发人员显式处理各种可能的错误。和 Rust 类似,Go 也返回两个值,一个是调用的结果,另一个是可能的错误值。如果一切运行正常,返回的错误值是 nil。
👍 不支持面向对象编程
虽然这么说有人会反对,但我个人认为,不支持面向对象特性是很大的优势。
重申 Linux Torvalds 的观点:
C++是一种很糟的(面向对象)语言……将项目局限于 C,意味着整个项目不会因为任何愚蠢的 C++“对象模型”而搞砸。
—— Linux 创建者 Linus Torvalds
Linus Torvalds 公开对 C++和面向对象编程持批评态度。限制编程人员在可选的范围内,是他完全正确的一面。事实上,编程人员的选择越少,代码也会更稳定。
在我看来,Go 可以回避了许多面向对象特性,免于重蹈 C++的覆辙。
👌 生态系统
一些标准库的确很笨重。大部分并不符合 Go 返回带外(out-of-band,OOB)错误的自身哲学。例如,有的库对索引返回-1 值,而非(int, error)。还有一些库依赖全局状态,例如 flag 和 net/http。
Go 的软件库缺少标准化。例如在错误时,有的库返回(int, error),也有软件库返回-1 等值。还有一些库依赖标识等全局状态。
Go 的生态系统规模远比不上 JavaScript。
👎 类型系统
几乎所有的现代编程语言都具有某种形式的泛型,其中包括 C#和 Java,甚至是 C++也提供模板类。泛型支持开发人员重用不同类型的函数实现。如果不支持泛型,那么开发人员就必须对整型、双精度和浮点型单独实现加法函数,这将导致大量的代码冗余。换句话说,Go缺失对泛型的支持导致了大量冗余代码。正如有人指出的,“Go”是“去写一些模板代码”(Go write some boilerplate)的缩写。
👎 空值
不幸的是,即使更安全的空值替代方案已存在数十年,Go 依然在语言中添加了空值。
👎 不可变性
未内置对不可变数据结构的支持。
评判
Go 并非一种好的语言,但也谈不上不好,只是不够优秀。使用一种并不优秀的语言时需谨慎,因为这可能会导致我们在随后的二十年中陷入困境。
Will Yager 的博客文章“Why Go Is No Good”
如果你并非供职于谷歌,也没有面对类似谷歌的用例,那么 Go 可能并非好的选择。Go 是一种最适合系统编程的简单语言,但并非 API 开发的好选择。原因是因为我们有更多更好的替代语言,本文稍后介绍。
我认为总体而言,尽管 G 的类型系统略弱,但比 Rust 还是略好。Go 是一种简单的语言,非常快,易于学习,并且具有出色的并发功能。当然,Go 成功地实现了做为“更好的 C++”这一设计目标。
最佳系统编程语言奖
最佳系统语言奖授予 Go。实至名归,Go 是系统编程的理想选择。Go 是一种低层语言,使用 Go 构建的大量成功项目,例如 Kubernetes,Docker 和 Terraform,证明其非常适合系统编程。
JavaScript
作为当前最流行的编程语言,JavaScript 无需过多介绍。
当然,将 JavaScript 排在 Rust、TypeScript 和 Go 之前是正确的。下面给出原因。
语言家族:C
👍 👍 生态系统
生态系统是 JavaScript 的最大优势。我们能想到的所有,,包括 Web 的前端和后端开发,CLI 编程、数据科学,甚至是机器学习,都可使用 JavaScript。JavaScript 可能具有提供任何功能的软件库。
👍 学习难度
JavaScript 和 Python 都是非常容易学习的编程语言。几周就能上手做项目。
👎 类型系统
和 Python 类似,JavaScript 是动态类型的。无需过多解释,但是其类型系统时常看起来很奇怪:
👌 不可变性
在 TypeScript 一节中已经介绍,展开操作符(spread operator)会影响性能,甚至并没有在拷贝对象时执行深拷贝。尽管有 Ramda/Immutable.js 等软件库,但 JavaScript 缺少对不可变数据结构的内建支持。
👎 JavaScript 并非针对响应式设计的
在 JavaScript 中使用 React,必须借助 PropTypes。但这也意味着必须去维护 PropTypes,这会导致灾难性后果。
此外,如果在编程中不加注意的话,可能会导致严重的性能问题。例如:
这个看上去无害的代码会导致严重性能问题。因为在 JavaScript 中, [] != []。上面的代码会导致 HugeList 在每一次更新时重渲染,尽管 options 值并未发生变化。此类问题会不断叠加,直到用户界面最终无法响应。
👎 关键字 this
关键字 this 应该是 JavaScript 中的最大反特性。其行为持续表现不一致,在不同的情况下可能意味完全不同,其行为甚至取决于谁调用了指定的函数。使用 this 关键字通常会导致一些细微而奇怪的错误,难以调试。
👌 并发
JavaScript 使用事件循环支持单线程并发,无需考虑加锁等线程同步机制。尽管 JavaScript 在构建时并未考虑并发性,但与大多数其他语言相比,更易于实现并发代码。
👍 新的 JavaScript 特性
相比 TypeScript,新特性能更快地在 JavaScript 中支持。即便是实验性特性,也可使用 Bable 支持在 JavaScript 中使用。
👎 错误处理 Error handling
抛出并捕获错误是 JavaScript 的首选错误处理机制。
评判
JavaScript 并非一种很好设计的语言。JavaScript 的最初版本仅用十天就拼凑出来,尽管在后期版本中修正了许多缺点。
抛开上述缺点,JavaScript 依然是全栈 Web 开发和很好选择。如果加以适当的代码修炼和分析,JavaScript 是一种很好的语言。
原文链接: These Modern Programming Languages Will Make You Suffer
评论 8 条评论