什么是模块化
模块化这个词最早出现在研究工程设计中的《Design Rules》,这本探路性质的书中。其后模块化原则还只是作为计算机科学的理论,尚不是工程实践。此时硬件的模块化一直是工程技术的基石之一。如标准螺纹、汽车组件、计算机硬件组件等。
软件模块化的原则也是随着软件的复杂性诞生的。从开始的机器码、子程序划分、库、框架、再到分布在成千上万公里的互联网上主机上的程序库。模块化是解决软件复杂性的重要方法之一。
模块化以分治法为依据,但是否意味着我们把软件无限制的细分下去。事实上当分割过细,模块总数增多,每个模块的成本确实减少了,但模块接口所需代价随之增加。要确保模块的合理分割则须了解信息隐藏,内聚度及耦合度。
模块化的意义
一句话:解决软件的复杂性问题,或说降低软件的复杂性。不至于随着变大而不可控而失败,使其可控,可维护,可扩展。
从这个意义上说:要编写复杂软件又不至于失败的唯一方法就是用定义良好的接口把若干简单模块组合起来。如此,多数问题只会出现在局部,那么就有希望对局部进行改造、优化甚至替换,而不至于牵动全局。
更术语一些的定义:模块化是一个软件系统的属性,这个系统被分解为一组高内聚、低耦合的模块。这些模块拼凑下就能组合出各种功能的软件,而拼凑是灵活的,自由的。经验丰富的工程师负责模块接口的定义,经验较少的则负责实现模块的开发。
什么是模块
上面提到,模块化是以分治法为依据。简单说就是把软件整体划分,划分后的块组成了软件。这些块都相对独立,之间用接口(协议)通信,每个块完成一个功能,多个块组合可以完成一系列功能。
以上可以看出划分后的模块应该具有清晰的,有文档描述的边界(接口/协议)。不同的语言对于模块的实现不同。比如SmallTalk,没有模块的概念,所以类就成了划分的唯一物理单元。Java有包的概念,也有类的概念。因此单独的类可以用来划分模块,包也可以用来划分。JavaScript是基于对象的语言,它创建对象无需先声明一个类,因此对象是天然用来划分模块的。
无论那种语言,封装是写模块的首要特质。即模块不会暴露自身的实现细节,不会调用其它模块的实现码,不会共享全局变量。一切只靠接口通信。模块化和封装是密不可分的。
模块的大小
模块分的越多,每一块就越小,接口的定义就越重要。全局复杂度和受bug影响的程度也会相应降低。软件应设计成由层次分明的嵌套模块组成,而且每层的模块粒度应该降至最低。Hatton绘制了一张缺陷密度和模块大小的关系图,发现曲线呈U型,凹面朝上。
可以看到,模块过大或过小都会滋生更多的bug。Hatton的经验表明,200~400行之间的逻辑行的代码是最佳的。
紧凑的模块
紧凑型是一个设计是否能装入人脑的的设计。紧凑的设计让人乐于使用,不会在你的想法与实际工作之间格格不入。紧凑不等同于“薄弱”,也不等同于“容易学习”。对于某些紧凑学习而言,在掌握其精妙的内在概念模型之前,要理解它是非常困难的。比如 Lisp 语言的设计就是紧凑的,又如 jQuery 库的设计也是非常紧凑的。
正交的模块
正交性是有助于使复杂设计也能紧凑的最重要特性之一。在纯正交设计的软件中,任何操作均无 副作用。每一个动作(方法调用)只做一件事,不会影响其它。
Douglas McIlroy 的“只做好一件事”的忠告是针对简单性的建议,但其实也暗含了对正交性的强调。