“软件设计”一词意味着去构思、创造或发明一套方案,把一份计算机软件的规格说明书要求转变为可实际运行的软件。设计就是把需求分析和编码调试连在一起得活动。好的高层次设计能提供一个可以稳妥容纳多个较低层次设计的结构。好的设计对于小型项目非常有用,对于大型项目就是更不可或缺。 ——《代码大全2》
很久不写文章了,最近一直在忙手里的活,期间也犯了很多错。尤其是代码设计这块每次都会重写好几遍,也发现了设计好的比我原先一股脑就写的简直好太多了,然后就被Boss推荐了这本书,借着看的功夫也记录下,一起学习~
这里直接从第五章开始,前面4张概念性的东西比较多,大家有兴趣的可以自己去找资源看一哈~
前四章:《欢迎进入软件构建的时间》、《用隐喻来更充分的理解软件开发》、《三思而后行:前期准备》、《关键的‘构建’决策》。
目录
设计中的挑战
软件设计的成果应该是组织良好、干净利落的,然而形成这个设计的过程却并非如此清爽。
- 取舍:在设计过程中,很有可能遇到两个需求彼此冲突的情况,比如在要求快色的反应时间得同时需要你缩减开发时间,这种时候就需要根据哪个更重要去进行取舍。
- 限制:设计的要点,一部分是在创造可能发生的事情,另一部分有事在限制可能发生的事情。如果不加限制,程序写的毫无约束,就会出现很多问题。
- 不确定:不同人设计出来的程序很可能都是不一样的。
- 启发式过程:设计过程中用会有试验和犯错,具有探索性。
- 自然形成的:综合以上我们可以说设计是“自然而然形成的”。设计是在不断地评估、讨论、试验中演化和完善的。
关键的设计概念
首要使命:管理复杂度
- 最小的复杂度:我们应该做出简单且容易理解的设计。如果你的设计不能让你在专注于程序的一部分时安心忽视其他部分,这一设计就没什么用了。
- 易于维护:设计时为做维护工作的程序员着想,时刻思考可能会就你写的代码而提出的问题。
- 松散耦合:我们应该设计出关联尽可能最少的类。
- 可扩展性:可增强系统的功能但不需要破坏其底层结构。
- 高扇入:让大量的类使用某个给定的累。这意味着设计出的系统很好的利用了在较低层次上的工具类。
- 低扇入:让一个类里少量或适中的使用其他的类(一般小于7个)。
- 可移植性:方便移植到其他环境中。
- 精简性:没有多余的代码(删的不能再删了)。
- 层次性:保持各个系统之间层次性。各个模块之间不应该相互耦合,而是分开不同的层用来统一访问,这样修改的时候也只用修改模块和层就可以。
- 标准技术:尽量用标准化的。常用的方法,让整个系统给人一种熟悉的感觉。
引用:
- 作为软件开发人员,我们不应该试着在同一时间把整个程序都塞进自己的大难,而应该试着以某种方式去组织程序,一遍能够在一个时刻可以专注于一个特定的部分。
- 受着人类固有限制影响的程序员的底线,就是要写出既让自己容易理解,也能让别人容易看懂,而且很少有错误的程序代码。
层次的设计
- 软件系统:整个系统。
- 分解为子系统或包:将系统分为确定的几个主要子系统,并且尽量限制系统之间的相互通信,避免形成环形图(A调用B,B调用C,C调用A)
- 分解为类: 识别出系统中所有的类。我们需要注意的是在定义类时,同时也应该注意到这些类与系统其余部分打交道的细节,确定好应有的接口。
- 分解成子程序:把每个类细分为子程序。上一步其实已经定义了一些子程序,这一步将要细化出私用(private)子程序。
- 子程序内部设计:这一层主要是组织代码块,确定算法来编写代码等等。
启发式方法
找出现实世界中的对象
根据现实世界中的对象分析。
例子:基于现实世界送牛奶。
对象分类:送货员、顾客、牛奶、订单
步骤:
- 辨识对象及其属性:例如送货员是个对象,他所拥有的属性可以有名字、负责范围、费率等等。
- 定义可对对象执行的操作:比如送货员可以更改负责范围等等。
- 确定每个对象可以对其他对象进行的操作:比如订单是否有某个顾客的、送货员是否有牛奶。
- 确定对象的那些部分对其他对象可见:设置公有(public)和私有(private)属性或方法。
- 定义每个对象的接口:包括public和peotected接口。
形成一致的抽象
任何时候当你在对一个局和物品工作室,你就是在使用抽象了。比如当你把一个东西称为“房子”而不是由玻璃、木材构成的组合体时。同理基类、接口也是一种抽象。
抽象所以也是我们用来处理现实世界中复杂度的一种手段,代码也是。
封装实现细节
封装是说,不只是让你能用简化的视图来看复杂的概念,同时还不能让你看到复杂概念的任何细节。你能看到的就是你能全部得到的。
引用上面的例子,意思就是可以让你知道房子的们在哪里,开着的还是关着的,但是不能让你知道们的材质是什么。
当继承能简化设计时就继承
比如有两种牛奶,木瓜牛奶和纯牛奶,这时候就可以定义个牛奶的基类,放进共同的属性方法,让那两种牛奶继承,不必每种牛奶写重复的东西了。
隐藏秘密(信息隐藏)
确定一个类中哪些特性应该对外可见,哪些应该隐藏起来。
例子:
加入我们要给物体赋值一个ID,ID是累加的,那么我们每赋值一个物体就会用到id++这种语句,这种做法会让对象在创建时执行的代码量最少,但是非常不利于修改。我们应该创建一个方法NewID,将id++隐藏起来,这样需要修改的时候我们只用修改NewID的方法就可以了,不会影响到其他的地方。
还有一种情况,假设现在需要吧ID的类型由整数变为字符串类型。如果在程序中已经大量的使用了int id这样的变量的话,那么我们修改也会非常麻烦。因此我们也应该把隐藏掉ID的类型,可以设计一个简单的IDType类(一个可以解释为int的用户自定义类型)。
找出最容易改变的区域
- 找出看起来容易变化的项目。
- 把容易变化的项目分离出来。
- 把看起来容易变化的项目隔离开来。
容易发生变化的区域:
- 业务规则
- 对硬件的依赖性
- 输入和输出:比如格式、排列顺序等...
- 非标准的语言特性:防止在不同环境下的使用出现问题。
- 困难的设计区域和构建区域:这些代码很可能因为设计的差需要重做,最好隔离起来,吧对其他部分的影响降到最低。
- 状态变量:比如定义一个错误变量,一开始用bool类型,后来改成了枚举类型。
- 数据量的限制:定义最大值变量隐藏信息。
引用:
- 对优秀的设计师一份研究表明,他们所共有的一项特质就是都有对变化的预期能力。
保持松散耦合
耦合度表示类与类之间或者子程序和子程序之间的紧密程度。耦合度设计的目标是创建出小的、直接的、清晰地类或子程序,使他们与其他类或子程序之间关系尽可能的灵活。
耦合的种类:
- 简单数据参数耦合:两个模块之间通过参数传递数据,并且所有数据都是简单数据烈性。
- 简单对象耦合:一个模块实例化一个对象。
- 对象参数耦合:如果对象1要求对象2给他一个对象3,就是对象参数耦合。
- 语义上的耦合:一个 模块不仅使用了另一个模块的语法元素,而且还使用了有关那个模块内部工作细节的语义知识。(非常不好)
查阅常用的设计模式
设计模式精练了众多现成的解决方案,可用于解决很多软件开发中常见的问题。
优点:
- 设计模式通过现成的抽象来减少复杂度
- 设计模式通过把常见的解决方案细节予以制度化来减少出错。
- 设计模式通过提供多种设计方案而带来启发性价值。
其他的启发方法:
- 高内聚性
- 构造分层结构
- 严格描述类契约(功能)
- 分配职责
- 为测试而设计
- 避免失误
- 创建中央控制点
- 考虑使用蛮力突破
- 画一个图
要点:
- 软件的首要技术使用就是管理复杂度。以简单性作为努力目标的设计方案对此最有帮助
- 简单性可以通过两种方式来获取:一是减少在同一时间所关注的本质性复杂度得量,而是避免生成不必要的偶然复杂度。
- 设计室一种启发式过程。固执于某一种单一方法会损害创新能力,从而损害你得程序。
- 好的设计都是迭代的。你尝试设计的可能性越多,你得最终设计方案就会变得越好。
- 信息隐藏是个非常有价值的概念。通过询问“我应该隐藏什么?”能够解决很多困难的设计问题。