code2读书笔记-第五章 软件构建中的设计
1.设计中的挑战
设计的过程
-
确定取舍和调整顺序
要去衡量彼此冲突的各项设计特性,寻求平衡。如果快速的反应开发比快速开发时间更重要,就需要设计者选取一套方案,但是如果相反,就需要另一套方案。 -
受到诸多限制
有限的资源限制才会促使产生简单的方案。 -
不确定的
不同的人有不同的设计方案 -
启发式的过程
设计过程总会有试验和犯错 -
自然而然形成的
它是在不断的设计评估、非正式讨论、写试验代码以及修改试验代码中演化和完善的。
2.关键设计的概念
软件的首要技术使命:管理复杂度
试着以某种方式组织程序,以便在一个时刻可以专注与一个特定的部分。这么做的目的是尽量减少在任一事件所要思考的程序量。
理想的设计特征
-
最小复杂度 做出简单且易于理解的设计
-
易于维护
设计出自明的(self-explanatory)系统 -
松散耦合
减少关联,通过应用类接口中的合理抽象,封装以及信息隐藏等原则,设计出相互关联尽可能小的类。 -
可拓展性
在增强系统功能时,无需破坏底层结构blanks-around-headers -
可重用性
- 高扇入: 让大量的类使用某个给定的类(工具类)
- 低扇出: 一个类里少量使用其他的类。如果使用较多其他的类,可能使其变得过于复杂。
- 可移植性
- 精简性: 一本书的完成,不再它能不能加入任何内容的时候,而在于不能删去任何内容的时候
- 层次性: 尽量保持系统各个分解层的层次性,使你能够在任意的层面上观察系统
交互关系的复杂度
- 最简单的交互关系是让一个子系统去调用另一个子系统的子程序。
- 复杂一点的交互关系是在一个子系统中包含另一个子系统中的类。
- 最复杂的交互关系是让一个子系统中的类继承另一个子系统中的类。
类与对象的比较
对象是指运行期间在程序中实际存在的具体实体(enity),而类是指在程序源码中存在的静态事物。对象是动态的,它拥有你在程序运行期间能得到的具体的值和属性。
3.设计构造块
找出对象
使用对象进行设计的步骤:
- 辨识对象及其属性(方法(method)和数据(data))
- 确定可以对各个对象进行的操作
- 确定各个对象能对其他对象进行的操作
- 确定对象的哪些部分对其他对象可见-哪些部分可以使公用的。哪些部分是私用的。
- 定义每个对象的公开接口。
形成一致的抽象
任何时候当你在对一个聚合物品工作时,你就是在使用抽象了。比如称呼一个东西为房子而不是材料和钉子。以复杂度的观点看,抽象的主要好处就在于它使你能忽略无关的细节。
封装实现细节
抽象是可以让你从高层的细节来看待一个对象。而封装则是除此之外,你不能看到对象的任何其他细节层次。封装不只是让你能用简化的视图来看复杂的概念,同时还不能让你看到复杂概念的任何细节。你能看到的就是你能全部得到的。
当继承能简化设计时就继承
定义对象之间的相同点和不同点就叫继承。
信息隐藏
信息隐藏式技术使命中格外重要的一种启发方式,因为它强调的就是隐藏复杂度。在设计一个类的时候,一项关键的决策就是确定类的哪些特性应该对外可见,而哪些特性应该隐藏起来。
好的类接口就像是冰山尖儿一样,让类的大部分内容不会暴露出来。
信息隐藏主要分为两大类:
- 隐藏复杂度:这样你就不用再去应付它,除非你要特别关注的时候。
- 隐藏变化源:这样当变化发生时,其影响就能被限制在局部范围内。复杂度的根源包括复杂度的数据类型、文档结构、布尔判断以及晦涩的算法。
信息隐藏的障碍
大多数让信息无法隐藏的障碍都是由于惯用某些技术而导致的心理障碍
- 信息过度分散
系统多处引用相同数据,可以通过设置常量来引用,另外比如全局数据的访问和处理,交给一个访问器子程序去处理,而不是分散在程序各个地方。 - 循环依赖
减少A类引用B类子程序,B类子程序引用A子程序,难于单独测试 - 把类内数据误认为全局数据(全局数据的两类问题)
- 子程序在全局数据操作,无法知道其它子程序对这个全局数据的操作。
- 子程序知道其它程序对全局数据进行操作,但无法知道具体进行了哪些操作。
- 可察觉的性能损耗
试图在系统架构层和编码层均避免性能损耗。 但按照信息隐藏目标去设计系统不会与按照性能目标去设计相冲突。
找出容易改变的区域
好的程序所面临的最重要的挑战之一就是适应变化。目标应该是把不稳定的区域隔离出来。下边就是应对变动措施:
- 找出看起来容易变化的项目
- 把容易变化的项目分离出来 把容易变化的组件单独划分为类
- 把看起来容易变化的项目隔离出来
如何定位容易变化的区域:
- 业务规则
- 对硬件的依赖性
- 输入和输出
- 非标准的语言特性
- 困难的设计区域和构建区域 因为这些区域的代码可能会写的很差而需要重新去做。
- 状态变量
在使用状态变量时增加至少两层的灵活性和可读性:
- 不要使用布尔变量作为状态变量,请换用枚举类型。给状态增加一个新的状态是很常见的。
- 使用访问子程序取代对状态变量的直接检查。
预料不同程度的变化
让变化的影响或范围与发生该变化的可能性成反比。办法:从程序的核心开始,增量扩展,不断扩充系统,同时考虑质的变化,比如让程序变成线程安全,这些潜在的改进的区域就是系统中潜在的变化。
保持松散耦合
耦合度设计的目标是创建出小的、直接的、清晰的类或子程序,使它们的与其他类或子程序之间关系尽可能的灵活。耦合的标准如下:
- 规模:模块间的链接数(参数,公用方法),小就是美
- 可见性:两个模块间连接的显著程度,比如通过参数传递数据而不是通过全局数据连接
- 灵活性:模块间的连接是否容易改动,比如传参使用的是多个变量,而不是一个一个模块里的特定对象。
耦合的种类:
- 简单数据参数耦合
- 简单对象耦合
- 对象参数耦合
- 语义上的耦合(使用了另一个模块的工作细节的语义知识)。比如module1向module2传递一个控制标志,这样就需要了解module2对控制标志的使用
要点
- 软件的首要技术使命就是管理复杂度。以简单性作为努力目标的设计方案对此最有帮助。
- 简单性可以通过两种方式来获取: 一是减少在同一时间所关注的本质性复杂度的量,二是避免生成不必要的偶然的复杂度。
- 设计是一种启发式的过程。固执于某一种单一方法会损害创新能力,从而损害你的程序。
- 好的设计都是迭代的。你尝试设计的可能性越多,你的最终设计方案就会变得越好。
- 信息隐藏是个非常有价值的概念。通过询问“我应该隐藏些什么?”能够解决很多困难的设计问题
- 很多有用有趣的,关于设计的信息存于本书之外。这里只是提供一点提示。
其它
二分查找算法很优雅,可往往一个蛮力的,顺序的查找算法就可以了
自上而下策略和自下而上策略的最关键区别在于,前者是一种分解策略而后者是一种合成策略
最大的设计问题通常不是来自于那些我认为是很困难的,并且在其中做出了不好的设计的区域;而是来自于那些我认为是简单的,而没有做出任何设计的区域
你在应用某种设计方法越教条化,你所能解决的现实问题就会越少
软件开发更多地是由理论指导还是由实践指导?软件开发从根本上而言是创造性还是确定性的?《software creativity》