4.3.5 模块独立
模块的独立性是指软件系统中每个模块只涉及软件要求的具体子功能,而和软件系统中其他的模块的接口是简单的。显然,模块化、抽象、信息隐藏和局部化的直接结果是模块独立。模块独立程度可以由两个定性标准度量:耦合和内聚。耦合衡量不同模块彼此间的相互依赖程度;内聚衡量一个模块内部各元素间彼此结合的紧密程度。显然,模块的耦合性越低,其独立性越强,内聚性越高,其独立性越强。
(1)耦合。耦合是对一个软件结构内不同模块间相互关联程度的度量。耦合强弱取决于模块间接口的复杂程度,一般由模块之间的调用方式、传递信息的类型和数量来决定。
软件设计,应该追求尽可能松散耦合的结构。这样的程序容易测试、修改和维护,当某一模块中出现错误时,传播到整个系统的可能性也小。模块间的耦合性强烈影响着系统的可理解性、可测试性、可靠性和可维护性。模块间的耦合方式有六种,如图4.4所示。

图4.4 耦合性的6种类型
①非直接耦合。如果两个模块分别从属于不同的上级模块的控制与调用,它们之间不直接传递任何信息,互相独立,则称这两个模块为非直接耦合关系。需要注意的是,在一个软件系统中,所有模块之间不可能没有任何间接的练习,否则,它们无法构成一个系统。
②数据耦合。如果两个模块之间有调用关系,相互以参数形式传递信息,而且交换的信息仅仅是数据,那么这种耦合称为数据耦合。系统中至少必须存在这种耦合,因为只有当某些模块的输出数据作为另一个模块的输入数据时,系统才能完成有价值的功能。数据耦合是理想的设计目标。
③特征耦合。两个模块通过传递数据结构加以联系,或都与一个数据结构有关系,则称这两个模块之间存在特征耦合(或标记耦合)。当被调用模块只需要整个数据结构中的一部分数据元素时,将整个数据结构作为参数的弊端是极易造成信息泄漏,给计算机犯罪提供了可乘之机。
④控制耦合。如果两个模块彼此间传递的信息中有控制信息,这种耦合称为控制耦合。比如,一个模块传递开关、标志、名字等控制信息,明显控制另一个模块的内部执行逻辑。这种耦合意味着被调用模块内部的功能分解不彻底,调用模块需要知道被调用模块内部的逻辑结构,这就降低了模块的独立性,对被调用模块再分解后可以用数据耦合替代。
⑤公共环境耦合。当两个或多个模块通过一个公共数据环境相互作用时,它们之间的耦合称为公共环境耦合。公共环境是指全局变量、共享通信区、内存的公共覆盖区、任何存储介质上的文件、物理设备等。公共环境耦合的强度,随耦合的模块个数而变化——随耦合模块的个数增加而增加。在只有两个模块有公共环境耦合的情况下,这种耦合又可分为两种情况。
一种情况是,一个模块往公共环境送数据,另一个模块从公共环境取数据。这种公共环境耦合是一种比较松散的耦合。
另一种情况是,两个模块都既往公共环境中送数据,又从中取数据。这种耦合比较紧密,介于数据耦合和控制耦合之间。
如果两个模块共享的数据很多,都通过参数传递可能很不方便,此时利用公共环境耦合是一种可以被接受的方案。
⑥内容耦合。如果一个模块直接访问另一个模块的内部数据,一个模块不通过正常入口而转入到另一个模块的内部,两个模块有一部分代码重叠(只可能出现在汇编程序中),一个模块有多个入口,这些现象都属于内容耦合。内容耦合是最高程度的耦合,是应该坚决避免使用的耦合。(https://www.daowen.com)
总之,在设计软件结构时,应该根据问题的特点,选择合适的耦合类型。尽量使用数据耦合,少用标记耦合和控制耦合,限制使用公共环境耦合,完全不用内容耦合。具体做法有:降低模块接口的复杂性,减少每个模块的参数个数,尽量使用标准过程调用,少用直接引用,以标准的形式直接传递数据,把模块的通信信息放在缓冲区中。
(2)内聚。内聚标志了一个模块内部各元素彼此结合的紧密程度。它是信息隐藏和局部化概念的自然扩展。理想的内聚是每个模块只做一件事。设计软件结构时应该力求做到高内聚,中等程度的内聚也是可以接受的选择。内聚和耦合密切相关,内聚高往往意味着耦合松散。内聚和耦合都是设计软件结构时需要考虑的结构特征,但是实践经验表明,内聚更重要,应该把更多的注意力集中到提高模块的内聚程度上。这类似于每个人做好自己,最终形成良好的社会的价值观,因为通过统筹管理获得全面提高的难度显然更大。
按照内聚程度的不同,内聚可以分为7种情况,如图4.5所示。

图4.5 内聚类型
①偶然内聚。模块内各元素之间没有实质性的联系,或者说模块内各组成成分在功能上互不相关。偶然内聚是强度最低的一种内聚,模块内各元素不是为同一个功能服务,各有不同需求,因而模块的内容不易理解,也不易修改和维护。
②逻辑内聚。如果一个模块完成的任务在逻辑上属于相同或相似的一类,各元素之间存在必然的逻辑关系,则它们之间的关系属于逻辑内聚。典型的情况是把几种相关的功能组合在一个模块内,由传递给模块的参数确定每次调用执行哪种功能。逻辑内聚会存在不同功能混在一起,共用部分程序代码的情况,在遇到需要修改模块时,往往会影响全局,使修改比较困难。
③时间内聚。一个模块包含的任务必须在同一时间段内执行,这些功能仅是因为时间因素被组织在一起。例如,初始化工作,同时打开文件、关闭文件,紧急故障处理等都是时间内聚模块。一般情况下,各部分只要满足时间上的同一性,执行顺序可以任意安排。
④过程内聚。如果一个模块内的元素是相关的,而且必须以特定次序执行,受同一控制流支配则称为过程内聚。使用程序流程图设计程序时,确定的模块划分往往得到过程内聚的模块。例如,把流程图中的循环部分、判定部分、计算部分分成3个模块,这3个模块是过程内聚模块。
⑤通信内聚。如果模块中所有元素都使用相同的输入数据或产生相同的输出数据,则称为通信内聚。
⑥顺序内聚。如果一个模块内的处理元素和同一个功能密切相关,而且这些处理必须顺序执行则称为顺序内聚。通常是,一个处理元素的输出作为下一个处理元素的输入,即在同一个数据结构上操作。根据数据流图划分模块时,往往得到顺序内聚的模块。
⑦功能内聚。模块内的所有处理元素属于一个整体,即完成一个单一的功能。功能内聚是最高程度的内聚,是设计的目标和理想的结构。
在实际设计中,没有必要精确确定内聚的级别,重要的是力争做到高内聚,并且能辨认出低内聚模块,通过修改结构提高内聚程度。