第七章 高级结构设计
7.5 往返设计
通过组合使用主要设计方法来扬长避短是完全可能的。每种设计方法都只是程序员工具箱 中的一件工具,不同的工具适合不同的工作,你将从研究所有方法的启发中获益无穷。
下面一小节结论述了软件设计为什么困难的某些原因,并指出了如何组合使用结构化设计,
面向对象设计和其它设计方法。
7.5.1 什么是往返
你可能会有这样的体验:当你编写程序快结束时,你非常希望能有机会再重新编写一次,
因为在编写过程中你对问题又有了更深的理解。这对设计也是同样适用的,只不过在设计中这 个循环的周期更短,带来的好处也更大,因此,你完全可以在设计过程中进行几次往返。
“往返设计”一词抓住了设计是个迭代过程这一特点:通常你不会只从 A 点走到 B 点,往 往还需要再返回 A 点。
在用不同的设计方法对各种设计方案进行尝试的进程中,将从高层次的总体上和低层次的 细节上对问题进行观察。在从事高层次问题时获得的总体印象将会对你在低层次细节中的工作 有很大帮助;同时,在从事低层次问题时所获得的细节将为你对高层次的总体理解和作出总体 设计决定奠定下良好的基础。这种在高层次和低层次之间往返思维过程是非常有益的,由此而 产生的结构,将比单纯自顶向下或自底向上产生的结构要稳定得多。
许多程序员,都会在这一往返过程中遇到麻烦。从对系统的一个观察点转到另一个观察点 上,的确是很困难的,但这是进行有效设计所必需的,你可以阅读一下 Adams 于 1980 年写的 一本叫“Conceptual Blockbusting”的书,来提高自己思维的灵活性。
7.5.2 设计是一个复杂的过程
J.P Morgon 曾经说过人们在做事情时常常有两个原因:表面上冠冕堂皇的原因和真正的 原因。在设计中,最终结果往往看起来是井井有条的,似乎设计者从未犯过任何设计错误,事
实上,设计过程很少有像最终结果那样井井有条。
设计是一个复杂的过程。因为你很难把正确答案与错误答案区分开来。如果你让三个人分 别设计同一个程序,他们带回来的往往是三个大相径庭的方案,而且其中每一个看起来都非常 适用。它是一个复杂的过程还因为你在设计过程中曾钻过许多死胡同、犯过许多错误。说它是 一个复杂的过程也是因为你不知道什么时候设计方案已经足够完善了。什么时候算完成呢?对 这个问题的通常答案是“当你没有时间时”。
7.5.3 设计是一个“险恶”的过程
Horst Rittel 和 Melvin Webber 把“烦人”的问题,定义成只有通过解决它或者部分解决它,
才能给出明确定义的问题。这个似是而非的定义事实上暗示着你不得不首先“解决”这个问题,
对其有一个清楚的定义和理解,然后再重新解决一遍,以获得正确的解决办法。这一过程对软 件开发就像母爱和面包对你我一样必不可少。
在现实中,关于险恶问题的一个富于戏剧性的例子便是托卡马大桥的设计。在修建大桥时,
主要考虑的便是它应该能承受设计载荷并能抗 12 级大风。然而,没人想到风在吹过桥时会产生
“卡门旋涡”——一种特殊的空气动力学现象,从而使桥产生横向简谐振动。结果,在 1940 年 的一天,只在 7 级风的作用下,桥便因振动而坍塌了。
说它是险恶问题的典型例子是因为直到桥坍塌时,也没有一个设计师想到应该在这个设计 中考虑空气动力学问题。只有在建成大桥(解决问题)之后,才使得他们意识到了要考虑的这 一“额外”问题,经过重新设计,新桥至今依然屹立在河上。
你在学校中设计的程序和在实际工作中设计的程序最重要的不同是:在学校中遇到的程序 设计问题,几乎没有哪个是险恶的,教师留给你的程序作业都是预先想好让你一次即可完成的。
如果哪位教师留给你们一个程序作业,当你们完成后他又突然改变了作业题目,接着,当你即 将完成那个程序时,他又改变了主意,会怎么样呢?我想,如果有谁胆敢这样的话,你们肯定 会把它绞死。但在实际工作中,几乎总是这样。
7.5.4 设计是一个启发的过程
进行有效设计的关键是要认识到它是个启发的过程。设计中,总是吃一堑,长一智的。往 返设计的概念事实上解释了设计是个启发过程这一事实,因为你要把任何设计方法都只当成一 种工具。一种工具只对一种工作或者一种工作的某一部分才有效,其余的工具适合其它的工作,
没有一种工具是万能的。因此,你往往要同时使用几种工具。
一种很有效的启发工具就是硬算。不要低估它。一个有效的硬算解决方案总比优雅却不能 解决问题的方案要好。以搜索算法的开发为例,虽然在 1946 年就产生了对分算法的概念,但直 到 16 年后,才有人找到了可以正确搜索各种规模的表的算法。
图示法是另一种有力的启发工具。一幅图抵得上一千个单词。你往往不愿用那一千个单词 而宁愿用一幅图,因为图形提供了比文字更高的抽象水平,有时或许你想在细节上处理某一问 题,但是,更常见的是在总体上处理问题。
往返设计的一个附加的启发能力是你在设计的头几次循环中,可以暂时对没有解决的细节 问题弃之不管,你不必一次就对一切都做出决定,应记住还有一个问题有待做出决定,但同时 要意识到,你目前还没有充分的信息来解决这个问题。为什么在设计工作的最后 10%的部分苦
苦挣扎呢?往往在下一循环中它们会自然获得解决。为什么非要在经验和信息都不足的情况下 草率决定呢?你完全可以在以后等经验和信息丰富时做出正确决定。有些人对一次设计没能彻 底解决问题会感到很不舒服,但与其很不成熟地勉强解决问题,不如把问题暂放一个,待到信 息足够丰富时,再解决它。
最重要的设计原则之一是不要死抱着一种方法不放。如果编写 PDL 无效的话,那么就作图,
或用自然语言写出来,要么就写一小段验证程序,或者使用一种完全不同的方法,比如硬算解 决法,坚持用铅笔不停地写和画,大脑或许会跟上。如果这一切都无效,暂时放开这个问题。
出去自由自在地散散步,或者想一下别的,然后再回到这个问题上。如果你已经尽了全力但还 是一无所获,那么暂时不考虑这个问题往往会比坚持冥思苦想更快得到答案。最后,可以借鉴 其它领域中的方法来解决软件设计中的问题。关于问题解决中的启发方法的最初的一本专著是 G. Polya 的《How To Solve In》一书(1957),Polya 的书推广了数学中解决问题的方法,表 7-1 就是对其所用方法的总结,本表摘自 Polya 的书中的类似的总结表:
表 7-1 怎样解决问题
l.理解问题,你必须理解要解决的问题
问题是什么?条件是什么?数据是什么?有可能满足条件吗?已知条件足以确定未知 吗?已知条件是否不够充分?是否矛盾 7 是否冗余?
画一个图,引入恰当的符号,把条件的不同部分分解开。
2.设计一个方案。找到已知数据和未知之间的联系。如果不能找出直接联系的话,你可能不得 不考虑一些辅助问题,但最后,你应该找到一个解决方案。
以前你是否遇到过这个问题?或者是见过与它稍有不同的问题?是否知道与其相关的问 题?是否知道在这个问题中有用的定理?
看着未知!努力回忆起一个有着相同或类似未知的问题。这里有一个与此相关的你以前 解决过的问题,你能利用它吗?是能利用它的结论还是能用它的方法?是否该引入辅助 要素以使这个问题可以再用?
能否重新表述一下问题?能用另外一种方式表述它吗?返回到定义。
如果你无法解决这个问题,可以先试着解决一些别的问题,是否能想象出一个容易解决的 相关问题;一个广义些的问题或是一个更特殊的问题?一个相似的问题呢?能否解决问 题的一部分呢?仅保留一部分条件,忽略其余条件;未知可以被决定到什么程度?会发生 什么变化?能否从数据中推导出一些有用的东西?能否找出适于确定未知的其余数据?
能否改变数据或未知?同时改变两者呢?这样做能否使新的未知和新的数据更接近些?
是否使用了全部的数据?使用全部条件了吗?是否考虑了这个问题的全部必要条件?
3.执行你的计划。
执行你解决问题的计划,同时检查每一步工作。你是否可以认定每一步都是正确的?你 能证明这点吗?
4.回顾,检查一下答案。
你能检查一下答案吗?能检查一个论证吗?能否用另外一种方法推导出答案?能否一眼 就看出答案?
能否在其它问题中再利用本题的答案或者结论?
7.5.5 受迎的设计特点
高质量的设计往往有一些共同的特点。如果你能达到这些对象,那么可以认为你的设计也 是非常成功的。有些对象是互相矛盾的。但是这是设计的挑战所在,在相互矛盾的对象之间做
出合理的折衷。某些高质量设计的特点同时也是高质量程序的特点——可靠性。其余的则是设 计所独有的。
以下是设计所独有的一些特点:
智力上的可管理性。对于任何系统来说,智力上的可管理性都是其重要目标之一。它对于
智力上的可管理性。对于任何系统来说,智力上的可管理性都是其重要目标之一。它对于