软件灵活性设计:如何避免陷入编程困境
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

推荐序

有时候,你在写程序时会被卡住。也许是因为你意识到问题的某些方面被忽略了,但很多时候是因为你在设计程序的早期做出了一些关于数据结构选择或代码组织方式的决定,导致结果非常受限,而且还很难撤销。

本书是让具体程序组织策略保持灵活性的大师之作。我们现在都知道,虽然声明一个固定大小的数组来保存要处理的数据是一件非常容易的事,但这样的设计决定可能会变成一个令人不快的限制,可能会使程序无法处理超过一定长度的输入行,或处理超过固定数量的记录。许多安全漏洞都是由于分配了一个固定大小的内存缓冲区,然后没有检查要处理的数据是否适合在缓冲区内导致的,特别是在为互联网编写的代码中,这种漏洞很常见。举个非常简单的例子,动态分配的存储空间(无论是由具有C语言风格的malloc库分配还是由自动垃圾收集器提供)虽然复杂但更灵活,而且还有一个额外的好处,那就是更不容易出错(特别是当程序语言总是检查数组引用,以确保索引在范围内时)。

一些早期的编程语言设计实际上提出了反映硬件组织风格的设计思想,这种理念被称为哈佛架构:代码在这边,数据在那边,代码的工作就是服务于数据。但是,程序组织方面存在一个严重的限制,那便是代码和数据之间僵硬的、一臂之隔的分离。早在20世纪末,我们就从函数式编程语言(如ML、Scheme和Haskell)和面向对象的编程语言(如Simula、Smalltalk、C++和Java)中认识到下列做法的好处:将代码视为数据,将数据当成代码,并将少量的代码和相关数据捆绑在一起,而不是将代码和数据分别组织成单一的模块。最灵活的一类数据是一种记录结构,它不仅能够包含像数字和字符这样的“原始数据类型”,也可以支持对可执行代码的引用(如一个函数)。最强大的一类代码构造了与适量精选数据捆绑在一起的其他代码。这样的捆绑不仅仅只是一个“函数指针”,还是一个闭包(在函数式语言中)或一个对象(在面向对象语言中)。

两位作者借助他们的集体编程经验,介绍了一套在麻省理工学院几十年的教学中开发和测试的技术,并进一步扩展了这一基本的灵活性策略。不要只使用函数,要使用通用函数。相比于普通函数,通用函数是开放式的。保持函数的小型化。通常情况下,一个函数最理想的返回值是另一个(已经使用精选数据进行构造的)函数。把数据当作代码,如果有必要的话,甚至可以在你的应用程序中创造一种新的嵌入式编程语言。(这就是关于Scheme语言如何开始的一种观点:MacLisp的Lisp方言不支持完全通用的函数闭包形式,所以Sussman和我只是用MacLisp编写了一种Lisp的嵌入式方言,该方言确实支持我们需要的那种函数闭包。)用更通用的数据结构来取代已有的数据结构,这个数据结构包含原来的数据结构并扩展了其功能。使用自动约束传播来避免过早决定哪些数据项是输入,哪些是输出。

这本书不是调研,也不是教程——正如我之前所说,它是大师之作。在每一章中,你可以看到两位专家通过逐步开发一大段工作代码来演示一种先进的技术,他们一边演示一边解释每一个步骤,偶尔暂停一下,指出陷阱或消除限制。你需要做好准备,当需要你动手操作时,能够通过扩展数据结构或编写额外代码来自己实现该技术,然后用你的想象力和创造力来超越作者所演示的内容。本书中的观点丰富而深刻,建议读者密切关注讨论环节和代码部分,这会让你受益匪浅。

Guy L. Steele Jr.

马萨诸塞州列克星敦

2020年8月