编程大师访谈录
尝试从不同的角度看待问题:
在某些情况下,如果你倒着做事情,之前显得很复杂的问题突然之间会变得非常简单。例如,解析前向引用(forward reference)可能很难。要是倒着扫描,它们就变成了后向引用(backward reference),很容易解析。只要从新的角度看待程序,原本可能很难解决的问题也会变得容易解决。
有一个适合自已与团队的命名方法,例如“匈牙利命名法”:
命名规范其实是要让代码更易读。这套规范能够很好地控制程序中所有变量的命名。
要是分解一个程序,把它放进磨床,然后对碎片进行分类,你就会发现程序的大部分都是名字。写下“apples + oranges”,你会发现名字“apples”有6个字符,运算符“+”只有1个字符,名字“oranges”有7个字符,一共14个字符,只有一个字符即加号与运算有关。因此,对我来说,要起到作用或有所改进,合乎逻辑的做法就是尽力完善程序的主要部分,也就是名字。“匈牙利命名法”是一种根据变量的属性自动为其创建名字的命名方法。这跟人们把当裁缝(tailor)的叫做泰勒(Taylor)以及把当铁匠(blacksmith)的叫做史密斯(Smith)非常相似。
因此,面对一个具备某些属性的结构,不要随随便便地取个名字,然后让所有人去琢磨名字和属性之间有什么关联,你应该把属性本身用作结构的名字。这种方法有很多优点。首先,造个名字很容易,想到那些属性时,把它们写下来,名字自然就有了。第二,它很容易理解,因为当你读到某个变量时,从名字本身就能了解到与属性有关的大量信息。这些属性会越来越多,因此很难简明地描述它们。为此“匈牙利命名法”引入了一种缩写符号,以很小的空间就能展现具体属性。当然,这在不知情的人看来完全是一团乱麻,那个玩笑就是这么来的。
有些人认为,如果他们可以读出代码里的每个字,那么程序就是可读的。实际上,这种意义上的可读性并不可取。没有人会拿着代码清单,站到演讲台上大声朗读程序。关键在于理解。只是能阅读单词并发出音来,这毫无用处。当人们看到采用“匈牙利命名法”的代码清单时,他们发现这些词很难念,可能就会认为代码不是可读的。但实际上,由于名字和属性之间存在关联,它更容易理解,也更便于沟通。那些使用匈牙利命名法编程的人,即使在离开我的部门之后,仍会继续使用它。这种命名法已经打入苹果电脑、3Com及其他许多公司。
怎样创建程序:
对编程而言,我认为我们应该知道自己想要做什么。如果不知道,那么有一个过程确实是解决各种问题的必经之路,那就是要弄清楚:我试图做什么?目标是什么?
打个比方,我想开发一个菜单驱动的文本编辑器,要求响应速度快,并且提供拼写检查器等。在开始真正编程之前,我需要先弄清楚最终产品。有时候,目标的选择取决于我都掌握了哪些技巧。以Bravo为例,这个程序是以算法为导引的。巴特勒·兰普森描述了两个很有意思的算法,于是我们试图围绕这些算法来编写这个编辑器,以充分利用这些算法。此外,J. 斯特罗彻·摩尔(J. Strother Moore),就是Boyer-Moore字符串查找算法的Moore,在文档编辑方面有几个很有意思的算法。于是我们决定:“嘿,这个编辑器要包含摩尔编辑算法、兰普森的屏幕更新算法还有两个缓存。”等到对目标有充分的把握之后,我才会开始真正的编程。我调整姿态,关上房门,并且大声宣布:“现在我要开始编程了。”
第一步:
编程的第一步是想象。就是要在脑海中对来龙去脉有极为清晰的把握。在这个初始阶段,我会使用纸和铅笔。我只是信手涂鸦,并不写代码。我也许会画些方框或箭头,但基本上只是涂鸦,因为真正的想法在我脑海里。我喜欢想象那些有待维护的结构,那些结构代表着我想编码的真实世界。
一旦这个结构考虑得相当严谨和明确,我便开始写代码。我会坐到终端前,或者换在以前的话,就会拿张白纸,开始写代码。这相当容易。我只要把头脑中的想法变换成代码写下来,我知道结果应该是什么样的。大部分代码会水到渠成,不过我维护的那些数据结构才是关键。我会先想好数据结构,并在整个编码过程中将它们牢记于心。
*******这是最重要的一步
最优算法的知识当属科学,结构的想象则是艺术。这些算法的细节,以及编写高效代码实现这些结构的转换,是编程像手艺活的一面。从技术上讲,这就是所谓维护结构的不变性。编写代码以维护不变性是相对简单的技艺,不过这需要非常用心并辅之以大量训练才能练就。
如何求解问题:
求解问题时,我们遵循以下四个步骤:首先理解问题,然后拟定计划,接着执行计划,最后回顾整个过程。
必须学会的最重要的原则是什么?
解决问题以及如何学习。知道如何学习能够让你顺利通过考试并获得其他的校园生存技能。而如果能学会如何解决问题,你这一生过得应该都会不错。
提高编程能力
通过数学,你学会逻辑推理。你还会学到如何证明,以及怎么处理抽象要素。通过物理学等实验科学或人文学科,你学会如何应用这些抽象在现实中建立联系。我相信在数学、英语和基础科学方面拥有坚实的基础极为重要。
你需要学习其他人的工作。他们解决问题的方法和他们使用的工具会让你以一个新的视角来审视自己的工作。在写程序前只需要学习一组为数不多的程序模块。例如,要写编译器,首先要写的是扫描功能,那是一个会用到很多次的小工具。一旦学会了这些工具,剩下的工作就只是把它们组合在一起。这里弄点儿、那里弄点儿,把这些功能模块都放到一起。查看其他人编写的程序可以为你提供构建条理清晰的代码的新思路。
你在设计系统时要力求简单吗?
控制复杂性有一些基本技巧。从根本上,要分而治之,把事情分解开,并准确描述各个部分应该实现什么功能。这会变成接下来如何行事的纲要。如果你还没想清楚怎么写规格,那表明你不明白具体是怎么回事。接着,你有两种选择:要么退回到你真正理解的另外某个问题上,要么更努力地思考。
此外,系统的描述不应该太庞大。也许你必须从多个较小部分的角度来考虑一个大系统。这有点像解数学题:你写的书可以包含许多有用的提示,但不能直接给出算法。
你在设计或开发程序时都会历经哪些过程?
大部分情况下,新程序就是现有程序的精炼、扩展、泛化或改良。开发全新程序的机会真是少之又少。通常,我会对现有程序或用户界面中的既有模型做些扩展或改进,然后放进新程序。
我会先画数据结构,然后花很长时间思考数据结构。在开始编程前,我会一直思考整个程序的流程。
采访者:编写好程序有什么技巧?
奥奇:我推崇严谨、一致而清晰的结构。另外,我认为软件应该高度模块化和分层,非常灵活地运用大量文件和目录。如果你必须分别构建不同组件,那么接口自然就会更加凸显出来,要求你规范这些接口。
当许多人共同开发一个程序时,在项目早期确立全局的错误处理、参数传递和子程序命名规范(虽然不见得所有人都会同意),显得非常重要。不过,对于代码该怎么注释、大括号怎么用或者代码该怎么缩进,我觉得你绝不应该对别人指手画脚。在修改别人的模块时,你最好还是按照他的惯例编写代码。这是学会与他人共事的必修课。
交流想法的环境应该是开放的。设计会议上允许非常激烈和紧张的讨论。我发现许多优秀设计师往往固执己见,但只要他们觉得是对的,就会毫不客气地坚持,但他们也知道什么时候应该怎么让步。优秀设计师不会有“非此创造”综合征 [1] 。
杰夫·拉斯金、戴维·索恩伯格(David Thornburg)和泰德·尼尔逊(Ted Nelson)。他们这三人崇尚简洁——嗯,他们真的是崇尚简洁。当他们说到简洁时,他们是认真的。例如,他们认为5是最大的数字,你能够拥有的东西数量不能超过5。
戴维·索恩伯格是把这一观点写得最清楚的。他写了一本小书,名叫《零设计》(Zero Mass Design),如果你要去做什么事情,那前提是从一个非常简洁的设计开始,不论是打算写一本书,准备编写一个软件,还是即将开始一个需要规划的项目,开始的时候都要有一个非常简洁的设计。更极端的做法是,开始时的设计过于简单,让后面干不下去了。这需要大量的训练,因为你进入项目时就假定项目会失败。除非试过了,亲眼看到它的失败,否则你不会知道到底自己能达到多么简洁。
索恩伯格的例子,是来自詹姆士·亚当斯(James Adams)的《创意人的思考》(Conceptual Blockbusting)一书中的“水手四号”火星航天探测器。它有个很大的、能展开的太阳能电池板。而有个问题就是,在电池板展开时,必须有一个机械装置来减缓电池板的速度,这样才不会被毁坏。于是他们尝试使用油,但搞得乱七八糟的,他们又试了弹簧,试了各种各样的东西。可是离发射的那一天越来越近了,还没找到合适的。他们该怎么办呢?请记住,问题是找到一种方法来减缓太阳能电池板的展开,或者找一个制动的机械装置。最后,有人出了一个高招,什么都不做,他们就直接打开了,太阳能电池板颤抖着,但没有断裂。如果你假设自己会遇到问题,你就会碰到问题。你必须简化、简化再简化。开始时要有一个非常简洁的设计,这会带来巨大的优势。但仍需要一个心理上的调整,你必须要有它会失败的心理准备,并坦然接受失败。

浙公网安备 33010602011771号