下载本文的PDF版本 PDF

阅读、写作和代码
DIOMIDIS SPINELLIS,雅典经济与商业大学

编写可读代码的关键是培养良好的编码风格。

四十年前,当计算机编程还是一种个人体验时,对易于阅读的代码的需求并未列入任何优先事项清单。然而,今天,编程通常是一项团队活动,编写其他人可以轻松理解的代码已成为一种必然。创建和开发可读的代码并不像听起来那么容易。

书写比阅读容易

有一种理论解释了为什么有时如此容易编写的计算机代码却如此难以阅读,它是这样的:

我相信您一定注意到,从家开车去一个您从未去过的购物中心可能是一项困难的导航任务。另一方面,从那个购物中心返回家往往容易得多。这种不对称背后的原因是根植于我们家基地的决策树。当我们冒险离开家进入未知领域时,我们做出的每一个决定都会打开更多未知的树枝,但是当我们返回基地时,我们的路线决策消除了未知的树枝。

同样,当我们编写代码时,我们可以遵循许多不同的实现路径来达到我们程序的规范,逐步缩小我们的选择范围,从而缩小我们的选择——就像我们从从未去过的购物中心返回家时所做的那样。我们键入的最后一个字符通常是唯一能产生正确程序的字符。

另一方面,当我们阅读代码时,我们解释语句或结构的每种不同方式都会为代码的其余部分打开许多新的解释,然而,沿着这棵树只有一条路径是正确的解释。因此,我们朝着决策树的根编写代码,但不幸的是,阅读代码却朝着树的枝干方向发展。

纯粹的人性

代码阅读通常如此困难还有另一个原因,这就是人性发挥作用的地方。代码有效的益处是立竿见影的:通常是一个运行的系统和程序员的丰厚薪水。但是,可读代码的回报会在未来累积,届时我们的代码将被维护和扩展。我们人类通常会低估未来的回报,尤其是当回报将由未知的代码维护者而不是我们自己获得时。

不幸的是,计算机编程教育通常侧重于如何在单一语言和单一执行环境中从头开始单独开发程序,这是一种在 20 世纪 50 年代和 60 年代流行的开发风格。如今,软件开发通常是一项团队活动,并且最常涉及扩展和维护以多种语言为各种执行环境编写的现有系统。现在,更重要的是理解代码概念、形式、结构和惯用语,以便能够编写其他程序员可以轻松阅读的代码。

稀缺的阅读材料

更复杂的是,我的研究表明,关于代码阅读的材料相对较少,这可能是因为直到 10 年前,可供阅读的真实或高质量代码还很稀缺。公司通常将源代码作为商业秘密进行保护,很少允许其他人阅读、评论、实验和从中学习。在少数情况下,重要的专有代码被允许走出公司的壁橱,这激发了巨大的兴趣和创造性的进步。

例如,考虑一下 John Lions 的《Unix 第六版注释,附源代码》(同行通信,1996 年),它使用源代码作为教学操作系统设计的一种方式。尽管他的书最初是在 AT&T 的资助下编写的,用于内部操作系统课程,并且不对公众开放,但多年来,它的副本以盗版 n 代复印件的形式流传。

然而,在过去的几年里,开源软件的普及为我们提供了大量的代码,我们可以自由阅读。当今一些最流行的软件系统——例如 Apache Web 服务器、Perl 语言、Linux 操作系统、BIND 域名服务器和 sendmail 邮件传输代理——实际上都以开源形式提供。

不可读的代码

我经常被问到:“在我遇到的‘生产级代码’中,哪种代码最不可读?” 我通常回答说,系统代码,例如操作系统内核、库、数据库系统和图形引擎,可能是最难理清的,原因有很多。

系统代码。首先,性能约束通常决定了低级结构的使用。此外,系统代码必须极其可靠,您可以在代码中看到许多特殊情况。系统代码通常直接与硬件交互,增加了另一层复杂性。

我最喜欢的例子是快速排序算法的实现。Robert Sedgewick,《C++ 算法,第 1-4 部分:基础知识、数据结构、排序、搜索》(Addison-Wesley,1998 年)的作者,将其展示为一个优雅的 19 行 C 程序,但我在实际 C 库实现中遇到的代码超过 130 行。多出的 111 行不是代码膨胀,而是一个精心设计且极其高效的实现的结果。多出的行代表了理论与实际实践之间的差异,一些开发人员只有在第一次处理实际系统时才会遇到这种差异。系统代码的一个优点是它在许多情况下是由非常聪明的人编写的,他们清晰的写作风格往往可以弥补一些固有的复杂性。

过度设计的面向对象系统代码。我经常遇到的另一种不可读代码是过度设计的面向对象 (OO) 系统。这种类型的软件使用深层继承层次结构和多层委托,但没有明显的理由(除了炫耀程序员对 OO 概念的掌握程度)。当此类系统缺乏设计文档时——不幸的是,这种情况经常发生——它们可能比任何旧式的意大利面条式代码更难理解。原因是代码元素之间的关系无法通过简单地阅读代码来确定,因为代码的行为在运行时会根据每个对象的类的值而变化。将其视为一系列 goto,目标存储在变量中。

与固有的复杂代码相比,我更容易对随意过度设计的代码感到沮丧。

没有灵丹妙药

您可能会问,如果目标是避免那种不可读的代码,那么是否有灵丹妙药——您可以遵循的规则来使代码可读?

不幸的是,事情并没有那么容易。我见过每一条本应使代码清晰易读的规则都被滥用到荒谬的地步。我见过拜占庭式复杂性的所谓结构化代码,注释繁重的代码,其中注释只是重复左侧的代码(将 var 乘以 2),以及通过过度应用设计模式而变得晦涩难懂的简单算法。编写这些程序的程序员并没有打算编写不可读的代码;他们只是采用了这些规则并盲目地应用了它们。

导致代码易于阅读的唯一因素是风格。您看,可读的代码类似于优秀的散文。虽然作家可能已经了解到保持句子简短和使用好的隐喻很重要,但这样做并不能保证写出一流的小说。杰出的作家知道优秀散文的规则——他们也知道何时打破这些规则。

同样,程序员应该有意识地思考他们键入的每个字符:“我如何才能让这个击键有价值?它将如何使代码更容易阅读?” 您永远不会通过自动驾驶编程来生成真正可读的代码。编码风格包括注意程序的结构、命名约定、注释、缩进等等。但更重要的是,它包括有意识地在每个点和每个级别决定哪个构造和哪个表达式将产生可读的代码。刻意地将您编写的每个代码元素都通过这个可读性试金石,是对您自己和您的同事最好的帮助。

程序可读性

代码可读性与编程语言设计之间的关系非常复杂,导致了一些违反直觉的结果。

例如,我见过的第一个大型生产级软件是原始 IBM PC BIOS 的源代码,它作为附录发布在 IBM 1981 年的技术参考手册中。6,000 行 8086 汇编代码很容易成为一团糟。然而,它们被示例性地结构化为子系统——例如自检代码、视频、磁盘、键盘处理程序等等——分为具有明确记录接口的过程。几乎每一行都加了注释。我实际上是通过阅读该代码来学习编写 8086 汇编程序的。IBM 的编码实践确保了用一种原始语言编写的程序实际上是可读的。

十年后,作为我的兵役的一部分,我参与了一个人事管理系统,该系统是用 Cobol 编写的,并由像我这样的平民维护了很多年,每个人只花几个月的时间在上面,然后继续前进。同样,我通过阅读现有代码来学习使用该语言。尽管我期望如此,但代码很容易理解和维护,如果有点枯燥的话。在这里,另一种力量在起作用——Cobol,尤其是年轻的计算机科学毕业生在工作中学习它时所实践的 Cobol,几乎没有留下创造性实验的空间。

实际上,有三个正交问题影响特定语言编写的程序的可读性。争论给定语言的可读性方面的人们经常未能区分它们。

关于注释的评论

早些时候,我提到了原始 IBM PC BIOS 的源代码有多么容易阅读,因为每一行都添加了注释。然而,有些开发人员说他们的代码是“天生可读的”,不需要任何注释,您只需要“阅读代码”即可。

让我向您讲述我在 1988 年在德国欧洲计算机工业研究中心担任研究学生时的经历。有一天,我发现第八版 Unix 的源代码不受保护地躺在磁盘分区上。我立即沉浸其中,寻找那些给我们带来 C 和 Unix 的英雄们实际上是如何编程的。考虑到 ed 行编辑器一定是 Unix 系统从一开始就存在的一部分,我找到了它的源代码并开始阅读。我记得,完整的编辑器由一个源代码文件组成,该文件立即以全局变量声明或包含语句开始;没有前导注释。这让我很惊讶,所以我搜索代码以寻找另一个注释。没有。有点失望,我开始探索代码。变量名很短,我在大学学到的许多结构化编程规则都被打破了,但代码却出奇地可读且易于理解。

不幸的是,我知道如果我在这里结束我的故事,程序员们会拿着这篇文章冲到他们的老板面前说,“看,没有必要写注释!”

好吧,让我告诉您一个坏消息:我们大多数人都不是 图灵奖得主 Ken Thompson 和 Dennis Ritchie。我们通常不会在 PDP 汇编程序中编写一个可运行的 Unix 内核和一个 C 编译器,然后再用 C 重写并引导它。即使我们的编码能力达到了那个水平,我们几乎可以肯定也不会在 20 世纪 70 年代的贝尔实验室工作,在那里,明天将阅读我们代码的隔壁邻居可能要么设计了该语言,要么编写了它的编译器。

因此,作为一项规则,我们需要注释来弥补我们使用的不太出色的编码结构和命名构造。我们还需要注释详细说明算法的功能,以帮助那些尚未记住完整的 Donald Knuth 的《计算机程序设计艺术》(Addison-Wesley,1998 年)的同事。

在我为自己使用而编写的代码中——我不希望任何人阅读的代码——我最近统计了大约每 10 行代码就有一个注释。根据我之前所说的,您可以将此解释为承认我是一个糟糕的代码编写者和一个更糟糕的代码阅读者。但事实是,好的注释是帮助代码阅读的宝贵工具。即使代码是自文档化的,好的注释也可以充当快捷方式,为您节省理解特定方法或函数的作用所需的时间。

注释也用作自动生成系统技术文档的基础。我在这里想到的是像 Javadoc 这样的应用程序。如果一个项目正在生成技术文档,那么这些注释是生成和保持文档更新的更有效的方法,而不是孤立地起草文档。

但是,您可能会问,因为注释很容易变得彼此不一致,那么注释会不会弊大于利呢?

“注释和代码会逐渐漂移,所以我们不要写注释”的论点是一个经常重复但通常毫无根据且站不住脚的借口。我读过成千上万行的代码,发现理解系统的两大障碍是编写糟糕的代码和注释的匮乏。只有极少数情况下,误导性注释才真正成为我的障碍。

诚然,误导性注释实际上可能会让您白费力气,因为我们倾向于相信原始程序员的措辞而不是我们自己的判断,但这对于代码来说通常也是如此。当我们看到 if 语句时,我们倾向于假设存在一些条件会导致执行其主体中的代码。然而,在维护不善的遗留系统中,我倾向于发现死代码的实例多于不一致的注释。没有人认真建议停止编写代码,因为它随着时间的推移会与其自身的结构不一致。然而,许多人仅仅因为注释提供了一个容易攻击的目标而攻击注释的编写。

宁愿不注释?

编程往往是一项紧张、沉浸式的活动。在所有此类活动中,任何分散即时目标注意力的事情——睡眠、健康饮食、社交互动,以及不幸的是,在我们的例子中,注释——往往被分配一个无限低的优先级。程序员只是在遵循其他创造性活动(如艺术绘画)中的规范特征。

我承认我倾向于陷入相同的行为。我采用的解决方案是将注释推迟到稍后,但不要太晚。当我完成编写一小段代码时——例如类定义、单个方法,甚至过程主体的离散部分——我知道是时候添加注释了。将注释推迟到所有代码都完成后才进行的程序员——以及容忍他们的管理者——永远不会得到正确注释的代码。即使您找到时间在事后添加注释——并且通常您不会——注释也会向“注释之神”磕头,并且在大多数情况下,它们将毫无用处,因为它们只是复制您在阅读代码时对代码的理解。

我的观察是,程序员不注释主要是因为他们发现这项任务非常费力。形式,例如编程语言的语法和我们遵循的设计模式,会让人感到自由。因此,代码是一种相对容易的表达媒介。相反,使用自然语言准确而简洁地表达自己非常困难,可悲的是,这并不是典型的工程或计算机科学课程中强调的技能。在两种表达形式之间交替甚至更加困难,因为它涉及在两个不同的脑部过程中快速上下文切换,这两个过程竞争使用相同的底层设备。

我们程序员将这种现象称为抖动,任何尝试在两种外语之间进行翻译的人都会对这种困难有生动的回忆。我认为分批执行创造性编码和注释是一个好主意。另一种方法是首先编写一系列空方法定义及其相关的注释,然后再填写它们的主体。

最后一个想法

新手程序员可能会发现,如果他们使用带有智能编辑器的集成开发环境 (IDE) 来格式化他们的代码,使其更具可读性。对于更有经验的程序员来说,使用 IDE 编写的代码通常不如使用程序编辑器编写的代码可读。这是因为 IDE 可以提高程序员的生产力,但它会将自己对世界的看法强加于软件之上。由于缺乏 IDE 视图,未来的维护者可能会发现许多松散的结尾和不连接的部分。只要您使用相同的 IDE 来读取和编写代码,您就可以通过 IDE 的美化镜头获得系统的精致外观。当这种效果消失时,可能是因为您已切换 IDE 或移动到缺少 IDE 的平台,您的代码结构就会崩溃。

一个极端的例子是 Visual Basic 代码。我有时会浏览已保存的 VB 项目的文本源代码,或者在代码上使用 egrep 来查找与复杂正则表达式匹配的行。但我永远不会梦想在 IDE 之外开发甚至编辑 VB 程序。

另请记住,使用某些 IDE 不可能开发可读的代码。我指的是那些仅以二进制文件格式保存代码的 IDE。我像躲避瘟疫一样躲避它们。本机代码格式无法由人类或其他工具读取,并且它会将您锁定为使用特定的 IDE。您无法将代码放入您选择的版本控制系统中,您无法查看文件版本之间的差异,除非 IDE 支持此功能,并且如果代码中的一个字节损坏,您就完蛋了。

我对程序员的最佳建议是深入学习一款功能强大且广泛使用的编辑器,如 Emacs 或 Vi 及其后代。使用该编辑器编写您的代码,以便 10 年后使用类似功能的工具阅读它的同事能够分享您对它的看法。现代 IDE 在许多方面都比单独的工具做得更好:符号调试、增量编译和代码浏览都是如此。所有这些活动都对您的生产力大有裨益,但不会直接影响您的代码。

因此,将 IDE 用于这些任务。但不要将您的代码锁定在其中。您的代码很可能比您用来开发它的 IDE 寿命更长,走得更远。

结论

在未来的几年里,程序员将越来越重视不仅能工作,而且还能轻松阅读和理解的编码。为了经受时间的考验,编码必须通过可读性的试金石。问

DIOMIDIS SPINELLIS 拥有软件工程硕士学位和计算机科学博士学位,均毕业于英国伦敦帝国学院(University of London, UK)。他的新书《代码阅读:开源视角》(2003 年)为 Addison-Wesley 的有效编程系列揭开了序幕。他是希腊雅典经济与商业大学管理科学与技术系的助理教授。

acmqueue

最初发表于 Queue vol. 1, no. 7
数字图书馆 中评论本文








© 保留所有权利。

© . All rights reserved.