Eric Raymond 发布的文章

我的上一篇博文《与 C 语言长别离》引来了我的老朋友,一位 C++ 专家的评论。在评论里,他推荐把 C++ 作为 C 的替代品。这是不可能发生的,如果 C++ 代替 C 是趋势的话,那么 Go 和 Rust 也就不会出现了。

但是我不能只给我的读者一个光秃秃的看法(LCTT 译注:此处是双关语)。所以,在这篇文章中,我来讲述一下为什么我不再碰 C++ 的故事。这是关于计算机语言设计经济学专题文章的起始点。这篇文章会讨论为什么一些真心不好的决策会被做出来,然后进入语言的基础设计之中,以及我们该如何修正这些问题。

在这篇文章中,我会一点一点的指出人们(当然也包括我)自从 20 世纪 80 年代以来就存在的关于未来的编程语言的预见失误。直到最近,我们才找到了证明我们错了的证据。

我记得我第一次学习 C++ 是因为我需要使用 GNU eqn 输出 MathXML,而 eqn 是使用 C++ 写的。那个项目不错。在那之后,21 世纪初,我在 韦诺之战 Battle For Wesnoth 那边当了多年的资深开发人生,并且与 C++ 相处甚欢。

在那之后啊,有一天我们发现一个不小心被我们授予提交权限的人已经把游戏的 AI 核心搞崩掉了。显然,在团队中只有我是不那么害怕查看代码的。最终,我把一切都恢复正常了 —— 我折腾了整整两周。再那之后,我就发誓我再也不靠近 C++ 了。

在那次经历过后,我发现这个语言的问题就是它在尝试使得本来就复杂的东西更加复杂,来粗陋补上因为基础概念的缺失造成的漏洞。对于裸指针这样东西,它说“别这样做”,这没有问题。对于小规模的个人项目(比如我的魔改版 eqn),遵守这些规定没有问题。

但是对于大型项目,或者开发者水平参差不齐的多人项目(这是我经常要处理的情况)就不能这样。随着时间的推移以及代码行数的增加,有的人就会捅篓子。当别人指出有 BUG 时,因为诸如 STL 之类的东西给你增加了一层复杂度,你处理这种问题所需要的精力就比处理同等规模的 C 语言的问题就要难上很多。我在韦诺之战时,我就知道了,处理这种问题真的相当棘手。

我给 Stell Heller(我的老朋友,C++ 的支持者)写代码时不会发生的问题在我与非 Heller 们合作时就被放大了,我和他们合作的结局可能就是我得给他们擦屁股。所以我就不用 C++ ,我觉得不值得为了其花时间。 C 是有缺陷的,但是 C 有 C++ 没有的优点 —— 如果你能在脑内模拟出硬件,那么你就能很简单的看出程序是怎么运行的。如果 C++ 真的能解决 C 的问题(也就是说,C++ 是类型安全以及内存安全的),那么失去其透明性也是值得的。但是,C++ 并没有这样。

我们判断 C++ 做的还不够的方法之一是想象一个 C++ 已经搞得不错的世界。在那个世界里,老旧的 C 语言项目会被迁移到 C++ 上来。主流的操作系统内核会是 C++ 写就,而现存的内核实现,比如 Linux 会渐渐升级成那样。在现实世界,这些都没有发生。C++ 不仅没有打消语言设计者设想像 D、Go 以及 Rust 那样的新语言的想法,它甚至都没有取代它的前辈。不改变 C++ 的核心思想,它就没有未来,也因此,C++ 的 抽象泄露 leaky abstraction 也不会消失。

既然我刚刚提到了 D 语言,那我就说说为什么我不把 D 视为一个够格的 C 语言竞争者的原因吧。尽管它比 Rust 早出现了八年(和 Rust 相比是九年)Walter Bright 早在那时就有了构建那样一个语言的想法。但是在 2001 年,以 Python 和 Perl 为首的语言的出现已经确定了,专有语言能和开源语言抗衡的时代已经过去。官方 D 语言库/运行时和 Tangle 的无谓纷争也打击了其发展。它从未修正这些错误。

然后就是 Go 语言(我本来想说“以及 Rust”。但是如前文所述,我认为 Rust 还需要几年时间才能有竞争力)。它的确是类型安全以及内存安全的(好吧,是在大多数时候是这样,但是如果你要使用接口的话就不是如此了,但是自找麻烦可不是正常人的做法)。我的一位好友,Mark Atwood,曾指出过 Go 语言是脾气暴躁的老头子因为愤怒而创造出的语言,主要是 C 语言的作者之一(Ken Thompson) 因为 C++ 的混乱臃肿造成的愤怒,我深以为然。

我能理解 Ken 恼火的原因。这几十年来我就一直认为 C++ 搞错了需要解决的问题。C 语言的后继者有两条路可走。其一就是 C++ 那样,接受 C 的抽象泄漏、裸指针等等,以保证兼容性。然后以此为基础,构建一个最先进的语言。还有一条道路,就是从根源上解决问题 —— 修正 C语言的抽象泄露。这一来就会破环其兼容性,但是也会杜绝 C/C++ 现有的问题。

对于第二条道路,第一次严谨的尝试就是 1995 年出现的 Java。Java 搞得不错,但是在语言解释器上构建这门语言使其不适合系统编程。这就在系统编程那留下一个巨大的洞,在 Go 以及 Rust 出现之前的 15 年里,都没有语言来填补这个空白。这也就是我的 GPSD 和 NTPsec 等软件在 2017 年仍然主要用 C 写成的原因,尽管 C 的问题也很多。

在许多方面这都是很糟糕的情况。尽管由于缺少足够多样化的选择,我们很难认识到 C/C++ 做的不够好的地方。我们都认为在软件里面出现缺陷以及基于安全方面考虑的妥协是理所当然的,而不是想想这其中多少是真的由于语言的设计问题导致的,就像缓存区溢出漏洞一样。

所以,为什么我们花了这么长时间才开始解决这个问题?从 C 1972 年面世到 Go 2009 年出现,这其中隔了 37 年;Rust 也是在其仅仅一年之前出现。我想根本原因还是经济。

从最早的计算机语言开始,人们就已经知道,每种语言的设计都体现了程序员时间与机器资源的相对价值的权衡。在机器这端,就是汇编语言,以及之后的 C 语言,这些语言以牺牲开发人员的时间为代价来提高性能。 另一方面,像 Lisp 和(之后的)Python 这样的语言则试图自动处理尽可能多的细节,但这是以牺牲机器性能为代价的。

广义地说,这两端的语言的最重要的区别就是有没有自动内存管理。这与经验一致,内存管理缺陷是以机器为中心的语言中最常见的一类缺陷,程序员需要手动管理资源。

当相对价值断言与软件开发在某个特定领域的实际成本动因相匹配时,这个语言就是在经济上可行的。语言设计者通过设计一个适合处理现在或者不远的将来出现的情况的语言,而不是使用现有的语言来解决他们遇到的问题。

随着时间的推移,时兴的编程语言已经渐渐从需要手动管理内存的语言变为带有自动内存管理以及垃圾回收(GC)机制的语言。这种变化对应了摩尔定律导致的计算机硬件成本的降低,使得程序员的时间与之前相比更加的宝贵。但是,除了程序员的时间以及机器效率的变化之外,至少还有两个维度与这种变化相关。

其一就是距离底层硬件的距离。底层软件(内核与服务代码)的低效率会被成倍地扩大。因此我们可以发现,以机器为中心的语言向底层推进,而以程序员为中心的语言向着高级发展。因为大多数情况下面向用户的语言仅仅需要以人类的反应速度(0.1 秒)做出回应即可。

另一个维度就是项目的规模。由于程序员抽象发生的问题的漏洞以及自身的疏忽,任何语言都会有可预期的每千行代码的出错率。这个比率在以机器为中心的语言上很高,而在程序员为中心的带有 GC 的语言里就大大降低。随着项目规模的增大,带有 GC 的语言作为一个防止出错率不堪入目的策略就显得愈发重要起来。

当我们使用这三种维度来看当今的编程语言的形势 —— C 语言在底层,蓬勃发展的带有 GC 的语言在上层,我们会发现这基本上很合理。但是还有一些看似不合理的是 —— C 语言的应用不合理地广泛。

我为什么这么说?想想那些经典的 Unix 命令行工具吧。那些小程序通常都可以使用带有完整的 POSIX 支持的脚本语言快速实现出来。重新编码那些程序将使得它们调试、维护和拓展起来都会更加简单。

但是为什么还是使用 C (或者某些像 eqn 的项目,使用 C++)?因为有转换成本。就算是把相当小、相当简单的程序使用新的语言重写并且确认你已经忠实地保留了所有非错误行为都是相当困难的。笼统地说,在任何一个领域的应用编程或者系统编程在一种语言的权衡过时之后,仍然坚持使用它。

这就是我和其他预测者犯的大错。 我们认为,降低机器资源成本(增加程序员时间的相对成本)本身就足以取代 C 语言(以及没有 GC 的语言)。 在这个过程中,我们有一部分或者甚至一大部分都是错误的 —— 自 20 世纪 90 年代初以来,脚本语言、Java 以及像 Node.js 这样的东西的兴起显然都是这样兴起的。

但是,竞争系统编程语言的新浪潮并非如此。 Rust 和 Go 都明确地回应了增加项目规模 这一需求。 脚本语言是先是作为编写小程序的有效途径,并逐渐扩大规模,而 Rust 和 Go 从一开始就定位为减少大型项目中的缺陷率。 比如 Google 的搜索服务和 Facebook 的实时聊天复用。

我认为这就是对 “为什么不再早点儿” 这个问题的回答。Rust 和 Go 实际上并不算晚,它们相对迅速地回应了一个直到最近才被发现低估的成本动因问题。

好,说了这么多理论上的问题。按照这些理论我们能预言什么?它告诉我们在 C 之后会出现什么?

推动 GC 语言发展的趋势还没有扭转,也不要期待其扭转。这是大势所趋。因此:最终我们拥有具有足够低延迟的 GC 技术,可用于内核和底层固件,这些技术将以语言实现方式被提供。 这些才是真正结束 C 长期统治的语言应有的特性。

我们能从 Go 语言开发团队的工作文件中发现端倪,他们正朝着这个方向前进 —— 可参见关于并发 GC 的学术研究 —— 从未停止研究。 如果 Go 语言自己没有选择这么做,其他的语言设计师也会这样。 但我认为他们会这么做 —— 谷歌推动他们的项目的能力是显而易见的(我们从 “Android 的发展”就能看出来)。

在我们拥有那么理想的 GC 之前,我把能替换 C 语言的赌注押在 Go 语言上。因为其 GC 的开销是可以接受的 —— 也就是说不只是应用,甚至是大部分内核外的服务都可以使用。原因很简单: C 的出错率无药可医,转化成本还很高。

上周我尝试将 C 语言项目转化到 Go 语言上,我发现了两件事。其一就是这活很简单, C 的语言和 Go 对应的很好。还有就是写出的代码相当简单。由于 GC 的存在以及把集合视为首要的数据结构,人们会预期代码减少,但是我意识到我写的代码比我最初期望的减少的更多,比例约为 2:1 —— 和 C 转 Python 类似。

抱歉呐,Rust 粉们。你们在内核以及底层固件上有着美好的未来,但是你们在别的 C 领域被 Go 压的很惨。没有 GC ,再加上难以从 C 语言转化过来,还有就是 API 的标准部分还是不够完善。(我的 select(2) 又哪去了啊?)。

对你们来说,唯一的安慰就是,C++ 粉比你们更糟糕 —— 如果这算是安慰的话。至少 Rust 还可以在 Go 顾及不到的 C 领域内大展宏图。C++ 可不能。

本站按:本文由著名开源领袖 ESR 撰写,了解 ESR 事迹的同学知道他拒绝去大公司荣养,而仍然主要负责一些互联网基础性项目的开发维护(如 NTPsec),所以,他在创造者赞助网站 Patreon 上有一份生活赞助计划,大家可以考虑献出一些微薄之力支持他,每个月 $20 也不过你一餐饭而已。

via: http://esr.ibiblio.org/?p=7724

作者:Eric Raymond 译者:name1e5s 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

这几天来,我在思考那些正在挑战 C 语言的系统编程语言领袖地位的新潮语言,尤其是 Go 和 Rust。思考的过程中,我意识到了一个让我震惊的事实 —— 我有着 35 年的 C 语言经验。每周我都要写很多 C 代码,但是我已经记不清楚上一次我 创建一个新的 C 语言项目 是在什么时候了。

如果你完全不认为这种情况令人震惊,那你很可能不是一个系统程序员。我知道有很多程序员使用更高级的语言工作。但是我把大部分时间都花在了深入打磨像 NTPsec、 GPSD 以及 giflib 这些东西上。熟练使用 C 语言在这几十年里一直就是我的专长。但是,现在我不仅是不再使用 C 语言写新的项目,甚至我都记不清我是什么时候开始这样做的了,而且……回头想想,我觉得这都不是本世纪发生的事情。

这个对于我来说是件大事,因为如果你问我,我的五个最核心软件开发技能是什么,“C 语言专家” 一定是你最有可能听到的之一。这也激起了我的思考。C 语言的未来会怎样 ?C 语言是否正像当年的 COBOL 语言一样,在辉煌之后,走向落幕?

我恰好是在 C 语言迅猛发展,并把汇编语言以及其它许多编译型语言挤出主流存在的前几年开始编程的。那场过渡大约是在 1982 到 1985 年之间。在那之前,有很多编译型语言争相吸引程序员的注意力,那些语言中还没有明确的领导者;但是在那之后,小众的语言就直接毫无声息的退出了舞台。主流的语言(FORTRAN、Pascal、COBOL)则要么只限于老代码,要么就是固守单一领域,再就是在 C 语言的边缘领域顶着愈来愈大的压力苟延残喘。

而在那以后,这种情形持续了近 30 年。尽管在应用程序开发上出现了新的动向: Java、 Perl、 Python, 以及许许多多不是很成功的竞争者。起初我很少关注这些语言,这很大一部分是因为在它们的运行时的开销对于当时的实际硬件来说太大。因此,这就使得 C 的成功无可撼动;为了使用和对接大量已有的 C 语言代码,你得使用 C 语言写新代码(一部分脚本语言尝试过打破这种壁垒,但是只有 Python 有可能取得成功)。

回想起来,我在 1997 年使用脚本语言写应用时本应该注意到这些语言的更重要的意义的。当时我写的是一个名为 SunSITE 的帮助图书管理员做源码分发的辅助软件,当时使用的是 Perl 语言。

这个应用完全是用来处理文本输入的,而且只需要能够应对人类的反应速度即可(大概 0.1 秒),因此使用 C 或者别的没有动态内存分配以及字符串类型的语言来写就会显得很傻。但是在当时,我仅仅是把其视为一个试验,而完全没有想到我几乎再也不会在一个新项目的第一个文件里敲下 int main(int argc, char **argv) 这样的 C 语言代码了。

我说“几乎”,主要是因为 1999 年的 SNG。 我想那是我最后一个用 C 从头开始写的项目了。

在那之后我写的所有的 C 代码都是在为那些上世纪已经存在的老项目添砖加瓦,或者是在维护诸如 GPSD 以及 NTPsec 一类的项目。

当年我本不应该使用 C 语言写 SNG 的。因为在那个年代,摩尔定律的快速迭代使得硬件愈加便宜,使得像 Perl 这样的语言的执行效率也不再是问题。仅仅三年以后,我可能就会毫不犹豫地使用 Python 而不是 C 语言来写 SNG。

在 1997 年我学习了 Python, 这对我来说是一道分水岭。这个语言很美妙 —— 就像我早年使用的 Lisp 一样,而且 Python 还有很酷的库!甚至还完全遵循了 POSIX!还有一个蛮好用的对象系统!Python 没有把 C 语言挤出我的工具箱,但是我很快就习惯了在只要能用 Python 时就写 Python ,而只在必须使用 C 语言时写 C。

(在此之后,我开始在我的访谈中指出我所谓的 “Perl 的教训” ,也就是任何一个没能实现和 C 语言语义等价的遵循 POSIX 的语言都注定要失败。在计算机科学的发展史上,很多学术语言的骨骸俯拾皆是,原因是这些语言的设计者没有意识到这个重要的问题。)

显然,对我来说,Python 的主要优势之一就是它很简单,当我写 Python 时,我不再需要担心内存管理问题或者会导致核心转储的程序崩溃 —— 对于 C 程序员来说,处理这些问题烦的要命。而不那么明显的优势恰好在我更改语言时显现,我在 90 年代末写应用程序和非核心系统服务的代码时,为了平衡成本与风险都会倾向于选择具有自动内存管理但是开销更大的语言,以抵消之前提到的 C 语言的缺陷。而在仅仅几年之前(甚至是 1990 年),那些语言的开销还是大到无法承受的;那时硬件产业的发展还在早期阶段,没有给摩尔定律足够的时间来发挥威力。

尽量地在 C 语言和 Python 之间选择 C —— 只要是能的话我就会从 C 语言转移到 Python 。这是一种降低工程复杂程度的有效策略。我将这种策略应用在了 GPSD 中,而针对 NTPsec , 我对这个策略的采用则更加系统化。这就是我们能把 NTP 的代码库大小削减四分之一的原因。

但是今天我不是来讲 Python 的。尽管我觉得它在竞争中脱颖而出,Python 也未必真的是在 2000 年之前彻底结束我在新项目上使用 C 语言的原因,因为在当时任何一个新的学院派的动态语言都可以让我不再选择使用 C 语言。也有可能是在某段时间里在我写了很多 Java 之后,我才慢慢远离了 C 语言。

我写这个回忆录是因为我觉得我并非特例,在世纪之交,同样的发展和转变也改变了不少 C 语言老手的编码习惯。像我一样,他们在当时也并没有意识到这种转变正在发生。

在 2000 年以后,尽管我还在使用 C/C++ 写之前的项目,比如 GPSD ,游戏韦诺之战以及 NTPsec,但是我的所有新项目都是使用 Python 的。

有很多程序是在完全无法在 C 语言下写出来的,尤其是 reposurgeon 以及 doclifter 这样的项目。由于 C 语言受限的数据类型本体论以及其脆弱的底层数据管理问题,尝试用 C 写的话可能会很恐怖,并注定失败。

甚至是对于更小的项目 —— 那些可以在 C 中实现的东西 —— 我也使用 Python 写,因为我不想花不必要的时间以及精力去处理内核转储问题。这种情况一直持续到去年年底,持续到我创建我的第一个 Rust 项目,以及成功写出第一个使用 Go 语言的项目

如前文所述,尽管我是在讨论我的个人经历,但是我想我的经历体现了时代的趋势。我期待新潮流的出现,而不是仅仅跟随潮流。在 98 年的时候,我就是 Python 的早期使用者。来自 TIOBE 的数据则表明,在 Go 语言脱胎于公司的实验项目并刚刚从小众语言中脱颖而出的几个月内,我就开始实现自己的第一个 Go 语言项目了。

总而言之:直到现在第一批有可能挑战 C 语言的传统地位的语言才出现。我判断这个的标准很简单 —— 只要这个语言能让我等 C 语言老手接受不再写 C 的事实,这个语言才 “有可能” 挑战到 C 语言的地位 —— 来看啊,这有个新编译器,能把 C 转换到新语言,现在你可以让他完成你的全部工作了 —— 这样 C 语言的老手就会开心起来。

Python 以及和其类似的语言对此做的并不够好。使用 Python 实现 NTPsec(以此举例)可能是个灾难,最终会由于过高的运行时开销以及由于垃圾回收机制导致的延迟变化而烂尾。如果需求是针对单个用户且只需要以人类能接受的速度运行,使用 Python 当然是很好的,但是对于以 机器的速度 运行的程序来说就不总是如此了 —— 尤其是在很高的多用户负载之下。这不只是我自己的判断 —— 因为拿 Go 语言来说,它的存在主要就是因为当时作为 Python 语言主要支持者的 Google 在使用 Python 实现一些工程的时候也遭遇了同样的效能痛点。

Go 语言就是为了解决 Python 搞不定的那些大多由 C 语言来实现的任务而设计的。尽管没有一个全自动语言转换软件让我很是不爽,但是使用 Go 语言来写系统程序对我来说不算麻烦,我发现我写 Go 写的还挺开心的。我的很多 C 编码技能还可以继续使用,我还收获了垃圾回收机制以及并发编程机制,这何乐而不为?

这里有关于我第一次写 Go 的经验的更多信息)

本来我想把 Rust 也视为 “C 语言要过时了” 的例证,但是在学习并尝试使用了这门语言编程之后,我觉得这种语言现在还没有做好准备。也许 5 年以后,它才会成为 C 语言的对手。

随着 2017 的尾声来临,我们已经发现了一个相对成熟的语言,其和 C 类似,能够胜任 C 语言的大部分工作场景(我在下面会准确描述),在几年以后,这个语言界的新星可能就会取得成功。

这件事意义重大。如果你不长远地回顾历史,你可能看不出来这件事情的伟大性。三十年了 —— 这几乎就是我作为一个程序员的全部生涯,我们都没有等到一个 C 语言的继任者,也无法遥望 C 之后的系统编程会是什么样子的。而现在,我们面前突然有了后 C 时代的两种不同的展望和未来……

……另一种展望则是下面这个语言留给我们的。我的一个朋友正在开发一个他称之为 “Cx” 的语言,这个语言在 C 语言上做了很少的改动,使得其能够支持类型安全;他的项目的目的就是要创建一个能够在最少人力参与的情况下把古典 C 语言修改为新语言的程序。我不会指出这位朋友的名字,免得给他太多压力,让他做出太多不切实际的保证。但是他的实现方法真的很是有意思,我会尽量给他募集资金。

现在,我们看到了可以替代 C 语言实现系统编程的三种不同的可能的道路。而就在两年之前,我们的眼前还是一片漆黑。我重复一遍:这件事情意义重大。

我是在说 C 语言将要灭绝吗?不是这样的,在可预见的未来里,C 语言还会是操作系统的内核编程以及设备固件编程的主流语言,在这些场景下,尽力压榨硬件性能的古老规则还在奏效,尽管它可能不是那么安全。

现在那些将要被 C 的继任者攻破的领域就是我之前提到的我经常涉及的领域 —— 比如 GPSD 以及 NTPsec、系统服务以及那些因为历史原因而使用 C 语言写的进程。还有就是以 DNS 服务器以及邮件传输代理 —— 那些需要以机器速度而不是人类的速度运行的系统程序。

现在我们可以对后 C 时代的未来窥见一斑,即上述这类领域的代码都可以使用那些具有强大内存安全特性的 C 语言的替代者实现。Go 、Rust 或者 Cx ,无论是哪个,都可能使 C 的存在被弱化。比如,如果我现在再来重新实现一遍 NTP ,我可能就会毫不犹豫的使用 Go 语言去完成。


via: http://esr.ibiblio.org/?p=7711

作者:Eric Raymond 译者:name1e5s 校对:yunfengHe, wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

当你真正掌握了整体化的工程设计思维时,你就会发现高屋建瓴的工程设计已经远远超越了技术优化的层面。我们的每一件创造都催生于人类活动的大背景下,被这种人类活动赋予了广泛的经济学意义、社会学意义,甚至于具有了奥地利经济学家所称的“ 人类行为学意义 praxeology ”。而这种人类行为学意义则是明确的人类行为所能达到的最高层次。

对我来说这并不只是一种抽象的理论。当我在撰写关于开源项目开发的文章时,文章的内容正是关于人类行为学的 —— 这些文章并不涉及哪个具体的软件技术或者话题,而是在讨论科技所服务的人类行为。从人类行为学角度对科技进行更深入的理解,可以帮助我们重塑科技,并且提升我们的生产力和成就感。这种提升并不总是因为我们有了更新的工具,而更多的是因为我们改变了使用现有工具的思路,提升了我们对这些工具的驾驭能力。

在这个思路之下,我的随笔文章的第三篇中谈到了 C 语言的衰退和正在到来的巨大改变,而我们也确实能够感受到系统级别编程的新时代的到来。在这里,我会把我的统观见解总结成更具体的、更实用的对计算机语言设计的分析。例如总结出为什么一些语言会成功,另一些语言会失败。

在我最近的一篇文章中,我写道:所有计算机语言的设计都是对机器资源和程序员人力成本的相对权衡的结果;是对一种相对价值主张的体现。而这些设计思路都是在硬件算力成本不断下降,程序员人力成本相对稳定且可能不减反增的背景下产生的。我还强调了语言设计在实现了一些原有的权衡方案之后,其未来的转化和演变成本在这种语言的成败中所要扮演的一些额外角色。在文中我也阐述了编程语言设计者是如何为当前和可见的未来寻找新的最优设计方案的。

现在我要集中讲解一下我在上面段落里最后提到的那个概念,即语言设计工程师们其实可以在多个方面来改进和提高现阶段编程语言的设计水准。比如输入系统的优化,GC (垃圾回收机制) 和手动内存分配的权衡,命令导向、函数导向和面向对象导向的混合和权衡。但是站在人类行为学的角度去考量,我认为设计师们一定会做出更简单的设计权衡,即针对近景问题还是针对远景问题来优化一种语言的设计。

所谓的“远”、“近”之分,是指随着硬件成本的逐渐降低,软件复杂程度的上升和由现有语言向其他语言转化的成本的增加,根据这些因素的变化曲线所做出的判断。近景问题是编程人员眼下看到的问题,远景问题则是指可预见的,但未必会很快到来的一系列情况。针对近景问题的解决方案可以被很快部署下去,且能够在短期内非常有效,但随着情况的变化和时间的推移,这种方案可能很快就不适用了。而远景的解决方案可能因为其自身的复杂和超前性而夭折,或是因其代价过高无法被接受和采纳。

在计算机刚刚面世的时候, FORTRAN 就是一个近景设计方案, LISP 语言的设计则是针对远景问题;汇编语言多是近景设计方案,很好的阐明了这类设计很适用于非通用语言,同样的例子还包括 ROFF 标记语言。PHP 和 Javascript 则是我们后来看到的采用了近景设计思维的语言。那么后来的远景设计方案有哪些例子呢? Oberon、Ocaml、ML、XML-Docbook 都是它的例子。学术研究中设计出的语言多倾向于远景设计,因为在学术研究领域,原创性以及大胆的假设与想法是很重要的。这和学术研究的动机以及其奖励机制很有关系(值得注意的是,在学术研究中,倾向于远景设计的本源动机并非出于技术方面的原因,而是出自于人类行为,即标新立异才能在学术上有成绩)。学术研究中设计出的编程语言是注定会失败的;因为学术研究的产物常常有高昂的转入成本,无人问津的设计。这类语言也因为在社区中不够流行而不能得到主流的采纳,具有孤立性,且常常被搁置成为半成品。(如上所述的问题正是对 LISP 历史的精辟总结,而且我是以一个对 LISP 语言有深入研究,并深深喜爱它的使用者的身份来评价 LISP 的)。

一些近景设计的失败案例则更加惨不忍睹。对这些案例来说,我们能够期待的最好的结果就是这种语言可以消亡的相对体面一些,被一种新的语言设计取而代之。如果这些近景设计导向的语言没有死亡而是一直被沿用下去(通常是因为转化成本过高),那么我们则会看到不断有新的特性和功能在这些语言原来的基础之上堆积起来,以保持它们的可用性和有效性。直到这种堆积把这些语言变得越来越复杂,变的危若累卵且不可理喻。是的,我说的就是 C++ 。当然, 还有 Javascript。Perl 也不例外,尽管它的设计者 Larry Walls 有不错的设计品味,避免了很多问题,让这种语言得以存活了很多年。但也正是因为 Larry Walls 的好品味,让他在最终对 Perl 的固有问题忍无可忍之后发布了全新的 Perl 6。

从这种角度去思考程序语言,我们则可以把语言设计中需要侧重的目标重新归纳为两部分: (1)以时间的远近为轴,在远景设计和近景设计之间选取一个符合预期的最佳平衡点;(2)降低由一种或多种语言转化为这种新语言的转入成本,这样就可以更好地吸纳其它语言的用户群。接下来我会讲讲 C 语言是怎样占领全世界的。

在整个计算机发展史中,没有谁能比 C 语言在选择远景和近景设计的平衡点的时候做的更完美。事实胜于雄辩,作为一种实用的主流语言,C 语言有着很长的寿命,它目睹了无数个竞争者的兴衰,但它的地位仍旧不可取代。从淘汰它的第一个竞争者到现在已经过了 35 年,但看起来 C 语言的终结仍旧不会到来。

当然,你可以把 C 语言的持久存在归功于文化惰性,但那是对“文化惰性”这个词的曲解,C 语言一直得以延续的真正原因是因为目前还没有人能提供另一种足够好的语言,可以抵消取代 C 语言所需要的转化成本!

相反的,C 语言低廉的 内向转化成本 inward transition costs (转入成本)并未引起大家应有的重视,C 语言几乎是唯一的一个极其多样和强大的编程工具,以至于从它漫长统治时期的初期开始,它就可以适用于多种语言如 FORTRAN、Pascal、汇编语言和 LISP 的编程习惯。在一九八零年代我就注意到,我常常可以根据一个 C 语言新人的编码风格判断出他之前在使用什么语言,这也从另一方面证明了 C 语言可以轻松的被其它语言的使用者所接受,并吸引他们加入进来。

C++ 语言同样胜在它低廉的转化成本。很快,大部分新兴的语言为了降低自身的转入成本,都纷纷参考了 C 语言的语法。值得注意的是这给未来的语言设计带来了一种影响:即新语言的设计都在尽可能的向 C 的语法靠拢,以便这种新语言可以有很低的内向转化成本(转入成本),使其他语言的使用者可以欣然接受并使用这种新语言。

另一种降低转入成本的方法则是把一种编程语言设计的极其简单并容易入手,让那些即使是没有编程经验的人都可以轻松学会。但做到这一点并非易事。我认为只有一种语言 —— Python —— 成功的做到了这一点,即通过易用的设计来降低内向转化成本。对这种程序语言的设计思路我在这里一带而过,因为我并不认为一种系统级别的语言可以被设计的像 Python 一样傻瓜易用,当然我很希望我的这个论断是错的。

而今我们已经来到 2017 年末尾,你一定猜测我接下来会向那些 Go 语言的鼓吹者一样对 Go 大加赞赏一番,然后激怒那些对 Go 不厌其烦的人群。但其实我的观点恰恰相反,我认为 Go 本身很有可能在许多方面遭遇失败。Go 团队太过固执独断,即使几乎整个用户群体都认为 Go 需要做出某些方面的改变了,Go 团队也无动于衷,这是个大问题。目前,Go 语言的 GC 延迟问题以及用以平衡延迟而牺牲掉的吞吐量,都可能会严重制约这种语言的适用范围。

即便如此,在 Go 的设计中还是蕴含了一个让我颇为认同的远大战略目标。想要理解这个目标,我们需要回想一下如果想要取代 C 语言,要面临的短期问题是什么。正如我之前提到的,这个问题就是,随着软件工程项目和系统的不断扩张,故障率也在持续上升,这其中内存管理方面的故障尤其多,而内存管理故障一直是导致系统崩溃和安全漏洞的主要元凶。

我们现在已经认清,一种语言要想取代 C 语言,它的设计就必须遵循两个十分重要准则:(1)解决内存管理问题;(2)降低由 C 语言向本语言转化时所需的转入成本。从人类行为学的角度来纵观编程语言的历史,我们不难发现,作为 C 语言的准替代者,如果不能有效解决转入成本过高这个问题,那设计者所做的其它部分做得再好都不算数。相反的,如果一种 C 的替代语言把转入成本过高这个问题解决地很好,即使它在其他部分做的不是最好的,人们也不会对这种语言吹毛求疵。

而 Go 正是遵循了上述两点设计思路,虽然这个思路并不是一种完美无瑕的设计理论,也有其局限性。比如,目前 GC 延迟的问题就限制了 Go 的推广。但是 Go 目前选择了照搬 Unix 下 C 语言的传染战略,把其自身设计成一种易于转入,便于传播的语言。这样它的广泛和快速的传播就会使其迅速占领市场,从而打败那些针对远景设计的看起来更好的语言。

没错,我所指的这个远景设计方案就是 Rust。而 Rust 的自身定位也正是一种远景和长期的 C 语言替代方案。我曾经在之前的一些文章中解释过我为什么认为 Rust 还没有做好和 Go 展开竞争的准备。TIBOE 和 PYPL 的语言评价指数榜也很好的证明了我的对于 Rust 的这个观点。在 TIBOE 上 Rust 从来没有进过前 20 名。而在 TIBOE 和 PYPL 两个指数榜上, Rust 都要比 Go 的表现差很多。

五年后的 Rust 会发展的怎样还未可知。但如果 Rust 社区的开发人员对这种语言的设计抱着认真投入的态度,并愿意倾听,那么我建议他们要特别重视转入成本的问题。以我个人经历来说,目前由 C 语言转入 Rust 语言的壁垒很高,使人望而却步。如果 Corrode 之类的 Code-lifting 工具只是把 C 语言映射为不安全的 Rust 语言,那么 Corrode 这类工具也是不能解决这种转入壁垒的。或者如果有更简单的方法能够自动注释代码的所有权或生命周期,那么编译器就能把 C 代码直接映射到 Rust,人们也不再需要 Corrode 这一类工具了。目前我还不知道这个问题要如何解决,但我觉得 Rust 社区最好能够找到一种解决方案来代替 Corrode 和其同类工具。

在最后我想强调一下,Ken Thompson 曾经有过语言设计的辉煌历史。他设计的一些语言虽然看起来只是为了解决近景问题,实际上却具有很高的质量和开放程度,让这些语言同样非常适合远景问题,非常易于被提高和拓展。当然 Unix 也是这样的, 这让我不禁暗自揣测,那些我认为的 Go 语言中乍看上去不利于其远景发展的一些令人担忧烦扰的设计(例如缺乏泛型)也许并没有我想象的那样糟糕。如果确如我所认为的那样,即这些设计会影响 Go 的远景发展,那么恐怕我真的是比 Ken 还要聪明有远见了。但是我并不认为我有那么高明。Go 的前途我们还是只能拭目以待。


via: http://esr.ibiblio.org/?p=7745

作者:Eric Raymond 译者:ValoniakimyunfengHe 校对:yunfengHewxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Eric Raymond 写了一个工具,用来帮助那些无畏的“代码考古学家们”理解“古代”代码的结构。它叫做 ifdex,它的背后有一段故事,拿起你的 Fedora 和赶牛的鞭子,让我讲给你听……

在开发 NTPsec 早期, 我们就决定替换它的构建系统——它是如此的难于理解和修改——严重的拖慢了我们的开发进度。

古老的 autoconf 构建方式就像一个恐怖的爬行动物,而 NTP 更是一个极端的例子。不完善的宏技术定义了太多的配置符号,为了掌握这些接口,即使你查看了 config.h 也无济于事。尤其是当你要做一些大的修改时,这更是一个问题!

我们的一个伙计 Amar Takhar,是 waf 构建系统的专家,当他初步提出迁移的想法时得到了我的积极响应。几个月之后,他就做出了一些 waf 功能,虽然还未完成,但是已经至少可以生成用于实际测试的二进制了。

这里我说“未完成”的意思是,代码里面还有一些 waf 构建绝不会设置的 #define 。很多人绝对不会碰那些 autoconf 构建的东西,而另外一些人则不。那些从来用不到的配置开关迷失在大堆的系统头文件和编译器设置的 #define 条件中。

我这里说的不是几个或几十个,我最终统计出了有超过 670 个不同的 #define 用在 #if/#ifdef/#ifndef/#elif 条件中,而这些条件,如 John D. Bell 指出的那样,有 2430 个之多。我需要一些办法来检查它们并分成不同的类型:有的来自系统头文件、有的是配置开关,以及其它的……

所以我写了一个分析器,它可以对代码树解析每个编译时条件的符号,然后将它们分成单纯的列表或 GCC 类的文件/行错误信息,你可以用 Emacs 的编译模式逐个分析。

为了降低干扰,它掌握着一个条件符号的长列表(大概200个),这些可以忽略掉。比如像 \_\_GNUC\_\_ 符号是 GCC 预定义,而 O\_NONBLOCK 宏用于几种系统调用等等。

这些符号分成几组,你可以使用命令行选项分别忽略它们。所以,如果你希望忽略列表中所有标准的 POSIX 宏而想看到操作系统相关的任何东西,那就可以做到了。

另外一个重要的功能是你可以构造你自己的排除列表及注释。这样当我探索 NTP 编译条件的黑暗森林时,所构造的越来越大的排除列表就代表了我已经了解了的条件符号。最终(我希望)未知符号的报告将缩减为空,那么我就已经了解了所有配置开关的确切意义了。

目前为止,我已经搞定了它们之中的 300 个,还有 373 个。这是我用我的漂亮工具在一周内完成的主要工作。噢,从来没有人说过代码考古是如此的容易。

最后, ifdef 的主页是: http://www.catb.org/esr/ifdex/,代码库在这里:https://gitlab.com/esr/ifdex 。希望你用的上。

近来我一直在与某资深开源开发团队中的多个成员缠斗,尽管密切关注我的人们会在读完本文后猜到是哪个组织,但我不会在这里说出这个组织的名字。

怎么让某些人进入 21 世纪就这么难呢?真是的...

我快 56 岁了,也就是大部分年轻人会以为的我将时不时朝他们发出诸如“滚出我的草坪”之类歇斯底里咆哮的年龄。但事实并非如此 —— 我发现,尤其是在技术背景之下,我变得与我的年龄非常不相称。

在我这个年龄的大部分人确实变成了爱发牢骚、墨守成规的老顽固。并且,尴尬的是,偶尔我会成为那个打断谈话的人,我会指出他们某个在 1995 年(或者在某些特殊情况下,1985 年)时很适合的方法... 几十年后的今天就不再是好方法了。

为什么是我?因为年轻人在我的同龄人中很难有什么说服力。如果有人想让那帮老头改变主意,首先他得是自己同龄人中具有较高思想觉悟的佼佼者。即便如此,在与习惯做斗争的过程中,我也比看起来花费了更多的时间。

年轻人犯下无知的错误是可以被原谅的。他们还年轻。年轻意味着缺乏经验,缺乏经验通常会导致片面的判断。我很难原谅那些经历了足够多本该有经验的人,却被长期的固化思维蒙蔽,无法发觉近在咫尺的东西。

(补充一下:我真的不是保守党拥护者。那些和我争论政治的,无论保守党还是非保守党都没有注意到这点,我觉得这颇有点嘲讽的意味。)

那么,现在我们来讨论下 GNU 更新日志文件(ChangeLog)这件事。在 1985 年的时候,这是一个不错的主意,甚至可以说是必须的。当时的想法是用单独的更新日志条目来记录多个相关文件的变更情况。用这种方式来对那些存在版本缺失或者非常原始的版本进行版本控制确实不错。当时我也在场,所以我知道这些。

不过即使到了 1995 年,甚至 21 世纪早期,许多版本控制系统仍然没有太大改进。也就是说,这些版本控制系统并非对批量文件的变化进行分组再保存到一条记录上,而是对每个变化的文件分别进行记录并保存到不同的地方。CVS,当时被广泛使用的版本控制系统,仅仅是模拟日志变更 —— 并且在这方面表现得很糟糕,导致大多数人不再依赖这个功能。即便如此,更新日志文件的出现依然是必要的。

但随后,版本控制系统 Subversion 于 2003 年发布 beta 版,并于 2004 年发布 1.0 正式版,Subversion 真正实现了更新日志记录功能,得到了人们的广泛认可。它与一年后兴起的分布式版本控制系统(Distributed Version Control System,DVCS)共同引发了主流世界的激烈争论。因为如果你在项目上同时使用了分布式版本控制与更新日志文件记录的功能,它们将会因为争夺相同元数据的控制权而产生不可预料的冲突。

有几种不同的方法可以折衷解决这个问题。一种是继续将更新日志作为代码变更的授权记录。这样一来,你基本上只能得到简陋的、形式上的提交评论数据。

另一种方法是对提交的评论日志进行授权。如果你这样做了,不久后你就会开始思忖为什么自己仍然对所有的日志更新条目进行记录。提交元数据与变化的代码具有更好的相容性,毕竟这才是当初设计它的目的。

(现在,试想有这样一个项目,同样本着把项目做得最好的想法,但两拨人却做出了完全不同的选择。因此你必须同时阅读更新日志和评论日志以了解到底发生了什么。最好在矛盾激化前把问题解决....)

第三种办法是尝试同时使用以上两种方法 —— 在更新日志条目中,以稍微变化后的的格式复制一份评论数据,将其作为评论提交的一部分。这会导致各种你意想不到的问题,最具代表性的就是它不符合“真理的单点性(single point of truth)”原理;只要其中有拷贝文件损坏,或者日志文件条目被修改,这就不再是同步时数据匹配的问题,它将导致在其后参与进来的人试图搞清人们是怎么想的时候变得非常困惑。(LCTT 译注:《程序员修炼之道》(The Pragmatic Programmer):任何一个知识点在系统内都应当有一个唯一、明确、权威的表述。根据Brian Kernighan的建议,把这个原则称为“真理的单点性(Single Point of Truth)”或者SPOT原则。)

或者,正如这个我就不说出具体名字的特定项目所做的,它的高层开发人员在电子邮件中最近声明说,提交可以包含多个更新日志条目,并且提交的元数据与更新日志是无关的。这导致我们直到现在还得不断进行记录。

当时我读到邮件的时候都要吐了。什么样的傻瓜才会意识不到这是自找麻烦 —— 事实上,在 DVCS 中针对可靠的提交日志有很好的浏览工具,围绕更新日志文件的整个定制措施只会成为负担和拖累。

唉,这是比较特殊的笨蛋:变老的并且思维僵化了的黑客。所有的合理化改革他都会极力反对。他所遵循的行事方法在几十年前是有效的,但现在只能适得其反。如果你试图向他解释这些不仅仅和 git 的摘要信息有关,同时还为了正确适应当前的工具集,以便实现更新日志的去条目化... 呵呵,那你就准备好迎接无法忍受、无法想象的疯狂对话吧。

的确,它成功激怒了我。这样那样的胡言乱语使这个项目变成了很难完成的工作。而且,同样的糟糕还体现在他们吸引年轻开发者的过程中,我认为这是真正的问题。相关 Google+ 社区的人员数量已经达到了 4 位数,他们大部分都是孩子,还没有成长起来。显然外界已经接受了这样的信息:这个项目的开发者都是部落中地位根深蒂固的崇高首领,最好的崇拜方式就是远远的景仰着他们。

这件事给我的最大触动就是每当我要和这些部落首领较量时,我都会想:有一天我也会这样吗?或者更糟的是,我看到的只是如同镜子一般对我自己的真实写照,而我自己却浑然不觉?我的意思是,我所得到的印象来自于他的网站,这个特殊的笨蛋要比我年轻。年轻至少 15 岁呢。

我总是认为自己的思路很清晰。当我和那些比我聪明的人打交道时我不会受挫,我只会因为那些思路跟不上我、看不清事实的人而沮丧。但这种自信也许只是邓宁·克鲁格效应(Dunning-Krueger effect)在我身上的消极影响,我并不确定这意味着什么。很少有什么事情会让我感到害怕;而这件事在让我害怕的事情名单上是名列前茅的。

另一件让人不安的事是当我逐渐变老的时候,这样的矛盾发生得越来越频繁。不知怎的,我希望我的黑客同行们能以更加优雅的姿态老去,即使身体老去也应该保持一颗年轻的心灵。有些人确实是这样;但可惜绝大多数人都不是。真令人悲哀。

我不确定我的职业生涯会不会完美收场。假如我最后成功避免了思维僵化(注意我说的是假如),我想我一定知道其中的部分原因,但我不确定这种模式是否可以被复制 —— 为了达成目的也许得在你的头脑中发生一些复杂的化学反应。尽管如此,无论对错,请听听我给年轻黑客以及其他有志青年的建议。

你们——对的,也包括你——一定无法在你中年老年的时候保持不错的心灵,除非你能很好的控制这点。你必须不断地去磨练你的内心、在你还年轻的时候完成自己的种种心愿,你必须把这些行为养成一种习惯直到你老去。

有种说法是中年人锻炼身体的最佳时机是 30 岁以前。我以为同样的方法,坚持我以上所说的习惯能让你在 56 岁,甚至 65 岁的时候仍然保持灵活的头脑。挑战你的极限,使不断地挑战自己成为一种习惯。立刻离开安乐窝,由此当你以后真正需要它的时候你可以建立起自己的安乐窝。

你必须要清楚的了解这点;还有一个可选择的挑战是你选择一个可以实现的目标并且为了这个目标不断努力。这个月我要学习 Go 语言。不是指围棋,我早就玩儿过了(虽然玩儿的不是太好)。并不是因为工作需要,而是因为我觉得是时候来扩展下我自己了。(LCTT 译注:围棋的英文也是“Go”)

保持这个习惯。永远不要放弃。


via: http://esr.ibiblio.org/?p=6485

作者:Eric Raymond 译者:Stevearzh 校对:Mr小眼儿

本文由 LCTT 原创翻译,Linux中国 荣誉推出