软件技术发展的使命之1就是控制复杂度(Complexity)。从高级语言的产生,到结构化编程,再到面向对象编程、组件化编程等等。关于复杂度的定义其实不1致,想要详细了解的可以读读The Many Faces of Complexity in Software Design.
英文中Complex和Complicated有着奥妙的不同。但总结起来,软件复杂度偏负面意义,包括两个要点:
- 难以理解 (难以保护和扩大。)
- 没法预测行动
复杂度是随着软件范围不断扩大而必定产生的。它本身又是1个相对的概念,同1个系统对设计者、开发者,和保护者而言,复杂度是不同的。不同时期,1个程序员所能掌握的复杂度也是不同的,这也是1个程序员不断提升的目标。
既然业界已对抗复杂度几10年了,我们就来整理1下。
以分解的方式进行的设计,主要特点是:
- 分离职责(Seperation of Concerns,参考单1职责原则)
- 关注接口(定义交互)
这是最常使用的技术了。将1个大问题,不断的拆解为各个小问题进行分析研究,然后再组合到1起。在西方称为Divide and Conquer Principle (分而治之原则)。
在结构化编程的时期,提倡模块化(Modularization)。最早提出软件复杂度的工程师提出了基于组件的软件(Component Based Software)。不知道是否是从乐高积木上得到的启发,将系统中拆分为不同的组件,各自实现,然后再组装在1起。
在架构设计中,不管是C/S风格,分层,还是N-Tier,SOA,和前面组件式1样,都是在进行分解,它们都更加强调组合交互。设计上,分分职责,定义好接口,就能够各自开发了。然后将交互限定于接口层,就可以够很好的控制全部系统的复杂度。
比如利用层使用1个语音库(Speech Library,1个以库的情势的模块化利用), 根本不用关心其内部实现,只要了解如何使用它的API就能够了。
改进依赖关系的要点:
- 无环形依赖
- 稳定依赖原则(SDP)
分解可以下降系统层级的复杂度,但还有1种复杂度没法解决,即依赖的问题。这在敏捷软件开发:原则、模式与实践中关于依赖性的讨论很详细。当参与者增加时,交互就会随之变得复杂。而当前的软件范围,系统中的各类SDK的API, Framework的API, 各种第3方库愈来愈多,模块间的依赖就会愈来愈复杂。
明显系统中的模块或组件太多了,需要进1步整理。但真实的问题在于出现了双向和环形的依赖。比如上图中负责计算的Computing模块也依赖到了UI模块,也许是由于UI层持有1个计算所需的关键参数。如果UI层变更,便可能会影响到Computing,出现没法预测的行动,给客户以不稳定的印象。
所以模块间的依赖关系必须简化,绝对不能出现环形的依赖。以Chromium为例,它对各个模块的依赖就有严格的定义,并且有DEPS在编译期保证程序员不会出错。下图是Chromium Component依赖关系的定义,其中Component内部目录的依赖关系也有定义:
当底层模块需要依赖上层模块的实现时,就要通过依赖颠倒(DIP)来处理。简单而言就是由底层模块定义1个接口,要求上层模块实现并注入到底层模块。
人的学习进程最有效的1种方式就是归类,其中应用的就是抽象思惟。面对变幻无常的天气,人类通过对云的形状进行抽象,就能够预测天气变化。这里有1个抽象建模的进程。
抽象其实不是面向对象语言专属,其实它和语言无关,本质上是1个思考的方式。它和分离的最大区分在于,抽象强调将细节隐藏,只关注核心的本质。而后者则重视于细节问题的分解和组合。
以求固定两点的最快捷线路为例。从分离的角度来,可以分解为以下问题:
- 步行需要多少时间?
- 乘公共交通多少时间?
- 乘的士多少时间?
- 组合以上答案,再评估哪个最快捷的方式。
而从抽象的角度来看,解决的思路会是这样的:
遍历所有可能的交通工具,取耗时最小的:
1. 步行
2. 乘公共交通
3. 乘的士
先给出1个抽象的解决思路,至于细节,则是进1步的实现。抽象最大的威力在于它比实现要稳定,也最能用于固化核心设计。在开发进程中,常常围绕着各种细节讨论,仿佛抽象过于虚。但是如果没有以抽象来建立系统的设计全景,有些讨论将变得效力低下。
在敏捷软件开发:原则、模式与实践中,Martin大叔简单的用抽象类在总类个数中的占比作为抽象性的度量,再结合稳定性的度量,用来评估设计。详情可以参考组件设计原则之概念篇(3)。
设计和实现时引入没必要要的抽象或分解,也是1种复杂度.斟酌扩大性也是肯定会产生的需求才要斟酌进来,否则就是引入没必要要的复杂性.这也是敏捷设计所提倡的.
1些约定俗成的命名,常常隐含着设计.比如Observer, Client, Adapter等等.我们要学习这些模式,也要准确加以命名.否则很容易造成理解上的问题.
软件设计是1个平衡的进程,软件的复杂度决定着系统的可保护性、可扩大性和灵活性。我们再来回顾1下前人定义出软件设计的3原则:模块化、抽象和信息隐藏。McCabe也曾有论文专门讨论将圈复杂度利用度量设计的复杂度,不过已历史久远。现在来看以依赖关系来评估设计的复杂度会更加有效。有兴趣可以了解1下CppDepend。另外Google的工程师则基于LLVM IR也实现了1个工具用于依赖关系分析(Generateing Precise Dependencies for Large Software)。
转载请注明出处: http://blog.csdn.net/horkychen