《代码大全》学习笔记一:第五章 软件构建中的设计
1、“深入一种语言去编程”,而非“在一种语言上去编程”。区别在于,不要将自己的思想受限于语言特性,而是首先决定表达的思想,然后使用特定的语言特性来表达这些实现。
2、软件设计的首要问题:管理复杂度。管理复杂度的两个行之有效的方法:
1)把任何人同一时间需要处理的本质复杂度的量减少到最少 所有软件设计的目标都是把复杂问题分解成简单的部分。子系统间的相互依赖越少,你就越容易在同一时间只专注于问题的一小部分。精心设计的对象关系是关注点相互分离,从而使你在每一时刻只专注于一件事情。在软件架构层次上,可以将整个系统分解为多个子系统来降低问题的复杂度,为了减少子系统间的相互依赖,子系统尽量设计成松散耦合。要避免做成聪明的设计,因为聪明的设计常常是难于理解的。应该做出简单且易于理解的设计。如果你的程序设计方案不能让你在专注于程序的一部分时安心的忽视其他部分的话,这一设计就没有什么作用了。
2)不要让偶然复杂度无谓的快速增长 偶然的复杂性包括后期需求更改,需求不明确等。可以通过软件过程加以控制。
3、编程首先是要让人理解的,其次才是计算机。
4、关于子系统的设计,从管理复杂度的角度触发,要对子系统间的通信加以限制,否则子系统间的耦合会是划分子系统的好处消失。系统层的设计应该是无环图?
启发式设计方法:
1、 抽象:是一种能让你在关注某一概念的同时可以放心的忽略其中的一些细节的能力,在不同的层次上处理不同的细节。
2、 封装:抽象是“从更高层次的细节来看待一个对象”,封装是“除此之外,你不能看到对象的任何其他层次的细节”。封装帮助你管理复杂度的方法是不让你看到那些复杂度。
3、 信息隐藏:信息隐藏的好处是,对减少“改动影响的代码量”至关重要。需要隐藏的类型一般包括:
隐藏复杂度:这样你就不用去对付它,除非需要的时候。
隐藏变化源:这样变化发生时,其影响就被限制在局部的范围内。
4、 信息隐藏的障碍:
信息过度分散:通过子程序来统一管理过度分散的信息。
循环依赖:A类调用B类,B类又调用A类。我的代码中有很大这样的用法,看有什么方法可以解决?
在设计的时候,要多问“我该隐藏些什么?”。
找出容易改变的区域:(核心在于隐藏变化,与上面的隐藏信息对应)
1、 尽量使用枚举来替换bool常量。
2、 识别容易变化的区域:
业务规则:
硬件依赖:
非标准语言特性:
底层库:
困难的设计区域和构建区域:
状态变量:
数据量的限制:
3、 预料不同程度的变化:
应该让变化的影响或范围与发生该变化的可能性成反比,即:如果变化容易发生而又容易做出计划,你就应该在它上面多下功夫,不要浪费过度的精力在不太可能发生而又很难做出计划的变化上。
4、 找出容易变化的区域的一个好的方法:
首先找到程序中可能对用户有用的最小子集,这一最小子集构成了系统的核心,不容易发生变化。接下来,有微小的步伐扩充这个系统。当你考虑功能上的改进时,同时也考虑质的变化。这些潜在的改进区域就构成了系统中的潜在的变化。
保持松散耦合:
1、 耦合度表示类和类之间或者子程序和子程序之间关系的紧密程度,耦合度设计的目标是创建出小的,直接的,清晰的类或子程序,使他们与其他类或子程序之间的关系尽可能的灵活,这就被称作“松散耦合”。
2、 模块间好的耦合关系会松散到恰好能使一个模块能够很容易地被其他模块使用。
3、 请尽量使你创建的模块不依赖或者很少依赖其他模块。
4、 耦合标准:
规模:指模块间的连接数。规模越小,耦合度越松散。
可见性:模块间连接的显著程度。连接要尽可能的明显。
灵活性:模块间的连接是否容易改动。
总结:一个模块越容易被其他模块调用,那么他们之间的耦合关系就会越松散。创建系统架构时,按照“尽可能的缩减相互连接”的准则来分解程序。
5、 耦合的种类:
简单数据参数耦合:可以接受。
简单对象耦合:一个模块实例化一个对象。
对象参数耦合:对象作为参数传递数据,比数据参数耦合更紧密。
语义耦合:一个模块不仅使用了另一个模块的用法元素,而且还使用了有关那个模块内部工作细节的语义知识。
6、 松散耦合的关键之处在于,一个有效的模块提供出了一层附加的抽象(?是不是这个抽象屏蔽了内部的细节,而是对此模块的调用更容易)。一旦你写好了它,你就可以想当然的去使用它,这样就降低了整体系统的复杂度。使你在同一时间只关注一件事情。如果一个模块的使用要求你同时关注几件事情——内部工作的细节,对全局数据的修改,不确定的功能点——那么就失去了抽象的能力,模块所具有的管理复杂度的能力也削弱或完全丧失了。类和子程序是降低复杂度的首选和最重要的智力工具,如果他们没有帮助你简化工作,那就是他们的失职。
设计模式:
1、 设计模式通过提供现成的抽象来减少复杂度。
2、 设计模式通过把常见解决方案的细节予以制度化来减少出错。(设计模式我还要好好看看)
3、 设计模式通过多种设计方案而带来启发性的价值。
4、 设计模式通过把设计对话提升到一个更高的层次上来简化交流。
5、 陷阱:强迫让代码适应某个模式,或者为了模式而模式。这两者都不可取,好的设计应该是巧好能够解决现实问题的设计。
其他的启示方法:
1、 高内聚性:类内部的子程序或子程序内部的代码在支持一个中心目标的紧密程度。
2、 构造分层结构:通过分层来管理复杂度,同一时间,只关注本层的细节,而不是在所有的时间都要考虑所有的细节。
3、 严格描述类契约:?
4、 分配职责:考虑:该怎样为对象分配职责,对象应该隐藏什么信息。
5、 为测试而设计:考虑,怎样设计系统会更容易测试?类似于测试驱动开发。
6、 避免失误:在设计时,考虑失败的案例,有可能有反面的启发意义。
7、 画一个图
8、 保持设计的模块化:
使用启发式设计方法的原则:
1、 不要卡在一个方法上。
2、 你无须马上解决整个难题。
5.4 设计实践
1、 迭代:从不同的视角来考虑问题。
2、 分而治之:增量式改进。
3、 自上而下和自下而上:区别在于,前者是一种分解策略,后者是一种合成策略。
4、 建立设计原型。
5、 合作设计。
设计做多少才够
1、 两个问题:一个设计的层次(详细程度),另一个是记录设计的文档的正规程度。
2、 受影响的因素包括:项目规模(正比),项目生存周期(正比),项目成员经验(反比)
3、 最大的失误来自于我认为自己已经做得很充分,可事后却发现还是做得不够,没能发现其他的一些设计挑战。
4、 最大的设计问题通常不是我认为是很困难的,并且在其中做了不好的设计的区域;而是我认为是简单的,而没有做出任何设计的区域。
5、 程序化的活动容易把非程序化的活动驱逐出去——Gresham法则
6、 宁愿花费80%的精力去创建和探索大量的备选方案,而用20%的时间去创建并不是很精美的文档,也不愿意把20%的精力花在创建平庸的设计方案上,而把80%的精力用于对不良的设计进行抛光润色。
记录设计成果
1、 把设计文档插入到代码中;
2、 用wiki来记录设计讨论和决策;
3、 写总结邮件;
4、 使用数码相机保存设计结果。这种方法可以考虑一下。
5、 UML图。学习一下。
5.5 对流行的设计方法的评论
1、设计一切和不做设计都是不可取的,比较合理的做法是,预先做一点设计,或者预先做足够的设计。
2、如果你无法判断最佳的设计量,请记住:设计所有细节和不做设计一定是不对的。
P.J. Plauger:“你在应用某种设计方法的时候越教条化,你所能解决的现实问题就会越少。请把设计看出是一个险恶的,杂乱的和启发式的过程(没有教条化的规则可以遵循,能够巧好解决现实问题的方案才是最佳的方案)。不要停留于你所想到的第一套方案,而是去寻求合作,探求简洁性,在需要的时候做出原型,迭代,并进一步迭代。你将对你的设计成果感到满意。”
由此,对概要设计文档,形式上没有必要要求太高,设计的层次上,应该是更高层面上的概要设计。
开发流程当中,概要设计阶段对设计结果进行评审还有一个好处,就是在设计者向评审人员讲解设计方案时,有助于他对自身的设计方案重新审视而获得不一样的设计思路。
核对表:软件构造中的设计设计实践 你已经做过多次迭代,并且从众多尝试的结果中选择最佳的一种,而不是简单的选择第一次尝试的结果吗? 你尝试用多种方案分解系统,以确定最佳方案吗? 你同时用之上而下和自下而上的方法来解决设计问题吗? 为了解决某些特定的问题,你对系统中的风险部分或不熟悉的部分创建过原型、写出数量最少的可抛弃的代码吗? 你的设计方案被其他人检查了吗? 你一直在展开设计,直到设计细节跃然纸上了吗? 你用某种适当的技术——比如说wiki、电子邮件、挂图、数码照片、UML、CRC卡或代码注释——来保留你的设计成果吗? 设计目标 你的设计是否充分的处理了有系统架构层定义出并且推迟确定的事项? 你的设计被划分为层次吗? 你对把这一程序分解层子程序、包和类的方式感到满意吗? 你对把类分解成为子程序的方法感到满意吗? 类和类之间的交互关系是否已被设计为最小的了? 类和子程序是否被设计为能够在其他的系统中重用? 程序是否易于维护? 设计是否精简?设计出来的每一部分都绝对必要吗? 设计中是否采用了标准的技术?是否避免使用怪异且难以理解的元素? 整体而言,你的设计是否有助于最小化偶然的和本质的复杂度吗? |
本章要点
软件的首要技术使命就是管理复杂度。以简单性作为努力目标的设计方案对此最有帮助。
简单性可以通过两种方式来获取:一是减少在同一时间所关注的本质性复杂度的量,二是避免生成不必要的偶然的复杂度。
设计是一种启发式的过程。固执于某一种单一方法会损害创新能力,从而损害你的程序。
好的设计都是迭代的。你尝试设计的可能性越多,你的最终设计方案就是变得越好。
信息隐藏是个非常有价值的概念。通过询问“我应该隐藏什么,能够解决很大困难的设计问题”。