第五章:软件构建中的设计

软件的首要技术使命:管理复杂度

当项目确由技术因素导致失败时,失控的复杂度通常就是原因。
软件变得极端复杂,让人无法知道它是做什么的,当人无法知道代码的改动会对其他代码产生什么影响的时候,项目也就快停止进展了。
往往由于以下的原因导致高代价,低效率的设计:
  1. 用复杂的方法解决简单的问题
  2. 用简单但错误的方法解决复杂的问题
  3. 用不恰当的复杂方法解决复杂问题
管理复杂度的方法
把任何人在同一时间需要处理的本质复杂度的量减到最少
不要让复杂度无谓地快速增长
理想设计的特征
最小复杂度:这是首要目标,应该做出简单且易于理解的设计。
易于维护:把维护代码的同事当成是你的听众,设计出能self-explanatory的系统
松散耦合:设计时让程序的各个组成部分关联最小
可扩展性:能增强系统的功能而无需破坏其底层结构
可重用性:重复使用
高扇入:高扇入就是说让大量的类使用某个给定的类,这意味着设计出的系统很好地利用了在较低层次上的工具类
低扇出:低扇出就是说让一个类少量或适中地使用其他的类,防止变得过于复杂
可移植性
精简性:设计出的系统没有多余的部分
伏尔泰曾说:一本书的完成,不在它不能再加入任何内容的时候,而在不能再删除任何内容的时候。
在软件领域,这一点就更正确。
层次性:尽量保持系统各个分解层的层次
标准技术:尽量使用标准的技术,这样易于理解。
设计的层次
第一层:软件系统
第二层:分解为子系统或包。常用的子系统如:业务规则、用户界面、数据库访问等
第三层:分解为类
第四层:分解成子程序:完整地定义出类内部的子程序。
第五层:子程序内部的设计:为每个子程序布置详细的功能。
  • 找出现实世界中的对象:确定对象的属性、数据、可以开发的操作、公用以及私有的部分、以及与其他对象的关联。
  • 形成一致地抽象:抽象是指一种能让你在关注某一概念的同时,可以放心地忽略其中的一些细节的能力——在不同层次处理不同的细节。
  • 封装实现细节:封装填补了抽象留下的空白
  • 当继承能简化设计时就继承
  • 隐藏秘密(信息隐藏):信息隐藏在所有层次都有很大的作用,结构化设计里面的“黑盒子”概念就是来源于信息隐藏。引出了封装和模块化的概念,并与抽象的概念紧密相关。类的接口应该尽可能少地暴露其内部的工作机制。
隐藏复杂度:这样你就不用再去应付它。
隐藏变化源:这样当变化发生时,其影响就能被限制在局部范围内。
信息隐藏的障碍
  1. 信息过度分散
  2. 循环依赖:A类中的子程序调用了B类中的子程序,B类中的子程序又调用了A类中的子程序。
  3. 把类内数据误认为是全局数据:全局变量会让你陷入很多编程陷阱,类内数据可能造成的风险则小得多。
信息隐藏的价值
信息隐藏具有独特地启发力,它能激发出有效的设计方案。
多问一问自己:这个类需要隐藏些什么?正切中了接口设计的核心。
找出容易改变的区域:
找出易变的部分,分类出来,彼此隔离开。
容易发生变化的区域:
业务规则、对硬件的依赖、输入和输出、非标准的语言特性、困难的设计区域和构建区域、状态变量。
用枚举类型来作为状态变量,使用访问器子程序取代对状态变量的直接检查。
预料不同程度的变化,设计时考虑系统的潜在变化。
首先找出程序中可能对用户有用的最小子集,这一子集构成了系统的核心,不易变。接下来可以用十分微小的步伐扩充这个系统。通过定义清楚问题的核心,你可以认清哪些组件属于附加功能,这时可以把它们提取出来,并把它们的可能改进隐藏起来。
查阅常用的设计模式:
关于设计模式的描述可以看另一篇笔记:传送门...。设计模式提供了现成的抽象来减少复杂度,标准化,带来启发性价值。
其他的启发式方法
高内聚性
内聚性指的是类内的子程序,或者子程序的所以代码在支持一个中心目标上的紧密程度——这个类的目标是否集中。
构造分层结构
指的是分层的信息结构,最抽象的概念表示位于层次关系的最上面,而越来越详细的具有特定意义的概念表示放在更低的层次中。
严格描述类契约
把每个类的接口看做是与程序的其余部分之间的一项契约会有助于更好地洞察程序。
分配职责
为对象分配职责。
有意识地选择绑定时间
绑定时间指的是把特定的值绑定到某一变量的时间。做早绑定的代码通常比较简单,但是也会比较缺乏灵活性。
创建中央控制点
对于每一段有作业的代码,应该只有唯一一个地方可以看到它,并且也只能在一个正确的位置去做可能的维护性修改。
考虑使用蛮力突破
一个可行的方案要好于一个优雅但不能用的方案
画一个图
保持设计的模块化
模块化的目标是使得每个子程序或者类看上去更像一个"黑盒子"
使用启发式方法的原则
1.理解问题。必须要理解问题。
2.设计一个计划。找出现有数据和未知量之间的联系。
3.执行计划以及回顾。
设计实践
分而治之
把程序分解为不同的关注区域,然后分别处理每一个区域。
自上而下和自下而上的设计方法
自上而下从一般性出发,把问题分解成可控的部分。自下而上的设计始于细节,向一般性延伸。
两者的关键在于,前者是一种分解策略,而后者是一种合成策略。