蚊章汇 - Eric Raymond
type: channel
link: https://linuxcn.bwsl.wang/author/127/
description: Eric Raymond
items: 计算机语言的巨变
items: 与 C 语言长别离
items: ESR：程序语言设计的要诣和真谛
items: ifdex： 一个由 ESR 出品的给代码考古学家的工具
items: ESR：黑客年暮

计算机语言的巨变
type: item
date: 2018-04-02T16:16:00+08:00
description: 我的上一篇博文《与 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++ 现有的问题。对于第二条道路，第一次严谨的尝试就是 …

与 C 语言长别离
type: item
date: 2018-01-25T16:31:00+08:00
description: 这几天来，我在思考那些正在挑战 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 语言转移…

ESR：程序语言设计的要诣和真谛
type: item
date: 2017-12-22T07:12:06+08:00
description: 当你真正掌握了整体化的工程设计思维时，你就会发现高屋建瓴的工程设计已经远远超越了技术优化的层面。我们的每一件创造都催生于人类活动的大背景下，被这种人类活动赋予了广泛的经济学意义、社会学意义，甚至于具有了奥地利经济学家所称的“ 人类行为学意义 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）以时间的远近为轴，在远景设计和近…

ifdex： 一个由 ESR 出品的给代码考古学家的工具
type: item
date: 2015-10-04T01:47:17+08:00
description: 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 。希望你用的上。

ESR：黑客年暮
type: item
date: 2015-01-12T23:54:00+08:00
description: 近来我一直在与某资深开源开发团队中的多个成员缠斗，尽管密切关注我的人们会在读完本文后猜到是哪个组织，但我不会在这里说出这个组织的名字。怎么让某些人进入 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 的摘要信…