Loading...

简洁就是力量

Original

2002年5月

"代数符号压缩的意义量是促进我们借助它们进行推理的另一个原因。"

  • Charles Babbage,引自 Iverson 的图灵奖演讲

在关于《书呆子的复仇》引发的问题的 LL1 邮件列表讨论中, Paul Prescod 写下了一些让我印象深刻的话。

Python 的目标是规则性和可读性,而不是简洁。

从表面上看, 这似乎是一个相当严厉的批评。 就我所知, 简洁性就是力量。 如果是这样的话, 那么我们可以认为:

Python 的目标是规则性和可读性, 而不是力量。

这似乎并不是一种权衡(如果确实是一种权衡)。 这几乎等同于说, Python 的目标不是成为一种有效的编程语言。

简洁性就是力量吗? 这似乎是一个非常重要的问题, 也许是任何对语言设计感兴趣的人最重要的问题, 我们应该正面地讨论它。 我还不能确定答案是一个简单的肯定, 但这似乎是一个不错的起点假设。

假设

我的假设是, 简洁性就是力量, 或者说它们足够接近, 除了病态的例子, 我们可以将它们视为等同。

在我看来, 简洁性就是编程语言的本质。 计算机本可以直接用机器语言告诉它们该做什么。 我认为, 我们开发高级语言的主要原因是为了获得影响力, 这样我们就可以用高级语言的 10 行代码来表达本来需要 1000 行机器语言才能表达的内容。 换句话说, 高级语言的主要目的就是让源代码变小。

如果更小的源代码是高级语言的目的, 而某物的力量是指它实现目的的效果, 那么评判编程语言力量的标准就是它能让你的程序变小多少。

相反, 一种不能让程序变小的语言就是在糟蹋它本来应该做的事情, 就像一把不能切割的刀或者难以辨读的打印一样。

指标

但是"小"应该如何定义呢? 最常见的代码大小度量标准是代码行数。 但我认为这种标准只是最常用的, 因为它最容易测量。 我不认为任何人真的相信它是衡量程序长度的真正标准。 不同语言在一行代码中放多少内容的惯例是不同的; 在 C 语言中, 很多行只有一两个分隔符。

另一个简单的测试是程序的字符数, 但这也不太好; 有些语言(比如 Perl)只是使用更短的标识符。

我认为衡量程序大小的更好标准是元素的数量, 其中一个元素就是源代码表示为树状结构时的一个独立节点。 变量或函数的名称是一个元素; 一个整数或浮点数是一个元素; 一段文字是一个元素; 模式或格式指令的一个元素也是一个元素; 一个新的代码块也是一个元素。 确实存在一些边界情况(比如 -5 是两个元素还是一个?), 但我认为大多数情况下它们对于所有语言来说都是一样的, 所以不会对比较造成太大影响。

这种指标需要进一步完善, 在处理特定语言时可能需要解释, 但我认为它试图测量正确的东西, 即程序由多少个部分组成。 我认为在这个过程中绘制的树状结构就是你在头脑中构建程序时所需要做的, 所以它的大小与编写或阅读程序所需的工作量成正比。

设计

这种类型的指标可以让我们比较不同的语言, 但这至少对我来说并不是它的主要价值。 简洁性测试的主要价值在于作为设计语言的指导。 语言之间最有价值的比较是在同一种语言的两个潜在变体之间。 我能在语言中做些什么来使程序更短?

如果程序的概念负荷与其复杂性成正比, 而一个特定的程序员可以承受一个固定的概念负荷, 那么这等同于问, 我能做些什么来让程序员做更多事情? 这似乎与问如何设计一种好的语言是一回事。

(顺便说一下, 没有什么比设计语言更能明确地表明"所有语言都是等价的"这种老生常谈是错误的。 当你设计一种新的语言时, 你不断在比较两种语言 - 如果我做 x 会怎样, 如果我不这样做会怎样 - 来决定哪种更好。 如果这真的是一个无意义的问题, 你不如抛硬币。)

追求简洁性似乎是找到新想法的好办法。 如果你能做一些事情来大大缩短许多不同的程序, 这可能不是巧合: 你可能已经发现了一个有用的新抽象。 你甚至可以写一个程序来帮助你, 通过搜索源代码中的重复模式来实现这一点。 在其他语言中, 那些以简洁著称的语言可能是寻找新想法的良好来源: Forth, Joy, Icon 等。

比较

据我所知, 第一个写这些问题的人是 Fred Brooks, 他在《人月神话》中写道, 程序员每天生成的代码量似乎与所使用的语言无关。 当我二十出头的时候第一次读到这个观点时, 让我非常惊讶,并且似乎有很大的影响。 这意味着(a)获得更快的软件编写速度的唯一方法就是使用一种更简洁的语言, 和(b)有人花时间这样做就可能远远超过那些不这样做的竞争对手。

如果 Brooks 的假说是正确的, 它似乎就是黑客文化的核心。 多年来, 我一直密切关注任何可以获得的证据, 从正式的研究到关于个人项目的轶事。 我没有看到任何与他相矛盾的东西。

我尚未见到对我来说堪称确凿的证据,我也不期待会看到。像Lutz Prechelt比较编程语言的那些研究,虽然产生了我所预期的结果,但它们使用的问题往往太短,不足以成为有意义的测试。一种语言的真正测试在于,当用它来编写一个需要整整一个月才能完成的程序时会发生什么。而如果你像我一样相信,一种语言的主要目的是作为思维工具(而不仅仅是用来告诉计算机你已经想好的东西),那么,一种语言的真正测试就在于用它可以写出什么新事物。所以任何一种语言比较,如果需要满足预定义的规格,那么它就在测试稍有偏差的东西。

一种语言的真正测试在于,它能多好地帮助你发现和解决新问题,而不是它能多好地用来解决别人已经阐述好的问题。这两种标准是大不相同的。在艺术领域,像刺绣和马赛克这样的媒材在你事先知道要制作什么的情况下效果不错,但如果你在制作过程中才想要发现最终的图像,它们就绝对糟糕透顶。当你需要在制作过程中发现图像——就像你必须这么做的那些复杂到需要描绘一个人的图像一样——你就需要使用一种更加流畅的媒材,比如铅笔或水墨或油画。事实上,制作挂毯和马赛克的实际做法就是先绘制一幅绘画,然后再将其复制。(这就是"卡通"一词最初的用法)。

这意味着,我们可能永远不会有关于编程语言相对能力的准确比较。我们可以有精确的比较,但不会有准确的比较。特别是,专门为了比较语言而进行的研究,因为它们很可能会使用小问题,而且必须使用预定义的问题,所以它们往往会低估更强大语言的能力。

尽管来自现场的报告必然会不如"科学"研究那么精确,但它们很可能更有意义。比如,爱立信的Ulf Wiger做了一项研究,得出结论说Erlang比C++简洁4-10倍,并且相应地更快地开发软件:

爱立信内部的开发项目比较表明,包括软件开发的所有阶段,生产率(每小时完成的代码行数)都差不多,几乎不受所用语言(Erlang、PLEX、C、C++或Java)的影响。不同语言的区别在于源代码量。

这项研究还明确地处理了在《人月神话》一书中只是隐含的一点(因为作者测量的是调试后的代码行数):用更强大的语言编写的程序往往拥有更少的bug。对于像网络交换机这样的应用来说,这可能比程序员的工作效率更重要。

品味测试

最终,我认为你必须听从内心的感受。编程时使用某种语言的感受如何?我认为找到(或设计)最佳语言的方法是,敏锐地感受一种语言有多好地让你思考,然后选择/设计感觉最舒适的那种语言。如果某种语言特性显得笨拙或过于限制,别担心,你一定会感知到它。

这种高度敏感性是有代价的。你会发现你无法忍受使用笨拙的语言进行编程。我发现在没有宏的语言中编程是难以忍受的限制,就像一个习惯了动态类型的人再次编程时,不得不在每个变量声明类型,也不能创建不同类型对象的列表,这种限制都是难以忍受的。

我并不孤单。我认识许多Lisp黑客都经历过这种情况。事实上,衡量编程语言相对能力的最准确方法,可能就是掌握该语言的人中,有多大比例会为了能使用该语言而接受任何工作,不管应用领域如何。

限制性

我认为,大多数黑客都知道什么是语言的限制性。当你感受到这种感觉时,发生了什么?我认为这就像当你想走的街道被挡住了,你不得不绕道走一个很长的路程才能到达目的地。有某些你想表达的东西,但语言不允许你这么做。

我认为,这里真正发生的是,一种限制性语言缺乏简洁性。问题不仅仅是你无法按原计划表达。而是语言强迫你绕道而行的"路程"变得更长了。试试这个思维实验吧。假设有某个程序你想要编写,语言不允许你按原计划表达它,而是强迫你用另一种方式编写程序,但这种方式更短。对我来说,至少,这种感觉并不会很限制。这就像你想走的街道被挡住了,但交警在路口指引你走捷径,而不是绕路。太棒了!

我认为,大部分(90%?)限制性的感觉来自于,语言强迫你编写的程序比你头脑中的程序更长。限制性主要就是缺乏简洁性。所以当一种语言感觉限制性时,这(主要)意味着它缺乏足够的简洁性,而当一种语言缺乏简洁性时,它就会感觉限制性。

可读性

我开头引用的那段话还提到了两个其他特质,即规则性和可读性。我不太确定什么是规则性,也不太确定,如果一段代码既有规则又可读,相比于仅仅可读,它是否具有任何优势。但我认为我知道什么是可读性,而且它也与简洁性有关。

我们在这里必须小心区分,单行代码的可读性和整个程序的可读性。后者才是真正重要的。我同意,一行Basic代码可能比一行Lisp代码更可读。但用Basic编写的程序将有更多行数,而用Lisp编写的同一程序(尤其是一旦进入了Greenspunland)将行数大大减少。阅读整个Basic程序的总努力必然会更大。

总努力 = 每行的努力 x 行数

我不确定可读性是否直接与简洁性成正比,正如我确定能力与之成正比一样,但简洁性肯定是可读性的一个因素(在数学意义上;见上式)。所以,说一种语言的目标是可读性而不是简洁性,可能是没有意义的;这就像说目标是可读性,而不是可读性。

首次接触该语言的用户而言,每行代码可读性意味着源代码看起来会显得不那么可怕。所以,每行代码的可读性可能是一个不错的营销决策,即使它是一个糟糕的设计决策。这与让人们分期付款的成功技术如出一辙:不是让他们吓一跳的高昂前期价格,而是告诉他们每月的低廉支付。分期付款对买家来说是一种损失,程序员读那些可读性很好的行也可能如此。

买家最后要支付很多低廉的款项;程序员最后要读很多那些单独可读的行。

这种权衡并非编程语言独有。如果你习惯于阅读小说和报纸文章,第一次读数学论文可能会让你失望。阅读一页纸可能需要半个小时。然而,我很确定问题不在于符号,即使它可能看起来问题就在此。数学论文之所以难读,是因为其中的思想很难。如果用散文表达同样的思想(数学家在发展简洁符号之前不得不这样做),它们也不会更容易读懂,因为论文会增长到与一本书一般。

到什么程度?

有一些人拒绝了"简洁性=力量"的观点。我认为,不是简单地争论它们是否相同或不相同,而是问:简洁性与力量到什么程度等同?因为显然,简洁性是高级语言存在的一个很大原因。如果它不是它们全部的功能,那么它们还有什么其他功能,这些其他功能相对来说有多重要?

我提出这个问题不仅是为了使辩论更加文明。我真的想知道答案。什么时候,如果有的话,一种语言会过于简洁,以至于有害?

我最初的假设是,除了一些病态的例子外,我认为简洁性可以被视为等同于力量。我的意思是,在任何人设计的语言中,它们都是等同的,但如果有人想设计一种语言来反驳这个假设,他们可能做得到。事实上,我也不太确定。

语言,而非程序

我们应该清楚,我们讨论的是语言的简洁性,而不是个别程序的简洁性。 个别程序确实可能写得太密集。

我在《论Lisp》中写过这个问题。一个复杂的宏可能必须节省它自身长度的好几倍才有道理。如果编写某个棘手的宏可以使你每次使用它时节省10行代码,而这个宏本身是10行代码,那么如果你使用它超过一次,你就会净获得行数的节省。但这仍然可能是一个糟糕的决定,因为宏定义比普通代码更难阅读。你可能必须使用这个宏10次或20次,才能获得整体可读性的净改善。

我相信每种语言都有这样的权衡(尽管我怀疑,随着语言变得越来越强大,这种权衡的代价也会越来越高)。每个程序员都一定见过某些聪明的人用可疑的编程技巧将某些代码勉强缩短的情况。

所以,关于这一点我没有争议——至少,我没有。个别程序确实可能过于简洁,损害了自身的利益。问题是,一种语言能不能也如此?一种语言能不能迫使程序员编写过于简洁(元素数量)而降低整体可读性的代码?

难以想象一种语言会太简洁的一个原因是,如果有某种过于紧凑的表述方式,通常也会有一种更长的方式。例如,如果你觉得大量使用宏或高阶函数的Lisp程序太密集,你可以(如果你愿意)编写等同于Pascal的代码。如果你不想用Arc中的这种方式表示阶乘:

(rec zero 1 * 1-)

你也可以写出递归定义:

(rfn fact (x) (if (zero x) 1 (* x (fact (1- x)))))

尽管我暂时想不出任何具体例子,但我对是否存在一种语言过于简洁这个问题很感兴趣。是否有某些语言会迫使你以一种过于简约和难以理解的方式编写代码?如果有人有例子,我会非常感兴趣看看。

(提醒:我要找的是根据上述"元素"度量标准非常密集的程序,而不仅仅是因为可以省略分隔符并且所有东西都有一个一个字符名称而变短的程序。)