围绕着软件开发的令人困惑的隐喻越来越多。 David Gries 说编写软件是一门科学( a science )( 1981 );而 Donald Knuth 说它是艺术( an art )( 1998 ); Watts Humphrey 则说它是一个过程( a process )( 1989 ); P. J. Plauger Kent Beck 都说它就像是驾驶汽车( driving a car )——可他们两个却几乎得出了完全相反的结论( Plauger 1993 Beck 2000 )。 Alistair Cockburn 说它是一场游戏( a game )( 2002 ); Eric Raymond 又说它就如同是一个集市( bazaar )( 2000 ); Andy Hunt Dave Thomas 说它就像园艺( gardening )一样; Paul Heckel 则说它就像是拍摄《白雪公主和七个小矮人》( 1994 );而 Fred Brooks 说它像耕田、像捕猎、或像是跟恐龙一起淹死在“焦油坑”里面( 1995 )……到底哪一个隐喻最好呢?
Software Penmanship: Writing Code
软件中的书法:写作代码
关于软件开发的最原始的隐喻是从“写作( writing )代码”这个说法发展出来的,这一隐喻暗示着开发一个程序就像写一封有缘由的信一样——坐下来,拿出文房四宝,从头写到尾就完了。这时不需要正规地做计划,你想到什么东西把它写出来就是了。
许多的想法就是从写作这个隐喻衍生而来的。比如 Jon Bentley 说,你应该可以坐在火炉边上,品一杯白兰地或抽一口上好的雪茄,边上坐着你心爱的猎犬,去品味一段“深奥的程序”,就像面对的是一本出色的小说那样。 Brian Kernighan P. J. Plauger 参考一本关于写作体裁的书《文体的要素》(《 The Elements of Style 》, Strunk and White 2000 [1] ,将他们关于编程风格( programming style )的书命名为《编程风格的要素》(《 The Elements of Programming Style 》, 1978 )。程序员们也经常会讨论“程序的可读性( readability )”。
对于个人规模的工作乃至小型的项目来说,这种写信的隐喻已经足够了,然而对于其他场合而言,这个隐喻还远远不够——它没有完整、充分地刻画软件开发工作。书写通常只是个人的活动,而一个软件项目多半会涉及承担许多不同职责的很多人。在你写完一封信之后,你只要把它塞进信封然后寄出去就完了,你再也不能修改它——从任何程度和目的上看,这件事情都已经结束了。而软件的修改没那么难,也很难说有真正完全结束的时候。典型的软件系统在其首次发布之后的工作量,可能达到整个工作量的 90% ,典型情况下也有三分之二之多( Pigoski 1997 )。对写作而言,最重要的是其原创性。但是对于软件构建来说,“努力创造真正的原创成果”的开发效率,往往低于专注于重用( reuse )以往项目的一些设计思想、代码以及测试用例( test case )的开发效率。总之,写作这一隐喻所暗示的软件开发过程太过简单、太过呆板了。

 
然而不幸的是,这种用文字(信件)写作所做的隐喻通过一本在软件领域最为流行的著作之一—— Fred Brooks 的《人月神话》(《 The Mythical Man-Month 》, Brooks 1995 )而变成了不朽的思想。 Brooks 说:“要计划抛弃一个,你必定会那样,无论如何。”这个咒语给了我们一幅如图 2-1 所示的景象:被扔进纸篓里的成堆的半成品草稿。
 p_w_picpath001.jpg
2-1  文字写作这一隐喻暗示着软件开发过程是一种代价昂贵的试错( trial and error )过程,而非仔细的规划和设计
在给你叔叔写一封“最近好吗”这样的礼节性问候信时,“计划扔掉一张草稿”也许还比较实际。但如果将“书写软件 ”这个隐喻引申为“计划扔掉一个(软件)”,则不是一个好的建议——尤其是在软件的主要系统就已经花费了相当于一栋十层高的办公楼或一艘远洋客轮这么多成本时。要想中奖不难,只要你能忍受坐在你钟情的旋转***上转上无数圈就行。诀窍在于当作第一次尝试的时候就让它成功 ——或者在成本最低的时候多试几次。其他一些隐喻更好地阐明了达到这个目标的途径。
Software Farming: Growing a System
软件的耕作法:培植系统
相对于前面那个呆板的用写作所做的隐喻,一些软件开发人员则认为应当将创造软件想象成类似播种和耕作的情形。你一次设计系统的一小部分、写出一段代码、做一点测试,并将成果一点点添加到整个系统中。通过这种小步前进,你可以把每次可能遇到的麻烦减到最小。
有时候人们会用很糟的隐喻去描述一种很好的技术,此时需要保全这一技术,并去寻找更好的隐喻。这个例子里的增量技术是很有价值的,但把它比作播种和耕作却非常糟糕。
“每次做一点”这个主意可能在某些方面与农作物生长类似,但把软件开发类比为耕作就很不贴切,也没有太多意义,而且我们很容易用下面即将介绍的更好的隐喻替代它。人们也很难把耕作这个隐喻引申到“一次做一点事情”之外。如果你认同耕作这种隐喻,就请想象一下图 2-2 的情况:你会发现自己谈论的是:对系统计划施肥、对细节设计疏果,并通过有效的管理土地来增加代码的产量,最终取得代码大丰收。你还会说“轮种 C++ 和大麦”,或者让土地闲置一年以增加硬盘里面氮肥的供应量。
软件耕作这一隐喻的弱点在于它暗示了人们将无法对开发软件的过程和方式进行任何直接的控制。你在春天播下代码的种子,然后按照农历节气向土地佬儿许几个愿,你将会在秋天收获到丰盛的代码。
p_w_picpath002.jpg
2-2  很难将耕作这一隐喻恰当地引申到软件开发领域
Software Oyster Farming: System Accretion
软件的牡蛎养殖观点:系统生长
在谈论培育( growing )软件的时候,有时人们实际上是指软件的生长( accretion ),这两种隐喻是紧密相关的,而软件生长是一幅更发人深省的景象。看到“生长”这个词,就算手头没有字典,我们也都能明白它指的是通过外在的增加或吸收而逐渐地生长或变大。“生长”这个词描述了牡蛎制造珍珠的过程,逐渐地增添微量的碳酸钙。在地质学里,“ accretion ”一词的意思是“冲积层”,指的是水流中夹带的沉淀物的冲积而不断扩大的陆地。在正式的术语中,“冲积层”是指海岸沿线的陆地因受到水流冲击,水中夹带的物质不断沉积而形成的增长。
这里并不是说要你学会如何从水流中夹带的沉积物中提炼出代码来,而是说你需要学会如何一次为软件系统增加一个小部分。跟“生长”密切相关的另一些词语有:“增量的( incremental )”、“迭代的( iterative )”、“自适应的( adaptive )”以及“演进的( evolutionary )”。以增量方式进行设计、编译和测试,都是目前已知的最强有力的软件开发概念。
在进行增量式开发时,我们先做出软件系统的一个尽可能简单、但能运行的版本。它不必接受真实的输入,也无须对数据进行真正的处理,更不用产生真实的输出——它仅仅需要构成一个足够强壮的骨架,支撑起未来将要开发的真实系统。对于你标志出的每一项基本功能,可能仅需要调用虚假的类( dummy classes )。这个最基本的起点,就像牡蛎开始孕育珍珠的那颗细小沙粒。
在骨架形成之后,你要一点点地在其上附着肌肉和皮肤:把每个虚假的类替换为真正的类;不再假装接受输入,而是把接收真实输入的代码替换进去;不再假装产生输出,而是把产生真实输出的代码替换进去。你一次增加一小部分代码,直到得到一个完全可以工作的系统。
支持这一方法的一件逸事或曰证据令人印象深刻。那位在 1975 年建议我们建造一份(软件)以备扔掉( building one to throw away )的 Fred Brooks 说,在他写完了里程碑式的著作《人月神话》之后的十年间,没有什么能像增量式开发那样彻底地改变了他个人的开发习惯及其效力( 1995 )。 Tom Gilb 在他突破性的著作《软件工程管理原理》( The Principles of Software Engineering Management 1988 )中也同样指出了这一点,该书介绍了演进式交付( Evolutionary Delivery ),它在很大程度上奠定了如今敏捷编程( agile programming )方法的基础。眼下不少方法论都是基于这一理念( Beck 2000 Cockburn 2002 Highsmith 2002 Reifer 2002 Martin 2003 Larman 2004 )。
作为一个隐喻而言,增量式开发的优势在于未做过度的承诺。比起耕作那个隐喻来,对它作不恰当地引申要更困难一些。牡蛎孕育珍珠的图景也很好地刻画了增量式开发(或说生长)的情形。
Software Construction: Building Software
软件构建:建造软件
与“写作( writing )”软件或者“培育( growing )”软件而言,“建造( building )”软件的图景就更加有用了。它和软件生长的概念是相通的,且提供了更详细的指引。建造软件这一说法暗示了软件开发中存在着诸多阶段,如计划、准备及执行等,根据所建造软件的不同,这些阶段的种类和程度可能会发生变化。进一步研究这一隐喻时,你还会发现许多其他方面的相似之处。
要搭一座四足的塔( four-foot tower ),你要有一双稳健的手,要找一个平坦的表面,以及十来个完好无损的啤酒罐。而要搭一座比它大 100 倍的塔,光是多 100 倍的啤酒罐还不够,还需要同时采用完全不同的计划方法和建造方法才行。
如果你要盖一个简单的建筑物——比如一个狗屋——你先开车到木材店买些木头和钉子。临近傍晚时分,你的爱犬 Fido 就有新窝了。如果你像图 2-3 那样忘了弄个门,或是犯了其他什么错误,那也没什么大不了的,修改一下或者干脆从头再来就是了。你的损失最多也就是一个下午的时间。这种宽松的方式对于小型的项目来说也还算合适。如果你写 1 0 00 行的代码时采用了错误的设计,你还可以重构甚至从头再来,不会损失太多。

 
p_w_picpath003.jpg
2-3  在简单结构上犯下错误,其惩罚也不过是一点时间,或是些许尴尬
如果你是在建一栋房子,那么这个建造过程就会复杂得多,而糟糕的设计所引发的后果也更严重。首先你要决定准备建一个什么类型的房子——在软件开发里的类似事项称为问题定义( problem definition )。接下来,你必须和某个建筑师( architect )探讨这一总体设计,并得到批准。这跟软件架构设计( architectural design )十分相似。然后你画出详细的蓝图,雇一个承包人。就像软件的详细设计。再然后,你要准备好建造地点,打好地基,搭建房屋框架,砌好边墙,盖好房顶,通好水、电、煤气等。这就如同是软件的构建( construction )一样。在房子大部分完成之后,庭院设计师、油漆匠和装修工还要来把你新盖的家以及里面的家什美化一番 这就好比 软件的优化( oprimization )过程。在整个过程中,还会有各种监查人员来检查工地、地基、框架、布线以及其他需要检查的地方。这相当于软件复查(评审, reviews )和审查( inspections )。
在这两种活动中,更高的复杂度和更大的规模都会带来更多的结论。盖房子的时候,建材多少也是有些昂贵,但主要的开销还是在人力上。把一栋墙推倒然后移动半尺是很昂贵的,倒不在于浪费多少钉子,而是因为你要付给工人们更多的工钱,移动这堵墙耗费了额外的工时。你只有尽可能地把房子设计好,就像图 2-4 那样,这样你才不用浪费时间去修正那些本来可以避免的错误。在开发一个软件产品时,原材料甚至更加廉价,但劳动力上的花销也更昂贵。变更一份报表的格式所要付出的代价,和移动房间里的一堵墙一样高昂,因为两者的主要成本构成部分都是花费人的时间。

 
p_w_picpath004.jpg
2-4  更复杂的结构需要更加仔细地规划
除此之外,这两种活动还有什么相似之处呢?建造一个房子的时候,你不会去试着建造那些能买得到的现成的东西。你会买洗衣机、烘干机、洗碗机、电冰箱以及冷藏柜。除非你是机电方面的巫师,否则你是不会考虑自己动手弄这些东西的。你还会购买预先造好的橱柜、餐桌、门窗以及浴具,等等。当开发软件时,你也会这么做的。你会大量使用高级语言所提供的功能,而不会自己去编写操作系统层次的代码。你可能还要用些现成的程序库,比如说一些容器类( container classes )、科学计算函数、用户界面组件、数据库访问组件,等等。总之,自己编写那些能买得到的现成的代码通常是没有意义的。
但如果你要建造一间拥有一流家具的高档住宅,那你可能需要特别订制的橱柜,还可能需要能和这些橱柜相搭配的洗碗机、冰箱和冷藏柜等,也可能需要以特殊的形状和特别尺寸订制的窗户。在软件开发中也有和这种订制相似的情况。如果你要开发一款一流的软件产品,你可能会自己编写科学计算函数以便获得更快的速度和更高的精度。你还可能需要自己编写容器类、用户界面组件以及数据库访问组件等,这样做可以让产品的各个部分无缝拼接,拥有一致的外观和体验。
适当的多层次的规划对于建造建筑物和构建软件都有好处。如果你按错误的顺序构建软件,那么编码、测试和调试都会更难。需要花更长的时间才能完成,甚至整个项目干脆就分崩离析了——由于每个人的工作都过于复杂,所有成果组合在一起的时候就变得混乱不堪了。
精心计划,并非意味着事无巨细的计划或者过度的计划。你可以把房屋结构性的支撑( structural support )规划清楚,而在日后再决定是用木地板还是地毯,

 
墙面漆成什么颜色,屋顶使用什么材料,等等。一项规划得当的项目能够提升你“在后期改变细节(设计)”的能力。你对同类软件的开发经验越丰富,(在开发新软件时)就能认准更多的细节。你只需要保证已经做了足够的计划,不会到后来因为计划上不足而引发重大问题。
用建筑房屋来类比软件构建,还有助于解释为什么不同的软件项目能从不同的开发方法中获益。建筑业中,盖间仓库或者工具房,或是一座医院或者核反应站,你在规划、设计及质量保证方面所需达到的程度是不一样的。盖一座学校、一幢摩天大楼,或一座三居室的小别墅,所用的方法也不会相同。同理,在软件开发中,通常你只需要用灵活的、轻量级的( lightweight )方法,但有时你就必须得用严格的、重量级的开发方法,以达到所需的安全性目标或其他什么目标。
软件的变动在建筑领域也有类似事物。把一堵承重墙移动半尺所需花费的成本,肯定要比仅仅移动一面隔墙更高。同样,对软件进行结构性的修改所需花费的成本,肯定也比仅仅增删一些周边功能更高。
最后,建筑这一隐喻让人们对超大型的软件项目的认识更加深刻。超大型的结构一旦出现问题,后果将非常严重,因此有必要对这样的结构进行超出常规的规划与建设( over-engineered )。建筑人员需要非常小心地制定并核查设计规划,在建设时留有余地以保障安全;宁可多花 10% 的成本买更坚固的材料,也比摩天大楼倒下来要划算得多。还需要特别关注工作的时间。在建造帝国大厦( The Empire Building )的时候,每辆运料车运输时都留有 15 分钟的余地。如果某辆车没能在指定时间到位,则整个工期就会延误。
同理,对于超大型的软件项目,就需要比一般规模的项目有更高级别的规划设计。 Capers Jones 发表的报告称,一套 100 万行代码的软件系统,平均需要 69 文档( 1998 )。其需求规格文档一般有四五千页长,而设计文档常常是需求的两三倍长。不太可能有哪一个人能完全理解这种规模的项目的所有设计细节——甚至只是通读一遍都不那么容易。因此,更充分的准备工作也就理所应当了。
如果需要创造在经济规模上可以匹敌帝国大厦的庞大的软件项目,那么与之相当水准的技术与管理控制也是必需的。
按房屋建筑所作的这一隐喻,可以向许多其他方向引申——这也是隐喻这一方法如此强有力的一个原因。有很多常见的软件开发术语都是从建筑这一隐喻中衍生出来的:软件架构(建筑学, architecture )、支撑性测试代码(脚手架, scaffolding )、构建(建设, construction )、基础类( foundation classes )以及分离代码( tearing code apart )。你可能还听说过更多这一类的词语。

 
 
p_w_picpath005.jpg
 
应用软件技术:智慧工具箱 
能有效地开发高质量软件的人们,在长年累月中积累了大量的技术、技巧和诀窍。技术并不是规矩( rule ),它只是分析工具( analytical tools )。好的工匠知道完成某项工作要用哪样工具,也知道该怎样正确地使用。程序员也该这样。编程方面的知识学得越多,你脑中的工具箱中就会有更多的分析工具,也会知道该在何时用这些工具,以及怎样正确地使用它们。
在软件领域里,专业的咨询人员有时会让你专用某种软件开发方法而远离其他方法。这样并不妥当,因为当你百分之百地依赖于某一方法论时,你就只会用一种方法去看世界了。某些情况下,对于你所面临的问题还有其他更好的方法,你可能错失良机。这种“工具箱隐喻”能够帮助你把所有的方法、技术以及技巧留在脑海中——合适的时候即可拿来就用。
Combining Metaphors
组合各个隐喻
因为隐喻是一种启发式方法而不是算法,因此它们彼此并不排斥。你可以同时使用生长( accretion )和建筑( construction )这两个隐喻。你如果想用“写作”隐喻也行,你还可以把“写作”同“驾驶”、“狩猎狼人( werewolf )”、“与恐龙一起在焦油坑中淹死”等隐喻组合到一起。你可以选用任何一种隐喻或是一些隐喻的组合,只要它能激发你的思维灵感,并让你和团队其他成员更好地沟通。
使用隐喻又是件说不清楚的事情( fuzzy business )。你需要适当地引申它的含义,才能从其蕴含的深刻启发中受益。但若你过分地或者在错误的方向上引申了它的含义,它也会误导你。正如人们会误用任何强大的工具一样,你也可能误用隐喻,但它的强大的功效,还会成为你智慧工具箱中的一个宝贵部分。
Additional Resources

更多资源

在关于隐喻、模型( model )以及范型( paradigm )方面的众多书籍中, Thomas Kuhn 写的那本是试金石。
Kuhn, Thomas S. 《科学变革的结构》(第三版)( The Structure of Scientific Revolutions , 3d ed. Chicago, IL: The University of Chicago Press, 1996. )。 Kuhn 关于在一个达尔文周期中,科学理论如何相对于其他理论而诞生、发展并消亡的书,于 1962 年首次发布,奠定了科学哲学的基础。该书短小精悍,列举了大量科学中隐喻、模型以及范型间此消彼长的有趣示例。
Floyd, Robert W. “编程范型”(“ The Paradigms of Programming. 1978 年图灵奖的颁奖演讲)。《 Communications of the ACM 》(《 ACM 通讯》), August 1979, pp. 455 460. 这是一篇令人神往的关于软件开发中的模型的讨论, Floyd Kuhn 的理念应用到了编程上。
Key Points

要点

■         隐喻是启示而不是算法。因此它们往往有一点随意( sloopy )。
■         隐喻把软件开发过程与其他你熟系的活动联系在一起,帮助你更好地理解。
■         有些隐喻比其他一些隐喻更贴切。
■         通过把软件的构建过程比作是房屋的建设过程,我们可以发现,仔细的准备是必要的,而大型项目和小型项目之间也是有差异的。
■         通过把软件开发中的实践比作是智慧工具箱中的工具,我们又发现,每位程序员都有许多工具,但并不存在任何一个能适用于所有工作的工具,因地制宜地选择正确工具是成为能有效编程的程序员的关键。
■         不同的隐喻彼此并不排斥,应当使用对你最有益处的某种隐喻组合