隐喻
代码大全
第二章 通过隐喻更充分地理解软件开发
一、隐喻的重要性
使用隐喻,可以让我们更深刻和快速的理解软件开发过程:
- 如果我们把一个不太熟悉的主题和一个比较好理解且类似的主题进行比较,就可以更好的理解这个不熟悉的主题;这种使用隐喻的方法称为“建模”;比如我们将代码中的错误称为“bug”即虫子的意思,说明代码中出现了不好的东西,就和吃的东西生了虫子一样令人不适;
- 模型的威力在于它们很形象,可以让人理解整个概念;比如人们通过以声波理论为模型研究光的波动理论,通过类比相似性进行研究;
- 没有最好的隐喻,只有更贴切的隐喻,我们要把“不太合适的”隐喻变为“更好”的隐喻;但被更好模型所取代的旧模型任然有用,例如尽管牛顿力学在理论上已经被爱因斯坦的理论所取代,但是工程师们依然用牛顿定律来解决大部分工程问题;
- 与其他领域相比,软件开发还是一个很年轻的学科,它还没有成熟到自有一套标准隐喻的程度,因此必然存在很多或许相互抵触的隐喻;某些隐喻好一点,而另一些差一点;对隐喻的理解程度决定着每个人对软件开发的理解程度;
2、如何使用软件隐喻
软件隐喻更像探照灯而不是路线图,它不会告诉你可以在哪里找到答案而是告诉你如何寻找答案;隐喻的作用更像是启发,而不是算法;
- 启发式方法是一种帮助人们寻找答案的技术;它给出的答案是偶然性的,因为它只告诉你如何查找,并不会告诉你要找什么;例如你想从A点到B点,它并不会告诉你如何直接从A点到达B点,它甚至可能连A点和B点在哪里都不知道;实际上,启发式方法是一种看似有趣的算法,它不好预测,但更有趣;
- 就本书的目的而言,算法和启发式方法的区别主要在于解决方案的间接程度;算法直接给出解决问题的指令,而启发式方法则告诉你该如何发现这些指导信息或者至少可以在哪里找到指导信息;
- 如果有指令可以准确告诉你如何解决编程问题,那么编程当然更容易,结果更可预测;但编程科学还没有那么先进,也许永远不可能那么先进;对编程而言,最有挑战性的是将问题概念化,编程中的许多错误都是概念性的错误;因为每个程序在概念上都是唯一的,所以在每种情况下都能找到解决方案,但要创建一套可以解决所有问题的通用指导规则,很难甚至不太可能;因此,知道能以一般的方式来解决问题,其价值至少也相当于知道以特定的方案来解决特定的问题;
- 如何使用软件中的隐喻呢?用它们来提高我们对编程问题和编程过程的洞察力;用它们来帮助我们思考编程中的活动,并帮助我们想象出更好的做事方式;我们不能看到一行代码就说它有悖于本章描述的某个隐喻;然而随着时间的推移,与那些不善于运用隐喻的人相比,人们普遍认为,那些使用隐喻来指明软件开发过程的人明显更能理解编程并能更快地写出更好的代码;
3、常见的软件隐喻
软件中的“书法”:编写代码
- 关于软件开发,最原始的隐喻就是编写代码;这个写作隐喻告诉我们,开发程序就像写信,坐下来,拿出笔墨纸砚,从头写到尾就好了。不需要任何正式的计划,可以想到什么东西就写什么;
- 对于小型的项目或者个人的工作,写信的隐喻已经足够贴切了。但对于其他场景,这个隐喻还远远不够,也不够恰当,并没有完整、充分地刻画出软件开发的所有工作;写代码通常是一个人的活动,而一个软件项目多半涉及许多不同职责的人;写完一封信后,只要把它塞进信封后寄出去就算是完成了,不能再修改了,因为从任何程度和目的上看,这件事情实际上已经结束了。而软件的修改并不难,而且很难说有真正完全结束的时候。典型的软件系统在首次发布后的工作量可能占整个工作量的90%,典型情况下,也有三分之二;就写作而言,最重要的是其原创性受到高度重视。但在软件构建中,试图创建真正的原创工作通常不如专注于重用以往的项目中的一些设计思想、代码和测试用例有效;简而言之,写作这个隐喻锁暗含的软件开发过程过于简单和僵化,不利于理解;
软件如同牡蛎养殖:系统生长
- 在谈软件开发时,有时实际上是指软件“生成”,这两个隐喻密切相关,但软件的“生成”是对未来更有洞察力的描述;生长这个词的意思是通过外部增加或者吸收而逐渐地生长或变大。这个词描述了牡蛎通过逐渐加入少量碳酸钙而形成珍珠的过程;这并不是说必须学会如何从水流中夹带的沉积物中提炼出代码。而是说需要学习如何向软件系统一次少量添加一部分。其它与“生成”密切相关的词汇有“增量的”“迭代的”“自适应的”“演进的”。以增量方式进行设计、编译和测试,都是目前风头正盛的软件开发概念;
- 在进行增量开发时,首先要做出尽可能简单但能运行的软件系统版本。它不需要接受真实的输入,也无需对数据做真正的处理。更不用产生实际的输出。它只需要有一个最够强壮的骨架,在开发过程中能够支撑未来真实的系统。对敲定的每个基本功能,可能只需要调用虚拟的类。这个基本的开始,就像牡蛎开始从那颗小沙粒孕育珍珠;在形成骨骼之后,一点点地添加上肌肉和皮肤:可以将每个虚拟的类更改为真正的类:不再让程序假装接收输入,而是接收真实输入的代码;不需要让程序假装产生输出,而是产生真实输出的代码。一次添加一小部分代码,直到得到一个完全可以工作的系统;
- 作为隐喻,增量开发的优势在于,不做过度承诺。牡蛎孕育珍珠的画面也生动地刻画了增量式开发(或生长)。
软件构建:构建软件
- 与“编写”写出来的软件或“培育生成”的软件相比,“构建”出来的软件更形象。它兼容了软件“生成”的思想,并提供了更详细的指导。构建软件,意味着软件开发包含很多阶段,例如计划、准备和实现等,这些阶段的类型和程度随着构建的内容而发生变化;
- 如果建一个简单的建筑物,比如一个狗屋,那么可以在木材城买些木板和钉子,一下午的时间,狗屋就能搭好。如果忘了给狗屋弄个门或犯了其他错误,那么没什么大不了,稍微修改一下就好了。如果对500行的代码使用了错误的设计,那么可以完全重构或者从头再来,并没有多大损失;
- 如果是建房子,过程就会复杂的多,因为糟糕的设计也会带来严重的后果。首先,必须确定要建什么样的房子,这类似于软件开发中的问题定义。其次,必须和建筑师探讨总体设计并通过审核,这类似于软件架构设计。再次,画出详细的蓝图,然后雇一个承包商。这类似于详细的软件设计。最后,确定地点,打地基,甲房屋,安墙板和房顶,接通水、点、煤气等。这类似于软件构建,房子大部分完工后,庭院设计者、油漆工和装修工进场。这类似于软件的优化过程。在整个过程中,各种监理来检查现场、地基、框架、布线和其他需要检查的地方。这类似于软件评审和审查;在这两个活动中,复杂性更大和规模更大,出现问题产生的后果也更大。在盖房子时,建材有些贵,但主要还是人工费用,拆掉一堵墙再移动15厘米很贵,不是因为浪费了多少材料,而是因为必须花钱请工人额外花时间移动这堵墙。开发软件产品时,原材料甚至更便宜,但劳动力成本依然高。改一个需求与移动房屋中的墙一样贵,因为这两种情况下的主要成本都是人工费用;这两个活动还有哪些相似之处呢?在建房子时,不会试图自己去做可以直接买的成品,比如洗衣机和电冰箱之类的。如果正在开发软件系统,我们会广泛的使用高级语言提供的特性,而不是自己写操作系统层的代码,还会使用现成的程序库,不比如一些科学函数和一些用户界面类。总之,如果可以买到现成的,通常就不值得亲自动手写代码。然而,如果正在建一栋拥有一流家具的高档住宅,那么里面的家具就需要定制,不能买普通的家具。在软件开发中,也有类似这种定制。如果正在开发一流的软件产品,那么可能需要自己动手写科学函数,获得更快的速度或者更高的准确度。
- 建造房子和软件,都得益于适当的多层级的规划,如果以错误的顺序构建软件,那么编码、测试和调试都会很难。可能需要更长的时间才能完成或者整个项目早就“玩儿完了”,因为每个人的工作都太复杂,导致所有工作集成在一起的时候就会变得混乱不堪;精心规划不代表巨细靡遗的规划或者过度的规划。可以把房屋的结构承重规划清楚,然后再决定墙上图什么颜色,屋顶用什么材料等等。一个精心规划的项目可以提高后期改变细节的能力。对于正在构建的软件,经验越多,越了解更多的细节,只要保证规划足够充分,就不至于到后来因为规划不足而产生大的问题。
- 用建筑房屋来比喻构建软件,也有助于解释为什么不同的软件项目受益于不同的开发方法。在建筑中,如果是盖仓库或工具棚,完全不同于建医疗中心或核反应堆。就需要完全不同的规划、设计和质量保证。同样在软件开发中,可能通常只用灵活的、轻量级的开发方法。但有时必须得用严格的、重量级的方法来实现系统所需的安全目标和其他目标。
- 对软件进行变更,会带来另一个与建房子类似的问题。与移动隔墙相比,对软件进行结构性的更改,在费用上远远高于添加或删除一些外围的功能。
- 最后,建筑这个隐喻让我们对超大型项目有了更深刻的洞察。超大型结构一旦被破坏,后果就非常严重,因此要对这样的结构进行超出常规的工程规划。建筑人员仔细制定和检查计划,在建设时留有余地以保障安全,宁肯多花10%的成本买更坚固的材料,也胜过建成的摩天大楼最后倒塌。同样,与一般规模的项目相比,超大型的项目需要更高层次的规划设计。一套100万行代码的软件系统,平均需要69种文档,这种系统的需求规范通常有4000~5000行的篇幅,设计文档的篇幅是需求文档的两倍。个人不可能理解这种大规模项目的所有设计细节,甚至只是通读一遍都不太容易。因此,更充分的准备工作也是理所当然。
- 房屋建筑隐喻可以扩展到很多方向,这就是隐喻方法如此强大的原因。
应用软件技术:智慧工具箱
- 擅长开发高质量软件的人,多年来积累了大量的技术、技巧和诀窍。技术不是“铁律”,它们只是分析工具。能工巧匠知道如何使用合适的工具。程序员也一样,编程方面的知识学得越多,思维工具箱的分析工具就越多,也知道该在何时使用以及如何正确的使用它们;在软件领域,咨询顾问有时要求我们购买某些软件开发方法而远离其它方法。这是不妥的,因为我们如果完全只依赖于某一种方法,就只会用这种方法来看待整个世界。在某些情况下,还有其它更好的方法。这种“工具箱隐喻”有助有保留所有方法、技术和技巧,以便在适当的时候按需选用。
组合各种隐喻
- 因为隐喻使用启发式方法而不是算法,所以彼此之间并不排斥。可以同时使用“生成”和“构建”两个隐喻。
- 隐喻有时不那么准确,必须扩展,才能从隐喻的启发种获益。但如果外延太多或方向错了,就会收到它们的误导。就像可以无用任何强大的工具一样,隐喻也可能被误用,但它们强大的功效,使其仍然不失为智慧工具箱中的宝物。
5、总结
- 隐喻是启发式方法而不是算法,因而往往有些随意。
- 隐喻把软件开发过程与人们熟知的活动联系在一起,帮助人们更好的理解。
- 有些隐喻比其他一些隐喻更为贴切。
- 通过把软件的构建过程比作房屋的建设过程,我们发现,必须要精心准备,而大型项目和小型项目也是有区别的,项目越大,越要准备充分才能做好。
- 把软件开发的实践比作智慧工具箱中的工具,进一步表明每个程序员都有许多工具,但并不存在任何一个能适合所有工作的工具。为每个问题选择合适的工具是成为高效率程序员的关键。
- 不同的隐喻并不排斥,要使用对自己最有用的隐喻组合。