简洁即力量
原文:Succinctness is Power
作者:Paul Graham 发表:2002-05
译者:Claude(baoyu-translate)
2002 年 5 月
“用代数符号将大量含义压缩进一小块空间,是另一种使我们习惯于借助这些符号进行的推理变得更容易的情形。” —— Charles Babbage(英国数学家、计算机先驱),引自 Iverson(Kenneth Iverson,APL 之父)的图灵奖演讲
在 LL1 邮件列表(Lightweight Languages 1 会议邮件列表)就《nerd 的反击》引出的若干议题展开的讨论里,Paul Prescod(Python 社区成员)写了一句话,一直留在我脑子里:
Python 的目标是规整性和可读性,不是简洁。
乍一看,这话用在一门编程语言身上像是一种相当不利的指控。据我所能判断,简洁 = 力量。如果是这样,把“简洁“代入,我们得到:
Python 的目标是规整性和可读性,不是力量。
这看起来不像是一笔你愿意做的取舍(如果这是一笔取舍的话)。这离“Python 的目标不是作为一门编程语言去管用“已经不远了。
简洁真的等于力量吗?这在我看来是一个重要的问题——也许是任何对语言设计感兴趣的人面对的最重要问题——一个值得直接面对的问题。我还不敢说答案是简单的“是“,但作为出发点,它看起来是个好假说。
假说
我的假说是:简洁即力量——或者足够接近以致于除了病态例子之外,可以当作同一件事来对待。
在我看来,简洁正是编程语言之所以存在的目的。计算机本来用机器语言被直接告知要做什么也一样不在乎。我们之所以费力去开发高级语言,是为了得到杠杆——这样我们能用 10 行高级语言说出(更重要的是,想出)需要 1000 行机器语言才能表达的东西。换句话说,高级语言主要的意义是让源代码变小。
如果让源代码变小是高级语言的目的,而一件东西的“力量“就是它达成自己目的的程度——那么衡量一门编程语言力量的指标,就是它能让你的程序变得多小。
反之,一门不让你的程序变小的语言,是在编程语言本该做的事上做得很糟——就像一把不利的刀,或者一段印不清的字。
度量
但是“小“是哪种意义上的小?最常见的代码长度度量是代码行数。但我觉得这个指标之所以最常见,仅仅是因为它最容易测——我不觉得有谁真的相信它是一段程序长度的真实考验。不同语言有不同的“一行该塞多少“的惯例——在 C 里,很多行除了一两个分隔符什么都没有。
另一个简单测试是程序的字符数——但这也好不到哪儿去;某些语言(比如 Perl)只是用了更短的标识符而已。
我觉得衡量一段程序大小的更好指标,是元素数——而所谓元素,就是你把源代码画成一棵树时会成为一个独立节点的东西。变量或函数的名字是一个元素;一个整数或浮点数是一个元素;一段字面文本是一个元素;一个模式中的元素,或一条格式化指令,是一个元素;一个新的代码块是一个元素。有些边界情况(比如 -5 是两个元素还是一个?)——但我觉得它们大多数对每门语言都一样,所以基本不影响比较。
这个指标还需要细化、并且在面对具体语言时可能需要解释——但我觉得它至少试图度量正确的东西,也就是一段程序由多少部分构成。我觉得你在这一练习中要画的那棵树,正是你为了构思这段程序必须在脑子里搭起来的那棵——所以它的大小,就和写或读它要做的工作量成比例。
设计
这种指标能让我们去比较不同的语言——但这(至少对我而言)不是它的主要价值。简洁性测试主要的价值是作为设计语言的指南。语言之间最有用的比较,是同一门语言两个潜在变体之间的比较。我能在这门语言里做点什么,让程序更短?
如果一段程序的概念负担与它的复杂度成比例,且一个给定程序员能容忍的概念负担是固定的,那么这件事就等同于:我能做点什么,让程序员做出最多的事? 在我看来,这又等同于:我怎么设计一门好语言?
(顺便说一句——没有什么比“亲手设计语言“更能让“所有语言都等价“这条老生常谈显而易见地为假了。当你设计一门新语言时,你无时无刻不在比较两门语言——“如果我加了 x 的语言“和“如果我没加的语言”——以判定哪个更好。如果这真的是个无意义问题,那你索性掷硬币得了。)
把简洁当作目标看上去是发现新想法的好办法。如果你能做某件让许多不同程序都变短的事,那大概不是巧合——你大概发现了一项有用的新抽象。你甚至可以写一个程序来帮忙——在源代码里搜索重复模式。在别的语言里,那些以简洁著称的——会是从中找新想法的好对象:Forth、Joy、Icon。
比较
据我所知,第一个写过这些议题的人,是 Fred Brooks(《人月神话》作者)在 The Mythical Man Month(《人月神话》)里。他写道:程序员每天产出的代码量看起来与所用语言无关,大致都一样。我二十来岁第一次读到这件事时,对我是个大惊喜——也似乎有巨大的含义。它意味着:(a) 想让软件写得更快,唯一的办法是用一门更简洁的语言;(b) 谁愿意花力气这么做,就能把没这么做的对手远远甩开。
如果 Brooks 的假说是对的,它似乎就在黑客这件事的最核心。这些年里,我一直密切留意我能拿到的任何相关证据——从正式研究到关于具体项目的轶事——没看到任何与之矛盾的东西。
我还没看到任何在我看来算得上确凿的证据,而且我也不指望会有。像 Lutz Prechelt(德国软件工程研究者)那种“编程语言比较“研究——虽然给出的结果是我预期的那种——倾向于使用短到没有意义的问题作为测试。一门语言的更好测试是:当一段程序需要花一个月写时会发生什么。而唯一真正的测试——如果你和我一样相信“语言主要的目的是好用来思考“(而不只是“在你想出来之后告诉电脑做”)——是你能用它写出什么新东西。所以任何“必须满足某个事先定义好的规格“的语言比较,测试的都是稍微错了的东西。
一门语言的真正考验是:你能多好地发现并解决新问题——而不是你能多好地用它去解决别人已经表述好的问题。这两个标准很不一样。在艺术里,刺绣和马赛克这种媒介——如果你事先就知道自己要做什么,工作得很好;但如果你不知道,它们糟透了。当你想要在动手过程中才发现那幅图像(——比如要画一个人这种复杂度的东西,你不得不这样做——),你需要更流动的媒介,比如铅笔、水墨、油画颜料。事实上,挂毯和马赛克在实践中的做法是——先画一张画,然后照着复制。(“cartoon“这个词最初就是用来描述为此目的而画的画——也就是卡通画稿(为挂毯/马赛克作准备的等大底稿)。)
这意味着:我们大概永远不会有“编程语言相对力量“的精确比较。我们会有精度比较——但不会有准确比较。具体说,那些“为了比较语言而做“的显式研究——因为大概会用小问题、且必然会用预定义问题——会倾向于低估那些更强大语言的力量。
来自一线的报告——虽然必然不如“科学“研究精确——大概更有意义。比如,爱立信的 Ulf Wiger(爱立信工程师,Erlang 实践者)做过一项研究,结论是 Erlang 比 C++ 简洁 4 到 10 倍,而开发软件的速度也按比例更快:
爱立信内部各开发项目之间的对比表明:每行/每小时的产出率(覆盖软件开发的所有阶段)相当类似——与所用语言相对无关(无论 Erlang、PLEX、C、C++ 还是 Java)。区分各门语言的,于是变成源代码的总量。
这项研究还显式地处理了一件 Brooks 书里只是隐含提及的事(Brooks 度量的是已调过 bug 的代码行数)——用更强大语言写出的程序,倾向于有更少 bug。在网络交换机这类应用里,这件事本身就成为目的——也许比程序员的产出率更重要。
品味测试
最终,我觉得你必须跟着自己的直觉走。用这门语言编程的感觉怎么样?我觉得找到(或设计)一门最好语言的办法,是变得对“一门语言让你思考得多好“高度敏感——然后选/设计感觉最好的那门。如果某项语言特性别扭或限制了你,别担心——你会知道。
这种高度敏感是有代价的。你会发现自己受不了用笨拙的语言编程。我觉得用没有宏的语言编程令人难以忍受地受限——就像一个习惯了动态类型的人,被迫退回到一门“必须声明每个变量类型、且不能做不同类型对象的列表“的语言里编程时令人难以忍受地受限一样。
这事不只发生在我身上。我认识许多有过这种经历的 Lisp 黑客。事实上,衡量“编程语言相对力量“最准确的指标,也许就是——懂这门语言、且无论应用领域是什么、都愿意接任何能让自己用上这门语言的工作的人,所占的百分比。
受限感
我猜多数黑客都知道“一门语言让人感觉受限“是什么意思。当你有这种感觉时,发生了什么?我觉得是和“你想走的那条街被封了、必须绕一大圈才到目的地“的同一种感觉——你想说点什么,而语言不让你说。
我觉得这里真正在发生的事是——一门让人觉得受限的语言,是简洁不够的语言。问题不是简单的“你说不出你计划要说的“,而是这门语言强迫你绕的那条路,更长。试想这个思想实验:假设你想写某段程序,语言不让你按你的计划表达,但强迫你写出另一种方式——而那种方式更短。对我至少,那感觉不会很受限——那就像你想走的街被封了、十字路口的警察把你导向一条捷径而不是绕路——好极了!
我觉得“受限感“里多数(百分之九十?)来自被迫把你脑子里想的程序在语言里写得更长这件事。受限感主要是简洁的缺乏。所以一门语言让人觉得受限时,那(多半)意味着它简洁不够——而一门不简洁的语言,会让人觉得受限。
可读性
我开篇引的那句话还提到了另外两种品质——规整性和可读性。我不太确定“规整性“是什么——也不确定“既规整又可读的代码“比“仅仅可读的代码“多了什么优势(如果有的话)。但我觉得我知道“可读性“指什么——而且我觉得它也和简洁有关。
这里我们必须小心区分——“单独一行代码的可读性”,和“整个程序的可读性“。重要的是后者。我同意:一行 Basic 大概比一行 Lisp 更可读。但同一段程序用 Basic 写出来,行数会比用 Lisp 多得多(尤其是一旦你跨入了 “Greenspun 之地”(指代码沦为自制 Lisp 解释器,源自 Greenspun 第十定律))。读 Basic 程序的总功夫肯定更大:
总功夫 = 每行的功夫 × 行数
我对“可读性是否与简洁直接成比例“的把握,不像我对“力量是否与简洁直接成比例“那么大;但简洁当然是可读性中的一个因子(数学意义上的“因子”——见上面的等式)。所以“一门语言的目标是可读性、不是简洁“这种说法可能甚至没意义——它可能等同于在说“目标是可读性、不是可读性“。
“每行的可读性”,对第一次接触这门语言的用户来说意味着什么呢?意味着源代码“看起来不吓人“。所以“每行可读性“也许是个不错的营销决策,即便它是个糟糕的设计决策。它和“让人分期付款“的极成功技巧同构——与其用一个高昂的总价吓走他们,你告诉他们一笔很低的月付。但分期付款对买方而言净亏——而“仅仅每行可读“对程序员而言大概也是。买方将会付很多很多笔那种低、低的月付;而程序员将会读很多很多条那种“单独看可读“的行。
这种取舍早于编程语言。如果你习惯了读小说和报纸,第一次读一篇数学论文的体验可能让人沮丧——读一页就要花半小时。但我相当确定问题不在记号系统,哪怕感觉上像是。数学论文难读,是因为思想本身难。如果你把同样的思想用散文表达(——这正是数学家们在演化出简洁的记号系统之前不得不做的事——),它们不会更易读,因为论文会长成一本书。
到什么程度?
不少人否定了“简洁 = 力量“的想法。我觉得,与其简单地辩论它们等不等,更有用的做法是问:简洁等于力量在多大程度上成立?因为很显然,简洁是高级语言之所以存在的很大一部分。如果不是它的全部,那剩下的部分是什么?这些其他功能相对有多重要?
我提这件事不只是为了让讨论更文明。我真的想知道答案——到底什么时候、若有其时,一门语言会简洁到对它自己不利**?
我开始时的假说是:除了病态例子之外,我认为简洁可被视为等同于力量。我的意思是——在任何人会去设计的语言里,两者都等同;但如果有人故意想设计一门反例语言来推翻这个假说,他大概能做到。其实——我连这一点都不确定。
语言,不是程序
我们应当说清楚——我们谈的是语言的简洁,不是单个程序的简洁。单个程序写得过密,当然是可能的。
我在 On Lisp(PG 的 Lisp 著作)里写过这件事。一个复杂的宏,可能要省掉自身长度的好几倍才值得存在。如果某个棘手的宏每次使用都能给你省 10 行代码,且这个宏本身就是 10 行——那只要你用它不止一次就能净省行数。但这仍可能是一步坏棋——因为宏定义比普通代码更难读。你可能要把这个宏用上 10 次或 20 次,它才在可读性上带来净改善。
我相信每门语言都有这种取舍(虽然我怀疑——语言越强大,赌注越高)。每个程序员都见过这样的代码——某个聪明人用些可疑的编程小把戏让它勉强短了一点。
所以这件事没有争议——至少我没有。单个程序当然可以“简洁过头“对自己不利。问题是——一门语言也可以这样吗?一门语言能逼程序员以“牺牲整体可读性“为代价、写出(按元素数算)很短的代码吗?
为什么很难想象一门语言简洁过头?一个原因是——如果存在某种过分紧凑的表达方式,那大概也存在一种更长的表达方式。比如说,如果你觉得用了大量宏或高阶函数的 Lisp 程序太密,那你只要愿意,就可以写出与 Pascal 同构的代码。如果你不想把 Arc 里的“阶乘“写成对一个高阶函数的调用:
(rec zero 1 * 1-)
你也可以写出递归定义:
(rfn fact (x) (if (zero x) 1 (* x (fact (1- x)))))
虽然我一时想不起具体例子,但我对“一门语言是否可能简洁过头“这个问题感兴趣。有没有这样一门语言——它逼你以一种晦涩拘谨、令人难以读懂的方式写代码? 如果有人能举例,我会非常感兴趣看看。
(提醒:我要找的是按上面“元素“指标看很密的程序——而不是仅仅因为可以省略分隔符、且所有东西都用单字符名而短的程序。)