设计是一项把需求转换为编码方案的活动。设计在软件开发中是必定存在的,无论何种情况下,我们都需要把需求转化为编码方案。
设计是一个不断完善的过程,设计的缺陷可能在设计之初无法被发现,直到设计完成时才被察觉到,这导致了再次设计的需要。注意,我们在首次设计时应该尽可能的避免缺陷的出现,而不是视而不见以求下次解决。
设计上的付出是必要的,在设计阶段发现错误并改正比编码后发现相同错误并改正的代价低的多,如果设计的错误拖延到维护阶段,那就更加不堪设想。
设计存在优劣,必须明确这一点。敷衍的进行设计后果是很严重的(虽然偶尔也能更加快速的产生产品),糟糕设计的最大特点是无法响应需求变更。但也应该注意,优秀的设计有多种,糟糕的设计也有多种。
事物的本质属性是一件事物必须具有的,如果不具有则就不在是该事物。事物的偶然属性是不起解决性作用的属性。例如,车总有轮子,没有了轮子就不在是车,那么轮子就是本质属性,但是轮子可能有 4 个,也可能有 3 个,无论多少个轮子依然是车,那轮子的数目就是偶然属性。
本质复杂度源于需求,无论采取何种手段,何种技术都不会对本质复杂度造成任何影响,所以有人才会说,软件开发本身就很复杂(无论使用何种技术开发都是复杂的)。我们需要管理好偶然复杂度(偶然性的复杂度),例如,需求是开发一个可用的计算器程序(图形界面可有可无),这时,开发命令行的计算器就比开发 GUI 的计算器的偶然复杂度小,用 C++ 进行开发其偶然复杂度就比用 Dephi 高。敏捷开发和 Unix 中都强调 KISS,而敏捷开发更加强调应该使用简单的技术和工具,这都是为了避免偶然复杂度过高带来额外的成本开销。
设计者应该真正理解偶然复杂度,如果设计出来的框架,在进行开发时需要开发人员关注于系统的每个细节,那么对于程序员来说偶然复杂度是高的,相比下,对于每个功能的开发,程序员只需要关心当前编写的任务,那么偶然复杂度是低的(COM 的设计非常强调的一点是封装,而不是复用)。
设计的基本的原则:
1)尽量降低偶然复杂度。如果可以简单的解决问题,那为什么不这么做了?另外某些角度来说:
模块化的系统(系统对“模块”一词有确切的定义),在一定程度上偶然复杂度较低,模块化的系统让程序员只需要关心当前开发的部分。
2)尽量保持高内聚,低耦合。高耦合的系统惧怕变动(动一处而牵动全身),变动带来的问题通过关联进行传递。高耦合的系统的关键点在于:系统组成部分的关联过多,导致过多的相互影响,当关联数量到一定程度,整个系统就会很不稳定。
具体来说:
<1> 集成的困难:高耦合的各个程序组成部分需要花费较多时间进行集成。
<2> 测试的困难:一个部分的问题通过与其他部分关联传递到其他部分,那么意味着一个部分的变动可能导致整个系统的重新测试。
低耦合:在类这个层次上来说,除了底层的工具类(例如 STL)之外(工具类允许和系统中的大部分类发生关联),应该尽可能的减少类之间的关联。
3)可扩展。可扩展的一个标志是,无需改变系统底层,即可增加新的功能。
4)可移植性。可移植性非常特别(似乎违背低偶然复杂度原则),它不同于软件的其他的特性,除非你保证你的项目永远无需移植到其他的环境中去,否则,你应该注意这点,因为通常来说,时间越长,越难移植,甚至最后只能重写,代价非常之高。
5)分层。分层的系统相对来说偶然复杂度较低,关联较少。每层都有每层的工作,相互影响较小。
另外,类的粒度也比较重要,大粒度的类间关联少,但是不可避免的就是类自身过于复杂。小粒度的类自身很简单,易于开发和维护,但是类间关联变多,通讯太频繁。粒度过大和过小都会带来较多的问题,应该根据经验作合理设计。
在 C++ 中使用巨大的类无异于使用 C 语言进行编程,这意味着你抛弃了 C++ 强大的抽象能力。含有巨类的系统中的一个显著的标志是:含有大量重复代码或者相似代码(这归结于 C 语言不够强大的抽象能力和项目在时间上的压力)
最后要说的一句是:KISS 和敏捷给太多人以误解,请仔细斟酌它们的含义(有几个人懂得 KISS 的真正含义)。设计是不可避免的,好的设计是至关重要的。好的设计在一定程度上控制了软件的成本,即使你的老板并不明白,但是你应该这么做。
author: killercat