软件困局:为什么聪明的程序员会写出糟糕的代码(txt+pdf+epub+mobi电子书下载)


发布时间:2020-08-28 07:41:56

点击下载

作者:(美)亚当·巴尔(Adam Barr)

出版社:机械工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

软件困局:为什么聪明的程序员会写出糟糕的代码

软件困局:为什么聪明的程序员会写出糟糕的代码试读:

前言

1988年11月,尚处于萌芽状态的互联网上的计算机遭到了一种计算机病毒攻击。这种病毒滥用了一个程序员的错误:假定可以信任另一台计算机发送了正确数量的数据。这是一个简单的错误,其修复也很容易,但是所使用的编程语言很容易受到这种类型错误的攻击,并且没有一种标准的方法来检测这种问题。

2014年4月,现在无处不在的互联网上的计算机遭到一种计算机病毒攻击。这种病毒同样滥用了一个程序员的错误:假定可以信任另一台计算机发送了正确数量的数据。这是一个简单的错误,其修复也很容易,但是所使用的编程语言依然容易受到这种类型错误的攻击,并且依然没有一种标准的方法来检测这种问题。

在经历了四分之一个世纪的发展之后,软件工程仍然停滞在“易受攻击的编程语言”和“无法检测错误”的阶段,这显然不是人们期望的。其他新的工程学科在发展初期会生产出不可靠的产品。例如,在航空业发展的早期,人们在车库里制造飞机,结果是可想而知的。而一百年后,我们已经无法想象那个没有航空旅行的世界了——我们已充分掌握了这项技术,在统一的工程标准的基础上,可以制造出非常可靠的飞机。

但是,编写软件却不是这样的。虽然软件被称为工程学科,但它几乎没有工程的特征,即随着时间的推移,在严格的实验基础上建立起一个知识体系。人们自然会问关于工程产品的那些问题:它有多坚固?可以使用多久?什么情况下可能失败?对于软件来说,无论是针对程序的一个部分还是整个软件,这些问题都无法得到可靠的答案。专业许可是大多数工程学科的标志,但这却被软件行业视为潜在的诉讼来源,而不是制定标准的机会。

这样做不仅造成了用户可见的错误,还造成了程序员的重复工作、大量精力浪费、挫败感增加,并且使软件发布被延迟或永远无法发布。

如果你听说过软件行业,那可能是因为不同寻常的程序员面试方式。网站、书籍,甚至是为期一周的培训课程都致力于帮助人们为令人畏惧的编码面试做准备,在这个过程中要么完全有机会展示你的技能和知识,要么完全没有机会。尤其是“白板编程”,候选人必须在白板上匆匆写出短程序。一些求职者抱怨说,这并不能准确反映他们的日常工作,他们希望公司把注意力集中在他们背景中的其他方面。然而,这些求职者可能没有意识到,在他们的背景中并没有太多可以关注的方面。与其他工程学科不同,拥有软件工程学位并不能保证你理解已有的关于编程工具和技术的知识,因为这样的东西根本不存在。你在大学期间可能写了很多代码,但是没有办法知道这些代码是否有用。因此,要求求职者在白板上写代码片段是我们评估一个人的最佳方式。

请看这个笑话,尽管这不是什么好笑的事:你怎么称呼医学院毕业排名最末的人?答案是“医生”——因为从医学院毕业并完成实习意味着他已经学会了如何做医生。我问过医生,他们是如何面试然后被聘用的。他们说,面试时从未被问过具体的医学问题或完成某种简单的医疗程序;相反,面试官谈论的是他们如何与患者交谈、他们对新药物的看法如何这类事情,因为面试官知道他们已经掌握了医学的基本知识。但是,对于计算机科学专业的毕业生来说,这样的普遍要求并不实际。

早在1990年11月,卡内基–梅隆大学的玛丽·肖(Mary Shaw)为《IEEE软件》杂志撰写了一篇题为《软件工程学科的前景》的文章,她解释说:“工程依赖于有关技术问题领域的、以实践者可以直接使用的方式编纂的科学知识,从而为实践中常见的问题提供答案。普通的工程师可以用这些知识来更快地解决问题。这样一来,工程部门就可以共享先前的解决方案,而不是总依赖于某个行家的问题解决方案。”她将软件工程与土木工程进行了比较,并指出,“尽管大型土木结构在有历史记载之前就已经建成,但在过去的几个世纪里,它们1的设计和建造都是基于理论知识,而不是凭直觉和积累的经验。”我翻阅了美国土木工程师协会的出版物目录,其中尽是有趣的标题,如《水管情况评估》和《寒冷地区路面工程》,我很欣赏在其他工程学科中有这么多的理论知识。

回顾各种形式的工程史,肖写道,“工程实践是从商业实践中产生的,它充分利用了一门伴随科学的成果。科学成果必须成熟和丰富到能够建模实际问题。这些知识也必须以对实践者有用的形式组织起2来。”然而,自从她的文章发表以来,软件工程界在构建支持真正的工程学科所需的科学结果方面几乎没有取得进展,它仍然停留在“直觉和积累的经验”阶段。同时,软件在现代生活中变得至关重要,人们认为软件比支撑它的工程方法的保障更可靠。

肖在文章的结尾说:“好的科学依赖于研究者和实践者之间强有力的互动。”然而,文化差异、无法访问大型且复杂的系统,以及完全难以理解这些系统,都干扰了支持这些交互的交流。同样,对如何将研究结果转化为生产环境的有用元素的理解不足,阻碍了研究界研究结果的采纳……简单地说,如果能够培养学术界和工业界之间的建3设性交互,那么软件的工程基础将发展得更快。

在2013年由计算机协会(ACM)主办的“系统、编程、语言和应用:服务人类的软件”(SPLASH)会议上,一位名叫格雷格·威尔逊(Greg Wilson)的程序员发表了题为“两个独行侠”的主题演讲,讨论了软件学术界和工业界之间的这种分歧。在做了一段时间的程序员之后,威尔逊发现了里程碑式的书籍《代码大全》(Code Complete),这是第一批尝试解释软件工程实践的书籍之一,也是少有的关于软件实践研究的书籍之一。威尔逊意识到他以前不知道这一4切,正如他在演讲中所说的:“我怎么不了解我们熟悉的事情呢?”后来他意识到他的同事跟他一样,而且,他们对自己的这种无知很满足,也不想多学点东西。他还评论说:“参加软件工程国际会议的人中,不到20%来自工业界,而且大多数人在微软研究院这样的实验室工作。相反,只有少数的研究生和一两个富有冒险精神的研究人员参5加大型的工业会议,比如年度敏捷大会。”

对软件工程的焦虑这一术语自50多年前被发明以来就一直存在。本书不会提出解决方案,虽然我在最后给出了一些建议,但本书将尝试提供软件行业从早期到现在所经历的路线图。

除了几章以外,其他章节是按时间顺序编排的,始于1980年左右,大致与我作为一名程序员的经历平行。本书并没有尝试给出软件行业的完整历史,相反,它深入挖掘了一些特别重要和具有代表性的特定时刻。这些时刻包括一系列号称可以一并解决程序员面临的所有问题的想法,这些想法在还没有不可避免地回到现实时就被下一件大事取代了。与此同时,学术界和工业界之间的鸿沟不断扩大,使得每一个新的想法在研究中都不那么持久,而软件则进一步远离而不是更接近玛丽·肖希望的工程基础。

从根本上讲,本书是关于一个我经常问自己的问题:是软件开发确实太难,还是软件开发人员能力不足?

最后,提醒一下不喜欢技术细节的读者:本书含有一些代码。不要惊慌。如果不了解程序员在想什么,就不可能了解软件行业;而如果不深入了解程序员编写的实际代码,就不可能了解程序员在想什么。好软件和差软件之间的区别可能就是一行代码——一个程序员做出的看似无关紧要的选择。要理解软件中的某些问题,需要对代码有足够的理解才能明白,以及为什么程序员编写了糟糕的代码,而不是优质的代码。

所以请阅读代码吧!非常感谢。注释

1.Mary Shaw,“Prospects for an Engineering Discipline of Software,”IEEE Software 7,no.6(November 1990):16,18.

2.同上,21页。

3.同上,24页。

4.Greg Wilson,“Two Solitudes”(keynote address,SPLASH 2013,Indianapolis,IN,October 30,2013),accessed December 18,2017,https://www.slideshare.net/gvwilson/splash-2013.

5.Greg Wilson and Jorge Aranda,“Two Solitudes Illustrated,”December 6,2012,accessed December 18,2017,http://third-bit.com/2012/12/06/two-solitudes-illustrated.html.致谢

真诚地感谢通过会面或电子邮件接受我采访的计算机科学家:亨利·贝尔德、维克托·巴斯利、弗雷德·布鲁克斯、罗伯特·哈珀、高德纳、亚当·麦凯、大卫·帕纳斯、沃恩·普拉特和本·施内德曼。我还要感谢格伦·达迪克和文森特·埃里克森,感谢他们回复我在脸书(Facebook)上即兴写下的信息,并且填补了一些重要的历史细节。

我还要向所有审阅本书的人表示最深切的感谢。我要向我的妹妹黎贝卡表达最衷心的感谢,她用专业编辑的眼光通读了全书三遍。我也很感谢我的父母迈克尔和玛西娅,还有我的弟弟乔,他们阅读了整本书并给出了很多反馈意见,还有克里希南·拉玛斯瓦米,尽管他与我没有任何关系,但他还是阅读并评论了每个章节。我还想感谢我的儿子扎卡里,他提供了宝贵的反馈,还有艾米丽·帕佩尔和卡丽·奥尔森,他们在本书早期创作期间给予了我很大鼓励。感谢伯纳德·范、凯特·瓦尼和约瑟夫·怀特的评论。鲍勃·德鲁斯再一次出色地完成了最终手稿的编辑。

非常感谢麻省理工学院出版社每一位参与本书出版工作的编辑,特别是玛丽亚·卢夫金·李,她对这本书表达了最初的兴趣并最终推动了本书的成功出版。克里斯汀·萨维奇和斯蒂芬妮·科恩回答了关于本书的许多问题。感谢一位匿名审稿人精辟的评论。我还要感谢弗吉尼亚·克罗斯曼、辛迪·米尔斯坦和苏珊·克拉克。

感谢微软卓越工程团队的所有同事,尤其是那些与我在卓越开发者项目中合作过的同事,我要特别感谢埃里克·布雷奇纳让我进入卓越工程团队,并为我们的许多工作提供指导。我还要感谢克里斯汀·莱恩向我解释旋钮和管道的接线问题。

感谢国王县图书馆系统的工作人员为我在写作本书时提供的环境。感谢当地各咖啡店的咖啡师,特别是伊萨夸的Yum-E酸奶。西雅图的计算机博物馆让我重温了许多年轻时的记忆,我非常感激。

Reddit论坛上的许多匿名人士贡献了一些与本书直接相关或间接相关的知识。虽然我们并不相识,但请相信,这是向您表达的感谢。我也要感谢我在写作本书期间阅读过的维基百科页面的维护者。

最后,我要感谢我的妻子毛拉,她忍受了我那一大堆布满尘土的软件书籍,一如既往地陪伴我左右。第1章 早期的日子

我手里有一些1982年无线电器材公司出售的计算机目录,不知是什么原因,这些东西从我高中时代一直保存到现在。这些产品的牌子是TRS-80,并且看起来很熟悉:带有显示器和键盘的计算机、打印机、硬盘以及游戏和办公软件。按目前的标准来看,它们的价格有点高,但在当时却是合理的:一台基本的台式计算机800美元,一台有额外内存的则要1100美元,再添加一个功能更强的商务系统则是2300美元。当时还没有笔记本计算机(尽管有“袖珍计算机”),触1摸屏也是后来很晚才出现,但其他的配件都和如今的差不多。

然而细节会让人惊讶。与目前的硬件相比,这些计算机的计算能力差得可怜。入门级的TRS-80Ⅲ型只有4KB内存,也就是4096字节。从今天的角度看,即使一台低端计算机也有4GB内存,很难想象4KB内存(不到如今低端计算机内存容量的百万分之一)的计算机能做什么有用的事情。

外存的差异同样是巨大的。当时一个售价为5000美元的硬盘可以容纳8.4MB,如今购买一个更大的8TB硬盘也用不了300美元。当时一张软盘可容纳170KB(需要再付1000美元购买软盘驱动器和磁盘操作系统软件),现在的U盘容量是这个软盘的100多万倍。将一个现代的存储设备容量缩小到当时的容量大小,就相当于将本书缩减到半封信。以今天汽车速度百万分之一的速度行驶的汽车每小时移动十几厘米,在正常人看来,这样的车是静止不动的。

1982年也是我个人历史上的一个里程碑:我们家购买了第一台家用电子计算机,一台运行IBM PC DOS(磁盘操作系统)1.00版的早期IBM个人计算机。

在1981年底IBM PC出现之前,计算机行业有三类不兼容的品牌:无线电器材(Radio Shack)、苹果(Apple)和康懋达(Commodore)。如果你想把软件卖给拥有这三款计算机的人,那么你必须写三次软件。IBM PC推出了第四个平台。在当时,IBM发展成为世界上最知名的计算机公司,它的名字几乎成了个人计算机产业的代名词,IBM PC的硬件设计比竞争对手的产品更具扩展性。同时,微软向其他硬件公司出售了DOS系统的一个版本,一家叫作凤凰科技(Phoenix Technologies)的公司编写了IBM计算机中一种受法律保护的底层软件。总之不论是由于意外事件还是上述或其他原因,所有这些因素都在某种程度上促使IBM PC成为所有个人计算机的标准,这一标准得到了兼容计算机销售公司的市场支持,另外三家竞争对手也渐渐退出了历史的舞台。标准平台的确立触发了个人计算机软件产业的快速增长。如果说这些运行缓慢、能力不足的计算机就像是一块贫瘠的土地(在这块土地上仍然成长出了像IBM这样的企业),那么从程序员的角度来看,过去的计算机似乎已经是几个世纪以前的产物。

我小时候接触实际计算机的机会并不多,因为大部分的技术不适合家用。我通过玩乐高积木为未来的职业生涯做准备,积木是我这个年龄段的程序员人生故事中的一条主线。在20世纪70年代中期,计算机有两种形式:大型计算机和小型计算机。大型计算机是你在老电影中看到的那种,用于天气预报和其他用途,往往为大公司、政府和大学所有;小型计算机则是那种体积更小、自成一体的机器,被企业用于诸如计算工资表之类的用途。在当时看来,在家里放一台计算机的想法是无聊且荒谬的。计算机是用来做枯燥但重要的事情的,如果任你选择,你会把它放在哪里呢?1976年微软成立时,企业的愿景是“让每张桌子上和每户家里都有计算机”,这听起来,似乎完全是一种幻想。

1977年首次出现了三种非常成功的个人计算机,即康懋达公司的PET、Tandy公司的TRS-80和苹果公司的AppleⅡ,随之一起出现的还有Atari 2600游戏系统(和现在一样,当时的游戏系统也是计算机,只是具有不同的用户界面和不同名义的用途)。与此同时,在这些斗志昂扬的新星之外,远在麦吉尔大学数学系(我父亲在那里担任教授),有人说服该系从一家名为王安(Wang)的公司以20000美元的价格购买一台小型计算机,外加每年2000美元作为维护服务费用。大型计算机往往是工业冰箱的大小,安装在能控制温度和湿度的玻璃墙后面,但小型计算机的尺寸要小得多,可以安装在任何地方。小型计算机最终被原始个人计算机的后代所替代(当时的个人计算机用不怎么被看好的术语“微型计算机”来称呼),不到15年,王安实验室(Wang Laboratories)便面临了危险的“创新者的窘境”(Innovator’s Dilemma)——借用克莱顿·克里斯坦森(Clayton Christensen)的书名——从一家拥有三万名员工的公司走向破产,2但在当时,王安计算机仍然被认为是性能优良的。

在我小的时候,家里没有参与到第一波的个人计算机购买潮流中——我们的客厅没有苹果、康懋达或TRS-80,甚至也没有游戏系统,但周六我父亲偶尔会带我到麦吉尔大学数学系,在那里我可以消磨一下午,在王安小型计算机上玩《保龄球》《足球》和《星际旅行》游戏——这些都是非常简单的版本,用文本可爱地呈现出“图形”(足球场地用破折号、加号和大写字母I标记,中场时屏幕上移动的字母B-A-N-D表示乐队的存在)。我一年大概有三次这样的机会,每当下一次机会临近时,我会满怀期待,至今我仍然能记得那种期待的感觉。写到这里的时候,我和我的两个孩子正在飞机上。此时两个孩子都在看书,但半小时前他们还在手持设备上玩游戏。对于玩计算机游戏这件事来说,当时和如今的差异(就不用说游戏本身的质量了)是如此巨大,令人难忘:就好像我在一代人的时间内观察到了从三叶虫进化到霸王龙的历程。

计算机游戏的出现大致与电子游戏《Pong》走进千家万户的时间点一致,所以,我不是唯一一个眯着眼睛紧盯着粗劣的电子游戏的孩子,但是,我开始编写软件的时间要比同龄人更早一些。我编写的第一个软件不是在计算机上的。惠普公司有一个计算器系列产品,这些产品可以执行用户编写的程序(正如其中一个计算器的说明书所述,“由于其先进的计算能力,这些计算器甚至可以被称为个人计算3系统”)。20世纪70年代末,我父亲拥有几台惠普计算器,其中一台还配有内置热敏打印机(HP-19C)。这些计算器主要用于数学运算,例如计算抵押贷款,但我不需要做这些。我只是对纯粹的程序设计很感兴趣,所以我写了一些在现实中无用的程序,比如打印素数的程序(我从来没有发现自己需要素数列表,但我已经用几种程序语言编写了这个程序)。

在进一步讨论程序设计之前,我应该先解释一下计算机是如何运行程序的。处理器是计算机内部的芯片,它有一组寄存器,每个寄存器可以存储一个数字。处理器可以对这些寄存器中的数字执行加法、乘法等操作,还可以对它们进行测试,检查它们是否等于某个值,或者其中一个值是否大于另一个值,例如,如果结果为真,则跳到程序中的另一个位置。最后,由于寄存器数量有限(通常是8到32个),处理器可以将值从寄存器移到计算机内存,并可从计算机内存移到寄存器,以便保存这些值并在需要时送回寄存器。

基本上这就是处理器可以做的事情:对寄存器的操作,这些寄存器之间的比较,基于这些比较结果的跳转,以及在寄存器和内存之间来回移动数据。处理器还有其他一些功能(比如直接对内存中的数据执行数学运算的能力,而不是先把数据移到寄存器中再做计算),但实际上所有的操作都是由以上这些指令的组合构成的。

计算机运行的程序是一系列指令。例如,在当今许多个人计算机使用的英特尔处理器上,有一个名为ADD的指令可以将两个寄存器相加。要将EAX寄存器和ECX寄存器相加(并将相加的结果存储在ECX中),可用如下可读形式的指令编写:4

但对于机器来说,它实际上是一个比特序列:

也就是“只包含0和1的序列”,即“计算机处理器解析只包含05和1的序列”。

只包含0和1的序列被称为机器语言,而人类可读的形式如

被称为汇编语言。一个被称为汇编程序的程序可以把汇编语言转换成机器语言,以使计算机能执行该程序。

惠普计算器上的程序设计其实也是一种汇编语言的体验:我的程序必须将数据在内存和处理器可以对其进行操作的位置之间来回移动(从技术上讲,这是一个栈,而不是寄存器,但实际使用中相当于在6只有两个寄存器的处理器上进行程序设计)。有些人说,每个程序员都应该先学习汇编语言,这样他们才能知道程序在底层是怎么运行的,但事实上,这些惠普计算器程序虽然在那个时候引人关注,但使人容易在程序设计时犯简单的错误,而且代码可读性很差。

在1980年底,我父亲把一种称为终端的设备带回家,它可以连接到麦吉尔大学的主机。按照今天的标准,这种设置是很原始的——即使按照几年后的标准来看,这种设置也是原始的——但这是可以理解的,因为它的发展大致处在计算爆发前的黎明到现在的中间点。终端是一种被称为电传打印机或电传打字机的类型,也被称为行终端。它有一个键盘,但没有屏幕,只有一台打印机,所以它看起来像一台消耗折叠纸的大型打字机(这种连续折叠纸的两边有可移动的孔,现在只能在旧电影和汽车租赁公司里看到)。这种设备一直处于无精打采的状态,直到我们拨通麦吉尔大学里一台大型计算机的电话号码,并将听筒放在一个声音耦合器上为止。

声音耦合器看起来像装在一个盒子上的两个超大耳机听筒(在71982年无线电器材公司的目录中一台卖240美元),把听筒紧紧地抱住,把哔哔声和隆隆声传给麦克风,就能听到扬声器发出相同的声音。这些声音被用来与麦吉尔大学里的计算机交换数据,麦吉尔大学里的计算机进行所有的实际处理。如果你设想了一台有键盘、显示器和主机的现代台式计算机,那么键盘和显示器在我家,而主机在麦吉尔大学,它们不是通过短电缆而是通过电话连接。它的连接速度相当慢,只有300波特,大约每秒30个字符,相当于今天典型宽带连接带宽的百万分之一。关于慢连接的好处,我能想到的只有终端的低速打印不会成为问题,因为一整行80个字符的文本需要大约3秒钟的时间来传输,所以打印机的打印速度虽然很慢,但跟上传输速度还是没有问题的。

坐在我父母的卧室里,我在这个系统上学会了一种叫作WATFIV8的程序设计语言,它是早期程序设计语言Fortran的一个版本。与汇编语言相比,这是一种更高级的语言,它提供了有用的抽象,例如能够为存储位置(被称为变量)命名,而不需要使用处理器寄存器名,例如EAX和ECX。一种叫作编译器的程序将高级语言转换成机器语言(从概念上讲,编译器将其转换成汇编语言,然后再转换成机器语言,但通常它会直接输出0和1)。

由于WATFIV语言是设计成在大型计算机上运行的,而这些计算机通常通过使用传统的行终端访问(就像我使用的行终端那样),因此它的输出功能有限。由于终端常常既没有屏幕也没有扬声器,因此它不支持在屏幕上绘制图形或通过扬声器发出声音。WATFIV程序的输入/输出功能仅限于读取和打印文本行。

我作为一名Fortran程序员的正式训练来自一本书——《使用WATFOR和WATFIV的Fortran IV程序设计》(Fortran IV with WATFOR and WATFIV),在高中上历史课时我都会抽时间看一看这本书。这本书教会了我Fortran语言的基本语法,但没有解释如何编写一个程序以完成一些有用的事情,就像了解英语语法后你可以完成一张商品宣传页或一纸求婚书。

下面是来自这本书的一个WATFIV程序的例子,如果有一种程序设计语言非常适合以固定宽度的(我将用于本书中的所有程序片段)大写形式打印,那么它肯定是源自Fortran的语言(事实上,整本书9都是以固定宽度的字体打印的):

这个程序并不难读懂,如果你本能地跳过了代码,那么我鼓励你返回去读一遍代码。程序中的一组步骤通常被称为代码(Code),但并不像《达·芬奇密码》(The Da Vinci Code)那样。达·芬奇密码是一个无法理解的谜,而代码只是符合特定规则的一系列指令。

程序声明了两个INTEGER类型(表示数字)的变量,分别命名为X和SUM,将SUM初始化为0,读取一个值并将其存储在X中,并检查X是否为0。如果X是0,那么它会打印出总和并终止;否则它会将X的值加到SUM存储的累加和中,然后返回读取另一个值。

指令行READ,X和PRINT,SUM就是所谓的API,它代表应用程序编程接口。API为程序提供完成某些任务的功能,在这里的功能分别是读取一个值和显示一个值。我们说代码调用了一个API,也就是说,代码告诉编译器它想跳转到该API来执行一个操作。API可以接收传递给它的参数,这些参数提供更详细的信息。在这段代码中,传给READ的参数是X,告诉它要将值读取到什么变量,传递给PRINT的参数是SUM,告诉它要打印什么变量。

代码左侧的数字2和117是可选的行号,它们是GO TO指令的目标;GO TO 2表示“跳到编号2的行并从该点继续执行”。与大多数现代语言不同,每行开头前的空格很重要。根据Fortran的规则,行号写在第1列到第5列中,实际的程序代码从第7列开始。第一行将C放在第一列作为起始,表示第一行是注释,编译器会忽略注释的内容。

至于这个程序调用应用程序编程接口READ时将要读取的数据,Fortran及其变体设计的是从穿孔卡(一张穿孔卡表示一行程序)读取程序,运行它们,并打印出结果。数据通常放在程序之后,在另一张穿孔卡片上(程序和数据之间必须有一个只包含文本$ENTRY的特殊卡片)。应用程序编程接口READ从卡片堆中的下一个穿孔卡读取数据(运行在麦吉尔大学计算机上的WATFIV版本是增强版,允许你将程序和数据存储在大型计算机的磁盘驱动器上,而不必每次从卡上10重新读取,如果你愿意,还可以读取键盘上键入的输入数据)。这个特定的程序期望最后有一个值为0的卡片,表示数据的结束。

这些都是人们编写的典型Fortran程序,用于解决简单问题,例如根据学生各门课的考试分数计算学期成绩(当然每张打孔卡表示一个考试分数)。在许多情况下,编写程序的人就是使用这些程序的人,因为他们编写的程序特定于自己的具体情况。

在Xbox游戏玩家拥有自己的真实场景的今天,很难想到不久前,玩计算机游戏仍然被视为一种极客的爱好。许多玩计算机游戏的人也编写计算机游戏,他们无疑是极客。你可能不记得英国一个名为Sigue Sigue Sputnik的流行乐队,他们以一首《爱情导弹F1-11》(Love Missile F1-11)轰动一时。1986年,当我在当地的山姆唱片公司(Sam the Record Man Store)看到密纹唱片的背面时,我发现这支唱片是Sigue Sigue Sputnik乐队的。毫无疑问,他们的服装和发型看起来很酷、很有远见。但令我惊讶的是,这个乐队的成员把“电子游戏”列为他们的爱好之一——这对我来说是一个很重要的时刻。今天的一个学生告诉我,他们对程序设计感兴趣,并不是因为1和0的迷人,而是因为他们喜欢使用软件,并且认为学习如何编写软件可能很有趣,这让我有点惊讶。他们的兴趣来源居然与乐高积木没有关系?

至于惠普计算器,我用它胡乱地学会了WATFIV,脑子里却没有任何特定的目标。我当时写了一些简单的程序,比如将一个数字列表(在程序设计术语中被称为一个包含数字的数组)从大到小排序。或者如果我想得到交互体验,就会编写一个游戏程序——“计算机存储了一个数。你可以猜一下这个数,我会用‘猜大了’或‘猜小了’来回应你猜的答案。”即使是这样一个小游戏,靠自己编写出可用的程序也是需要花时间的,因此,你必须相信解决问题本身是有价值的——相信寻找解的过程本身就是回报。此外,你没有多少机会犯一个不能用试错法解决的错误。你可能会将更多的时间花费在发现一行Fortran代码错误地从第6列而不是第7列开始,而花费较少的时间用于处理程序逻辑错误。

就这方面而言,小时候玩乐高可能是个不错的准备。要将小组件拼成一个更大的部件,必须准确地遵循详细的指引。将它们轻轻地“咔嚓”连接在一起的过程与编写小程序的过程极为相似。

我们1982年购买的IBM PC是一个比麦吉尔大型计算机能力更强的软件平台。除了不必通过调制解调器远程连接(等到家里人不使用11电话的时候)以外,IBM PC还可以显示图形和播放声音。程序员利用这一优势编写文字处理程序,从而在屏幕上显示准确的格式,或者显示实时计算的电子表格,以及实现其他当时看来奇迹般的操作。玩足球游戏时不再需要先输入文本命令然后观察响应,不再需要通过字母B-A-N-D在屏幕上来回移动来表示乐队。现在你可以编写真正实现人机交互的游戏了(这正是我所关心的)。

更妙的是,包含在IBM PC中的BASIC语言支持所有这些硬件,因为这种语言是为IBM PC定制的。事实上,IBM PC的BASIC语言包含一些高级功能,这些功能是我在其他系统上找不到的。有一个名为PLAY的API,你可以用它来输入一种“曲调定义语言”,它便可以播放所定义的音符。比如12

将播放“玛丽有只小羔羊”。一个名为DRAW的API支持一种“图形定义语言”,如

将画出一个长方形和一个三角形,其中,三角形位于长方形的上

13方。

麦吉尔大型机上的可用命令非常有限,只能满足“学习程序设计”示例小程序的运行需求,而这些示例小程序可以很快写好,但是也很快就会被丢在角落。突然间,我从这种资源非常有限的程序设计环境转向了一个资源相当丰富的环境,在新的环境下我可以编写任何我想实现的高级程序,例如可扩展、可稳定地持续运行的程序(我甚至会将这些高级程序展示给其他人)。

这段时间涌现了一大批为IBM PC社区服务的书籍和杂志。然而,我的BASIC知识主要来源于计算机附带的参考手册。我再一次通过自学掌握了BASIC语言——我并不是想突出我自己的能力,我想强调的是,阅读参考手册是那时人们学习程序设计的标准方式。同时,我还在一个朋友的AppleⅡ计算机上编写BASIC代码,以同样的方式学习其中的细节(AppleⅡ的BASIC语言也支持图形和声音,但是在IBM PC和AppleⅡ计算机上BASIC语言的操作细节是不同的,事实上,当时大多数个人计算机的BASIC语言都有不同的操作细节)。

我跌跌撞撞地自己学会了足够多的IBM PC BASIC知识,还制作出一些街机游戏的劣质翻版,比如《吃豆人》(Pac-Man)游戏和《Q伯特》(Q*Bert)游戏。我在高中的最高成就可能是在毕业的那一年写了一个程序,将班上所有同学的名字排列成一个巨大的数字“84”,然后印在运动衫上。不幸的是,有几个因素阻碍了我学习编写大型程序的正确方法,尤其是在程序员职业生涯中编写的那种大型程序。

第一个问题是,无论那时是否有关于如何写“好”程序的知识,我都对此一无所知。BASIC手册中有一些简短的代码片段,每一段都描述了编写BASIC程序时使用的一部分关键字和API的正确语法和用法。虽然这些例子可能说明了不同部分完成的工作,但从未讨论过为什么特定的代码段是以某种特定方式编写的。代码示例有效,这就足够了。剩下的由你自己去判断。

面对仅用于演示调用一个API的小代码示例时,你不会花太多时间考虑可读性或清晰性。这种可读性和清晰性会以各种方式出现。例如,在一个较长的程序中,用I或J命名变量对可读性和清晰性并没有特别大的帮助,在这种情况下你需要更具描述性的信息。但是在这些示例中,人们经常使用单字母来为变量命名,在没有考虑过这一点(大型程序的可读性和清晰性)的情况下,这种风格将被沿用到实际的代码中。大多数程序员编写代码是为了自己使用,他们自己明白代码是如何工作的,所以不太关心代码对别人的可读性。当时的我当然也不关心。

第二个问题是计算机有限的内存。

为了适应资源的限制,IBM PC附带了三个BASIC版本,你可以购买一台只有16KB内存的IBM PC。在这样一台机器上,你只能运行计算机附带的盒式磁带BASIC(Cassette BASIC),这种版本被烧成32KB的ROM(只读存储器)芯片,这意味着它不占用16KB内存的任何空间。它之所以被称为盒式磁带BASIC,是因为你可以订购不带磁盘驱动器的IBM PC,而使用盒式磁带进行存储。在这种情况下,计算机将直接引导到盒式磁带BASIC。机器上没有DOS,因为没有可以操作(或从中加载DOS)的磁盘。

我估计没有几个人会订购一台仅可以使用盒式磁带的IBM PC(我隐约记得给早已休刊的杂志《Softalk for the IBM PC》的一封信中曾讲到这个问题),绝大多数人订购了带软盘驱动器的PC。这意味着你正在运行DOS,因此可以启动磁盘BASIC(Disk BASIC)或高级BASIC(Advanced BASIC)(即BASIC和BASICA),只需在DOS命令符后键入启动指令。

磁盘BASIC是盒式磁带BASIC的一个超集,它增加了对在磁盘上读写文件以及通过调制解调器通信的支持。高级BASIC还包括高级图形和声音API,如DRAW和PLAY。磁盘BASIC需要32KB的内存,而高级BASIC则需要高达48KB的内存。需要记住的是,内存必须容纳BASIC的解释器本身(尽管一些更高级的BASIC确实依赖于存储在单独的ROM中的一些磁带BASIC代码)、你的程序代码以及任何程序中用变量存储的数据。

考虑到这一点,所有变量使用单个字母命名的习惯便很容易理解了,使用较短的变量名可以尽量减少代码占用的内存。BASIC允许你在代码中添加注释来解释这段代码的行为,注释的方法是在语句的前面加上REM或'(单引号)字符,但是注释也被视为一种占用内存空间的奢侈品。一台16KB内存的IBM PC甚至不是运行BASIC的最低配环境。回想一下,无线电器材公司销售的是一台具有4KB低配置内存的TRS-80计算机,它运行的BASIC非常小,甚至几乎不支持包含文本序列的字符串变量。你的程序代码中只能有两个字符串变量,分别14被命名为A$和B$。

事实上,为了节省内存,一些早期的BASIC(虽然不是IBM PC附带的版本)允许省去空格字符。循环(一种允许语句重复的标准程序设计结构)通常是像下面这样用BASIC编写的(当时的BASIC程序在每一行上都需要行号):

但是你也可以把它们写在一行:

这一行可以被压缩成

而且这被认为是完全正常的,甚至是聪明的。

这些内存限制在大型计算机时代的配置上倒退了一步。那些大型计算机也没有太多的内存(虽然我不知道麦吉尔大学计算机的具体细节,但在20世纪70年代,一台典型大型计算机的内存在256KB到1MB之间),并且在任何时候所有连接的用户共享内存,但是操作系统使用了一种称为虚拟内存的技术,使得磁盘驱动器可被用作额外的内存。在这样的环境中,从代码中删除一些字节就不那么重要了,程序员可能偶尔会在注释行上挥霍一下。

妨碍我在IBM PC上学习正确的编码技术的第三个问题是,用当时的BASIC语言编写大型程序是困难的。大型程序由多层的代码组成,而且通常各层由不同的人编写,并通过API连接在一起。你编写的代码通常调用较低层提供的一个API,但也提供一个供较高层调用的API。虽然BASIC允许代码调用BASIC本身提供的API,例如DRAW和PLAY,但是你的代码无法向其他代码提供自己命名的API。

BASIC确实有子程序,它允许代码跳转到程序中的其他地方,然后返回到调用代码。它在概念上是一个API,但是不是由名称引用的,而是由行号引用的(类似于Fortran的GO TO语句或BASIC的GO TO语句)。此外,子程序不支持参数。它直接引用变量。程序员使用GOSUB(有时拼写为GO SUB)语句和指定行号调用子程序。考虑1983年出版的《结构化BASIC》(Structured BASIC)一书中的这个例15子(为了清晰起见稍作修改):

在第120行,程序调用从第700行开始的子程序,其功能是将0到A的数字相加,并将结果存储在S中。从概念上讲,变量A是该子程序的参数,因为子程序在计算中使用A,变量S是子程序的返回值,因为S设置为子程序的净值。但是事实上,除了子程序用到了这些变量外,这些变量与子程序是没有任何关系的。用软件术语来讲,BASIC中的所有变量都是全局变量,这意味着,无论是在主程序还是子程序中,任何代码都可以访问这些变量。调用者在调用第700行开始的子程序时,必须“恰好知道”在调用前将某个值存入A,并且“恰好知道”子程序计算的返回值将存储在S中。更不用说,它必须“恰好知道”将0到A之间的数字相加的子程序是从第700行开始的,而不会一16不小心把程序跳转到第690行或710行。第700行和740行的注释以一个感叹号(!)开始,告诉读者它们是子程序的开始和结尾,但是编译器忽略注释,并不知道第700行应该是子程序的开始。

这看起来可能只是一个很小的细节,但实际上,它使得调用其他人的代码变得相当棘手(当然,我独自编程,没有与其他程序员合作(所以不存在这个问题)——这也是另一个阻止我学习“真正的”软件开发的原因)。设想将另一个程序员的代码复制到自己的BASIC程序中。除了需要知道被调用子程序的确切行号以及用于传递和返回值的确切变量名之外,你不能保证在其他人的代码中使用的行号和变量名与你已经使用的行号和变量名不同,如果恰好出现了相同的情况,这将导致冲突。BASIC需要行号的部分原因是,它原本的设计是针对可能没有任何文本编辑器的交互系统的。如果要在两个程序行之间插入一行代码,则必须选择介于这两个行号之间的一个数字作为行号。如果你选择了一个已经使用的行号,那么BASIC将用新的代码行替换旧的代码行,这意味着如果行号有冲突,那么加载其他人的代码将替17换你的部分代码。你编写的程序基本上只能依赖BASIC的内置API。如果一个程序确实加载了其他代码段,那么你必须精心设计,使得同一程序的这两部分的行号不会重叠,这与现代分层程序更常用的“调用某人编写的一个API实现”是完全不同的。

另外,作为BASIC程序员,你可能缺少为其他人定义一个清晰的API接口的实践。这些接口连接是程序中的脆弱点,因为API的接口名容易引起误解,API的调用者可能无法确切地了解API的功能,特别是如果API是由不同的人在不同的时间编写的(在大型程序中,实现底层API的源代码(未经编译的原始代码)对于调用者来说通常是不可见的)。在BASIC里,你不必考虑传递什么变量给子程序或子程序返回什么变量。任何子程序都可以读取或设置程序中的任何变量。甚至关于API命名问题也没有人讨论,因为只有行号可以标识子程序。

即使维护一个不依赖任何其他代码的独立程序,你仍然会遇到难读的代码。为IBM PC开发的操作系统MS-DOS包含了一些BASIC示例程序,这些示例程序用于演示该语言的功能,并给在商店挑选计算机的人们提供一些操作的试用机会。我记得有一家商店展示了IBM PC上可用的颜色图表(一共有16种),另一家商店则在计算机屏幕上连续不断地绘制随机大小的矩形来勾勒出一个“城市”的“天际线”——对于一个调用应用程序编程接口LINE、仅用大约10行代码写成的程序来说,这已经是很好的展示效果了。如果你传入合适的参数到18应用程序编程接口LINE,这段程序还可以画出填充的矩形。

大约一半的示例程序是由IBM编写的,其余的程序是由微软编写的。据提供IBM代码的格伦·达迪克(Glenn Dardick)说,他在手术后恢复的几天内编写了大部分代码。他的主要贡献是“音乐”程序,该程序可以播放11首不同的歌曲,包括《砰!去追黄鼠狼》(Pop!Goes the Weasel)、《胜利之歌》(Yankee Doodle Dandy)、约翰·施特劳斯的《蓝色多瑙河圆舞曲》(Blue Danube Waltz)和沃尔夫冈·阿玛多伊斯·莫扎特的《第40号交响曲》(Symphony#40)。该程序在播放的同时会在钢琴键盘示意图上显示相应的音符。达迪克邀请了一位家族朋友,纽约大都会歌剧院的音乐指挥理查德·沃伊塔赫(Richard 19Woitach)来帮他编写曲子。

在这些BASIC示例中,最著名的或者说最声名狼藉的,是《驴子》(DONKEY),也被称为“DONKEY.BAS”(“DONKET.BAS”是包含《驴子》的BASIC源代码文件名)。《驴子》是一个非常简单的游戏。游戏中屏幕显示了一条从屏幕顶部到底部的双车道公路,在屏幕底部有一辆赛车,赛车可以在左车道行驶,也可以在右车道行驶。一头驴会出现在其中一条车道上,从屏幕顶部向下移动,然后你使用空格键把车移到另一条车道上,以避免撞到驴。避开这头驴之后,另一头驴会出现在屏幕顶部的其中一条车道,玩家需要不断地躲避出现的驴子。这个游戏就是这样!网络上流传了该游戏的视频,但不要误解,我并不是在建议你观看这个视频。游戏的作者(比尔·盖茨也是游戏的作者之一)辩称,这个游戏只是在深夜编写的一个练习,目的是展20示高级BASIC的力量。游戏使用了应用程序编程接口DRAW和PLAY,演示了编写交互式游戏的基本知识,对于像我这种习惯于大型计算机的非交互式线路终端体验的人来说,这个游戏非常有指导意义。

我承认我也玩过几次《驴子》游戏,而且我是把它当成一款电子游戏作为娱乐消遣,而不仅仅是惊叹于它的简洁优雅。我的一个孩子在读本章的草稿时,找到了这款游戏的一个可以玩的版本,而且他发现自己很快就被这款游戏迷住了。每次成功避开一头驴,赛车就会移动到更靠近屏幕顶部的地方——我已经忘记了这个细节,然而正是这个细微之处唤醒了玩家的兴奋感。2014年,一款手机游戏《像素小鸟》(Flappy Bird)流行一时,但是与1981年的《驴子》相比,这款游戏的玩法几乎没有什么吸引力,控制也没有那么复杂。

得益于《驴子》游戏的出名,这款游戏的源代码被保存到了今天,所以我们今天仍然可以看到一个8位时代的BASIC程序长什么样

21子。它只有131行,其中前45行用于打印介绍性消息并确保你使用的硬件是正确的。游戏的核心逻辑很容易看懂,但是当你碰到了下面这两行,可能会皱一下眉头(在IBM PC BASIC中,GOSUB被写成一22个词):

这里没有上下文来帮你理解这些子程序的语义,即子程序的功能是什么,它们依赖于哪些调用之前需要设置的变量,以及它们在运行时修改了哪些变量。至少在Fortran中,类似的代码会这样写:

这段代码提供了关于接口用途的一点提示,即将驴子和汽车的图像加载到变量DNK和CAR中,以便在屏幕上更容易绘制(这是1940行和1780行的BASIC子程序所做的事情)。即使不能像Fortran那样编写代码,《驴子》的作者本来可以在BASIC代码行中添加注释:

接下来写1940行的代码:23

这里本不应直接跳转到这个序列中去画驴子,而是应该以注释行开始,说明其功能,比如

同时,三个字母的变量DNK和CAR是整个程序中使用的最长和最具描述性的变量。其他变量包括Q、D1、D2、C1、C2和B。请注意,BASIC允许的最长变量名可以达到40个字符。24

在DONKEY.BAS中的还有一行代码是这样的:

这是汽车和驴子的碰撞测试,CX和CY是汽车的屏幕坐标,DX和Y(为什么不是DY?原因不明)是驴子的屏幕坐标。考虑这些变量间的数学关系总是需要动点脑筋的,所以IF语句的复杂性是可以预料的(该IF语句的意思是,如果屏幕上的x坐标相同,表示汽车和驴子在同一条车道上,而屏幕上驴子的y坐标与汽车的y坐标差距在25之内,那么驴子的下边缘就与汽车的上边缘重叠,因此发生了碰撞)。代码令人困惑的原因是,发生碰撞时(IF判断的条件为true时)没有调用诸如名为SHOWEXPLOSION的API,而是跳转到第2060行(就是THEN 2060所做的操作)。同样,也没有注释来解释这些操作。如果你转去252060行想弄清楚要做什么,你会看到:

首先,给变量SD加1并没有提供多少有用的信息(SD保存了驴子撞车的次数,对于一直在读代码的人来说,它看起来像是“score-donkey”的缩写),然后LOCATE语句神秘地将光标移动到第14行第6列,但你会看到它打印了单词“BOOM!”你可能会猜到(特别是如果你玩过这个游戏的话),这就是碰撞代码。但是,必须在程序中来回跳跃,记住所有这些代码的含义,并且只能依靠可识别的PRINT语句来弄清楚你在看的代码,所有这些细节都使得代码很难阅读和理解。

你可以想象在另一个平行宇宙,MS-DOS系统附带的BASIC示例有通俗易懂的变量名和帮助理解的注释,人们可以利用这些优点来扩展游戏。我可能会写一个《超级驴子》(SUPERDONKEY)游戏(在屏幕上同时有三条车道和两头驴),而由于MS-DOS系统对文件名的限制,《超级驴子》游戏的源代码会被命名为SUPRDONK.BAS。在这个平行宇宙里,良好的代码习惯可以培养人们欣赏和编写易于理解的代码,我们(这里的我们指“受IBM PC BASIC启发,想在微软工作的一群人”,我是其中的一员)会继续从事自己的工作,尽管没人知道软件工程的未来将如何发展。但是现实是,代码很难阅读,平行宇宙里的事情也都没有发生。如今,那些准备开放源代码给公众的公司可能会担心人们对他们的变量名或代码布局提出批评,但在过去,人们显然没有这种担忧。在BASIC示例中,代码作者做出的唯一让步是在代码头部加入三行IBM版权信息。

BASIC代码的另一个来源是诸如《BASIC计算机游戏》(BASIC Computer Games)和《更多BASIC计算机游戏》(More BASIC Computer Games)等的书籍,这两本书都是由早期杂志《创造性计算》(Creative Computing)的创始人和发行人大卫·艾尔(David Ahl)编写的。书中包含了各种游戏的源代码,而玩这些游戏的唯一方法是自己按照书本键入正确的代码(或许有朋友已经写好了代码,这样的话你可用软盘或磁带复制他们的代码)。这个过程实际上有助于学习BASIC语言,因为键入代码时你有机会思考程序是如何工作的。

每台计算机都有自己的BASIC版本,有一部分原因是用于处理计26算机的特定功能,另一部分原因是BASIC语言还没有标准化。这种现象的影响是,书中的程序永远不会使用特定计算机的图形或实时交互,程序都是基于文本的,需要用户键入命令并按“回车”键进行交互,而且一次只能显示一行输出。这使得这些程序几乎适用于任何BASIC版本,无论是在IBM PC上运行的还是终端连接的。我现在意识到,我在麦吉尔大学的王安小型机上玩的《星际迷航》(Star Trek)游戏与《BASIC计算机游戏》中的《超级星际迷航》(Super Star Trek)游戏有着密切的关系,把它移植到王安小型机上的人已经对它进行了充分的修改,使游戏中的星图在完全以ASCII图形呈现的情况下显示在屏幕中央,比模仿行终端滚动要更好。

如果你的BASIC版本与上述书上的版本不同,那么可能只需要稍加修改就可以让游戏正常运行。例如,许多BASIC版本允许一行中有多个语句,用冒号分隔,该书就是利用了冒号,但有些版本则不允许,这时候需要改写一下。或者有的版本允许一行多个语句,但用反斜线27符号分隔。有的版本在处理字符串方面有些不同,尤其是用于提取字符串子集的API。IBM PC BASIC与该书上的BASIC基本一致,只是对生成随机数的代码需要做一些小的调整。这也并不奇怪,因为分别于1978年和1979年出版的这两本书使用的标准是微软BASIC,它不是在IBM PC上运行的版本,而是在早期的计算机上运行的,因为当时IBM PC还没有出现。这也提醒人们,在签订将MS-DOS销售给IBM的协议并由此确保了IBM的长期成功之前,微软作为一家销售编程语言的公司已经运行了好几年。事实上,上述第一本书的参考版本是微软的MITS Altair BASIC 4.0版,它是微软销售的第一款产品的后代。28

游戏的质量参差不齐。由于缺乏图形支持,其交互功能也有点欠29缺。下面是第一本书中关于《冰球》(Hockey)游戏的说明:

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载