一、联系
当一个程序段或语句(指令) 引用了其它程序段或语句(指令)中所定义或使用的数据名(即存贮区、地址等)或代码时,他们之间就发生了联系。一个程序被划分为若干模块时,联系既可存在于模块之间,也可存在于一个模块内的程序段或语句之间,即模块内部。联系反映了系统中程序段或语句之间的关系,不同类型的联系构成不同质量的系统。因此,联系是系统设计必须考虑的重要问题。
系统被分成若干模块后,模块同模块的联系称为块间联系;一个模块内部各成份的联系称为块内联系。显然,模块之间的联系多,则模块的相对独立性就差,系统结构就混乱;相反,模块间的联系少,各个模块相对独立性就强,系统结构就比较理想。同时,一个模块内部各成份联系越紧密,该模块越易理解和维护。
二、评判模块结构的标准
1.模块独立性
模块化是软件设计和开发的基本原则和方法,是概要设计最主要的工作。模块的划分应遵循一定的要求,以保证模块划分合理,并进一步保证以此为依据开发出的软件系统可靠性强,易于理解和维护。根据软件设计的模块化、抽象、信息隐蔽和局部化等原则,可直接得出模块化独立性的概念。所谓模块独立性,即:不同模块相互之间联系尽可能少,应尽可能减少公共的变量和数据结构;一个模块应尽可能在逻辑上独立,有完整单一的功能。
模块独立性(Module independence)是软件设计的重要原则。具有良好独立性的模块划分,模块功能完整独立,数据接口简单,程序易于实现,易于理解和维护。独立性限制了错误的作用范围,使错误易于排除,因而可使软件开发速度快,质量高。
为了进一步测量和分析模块独立性,软件工程学引入了两个概念,从两个方面来定性地度量模块独立性的程度,这两个概念是模块的内聚度和模块的耦合度。
2.块间联系的度量—耦合度
耦合度是从模块外部考察模块的独立性程度。它用来衡量多个模块间的相互联系。一般来说,耦合度应从以下三方面来考虑,即:
耦合内容的数量,即模块间发生联系的数据和代码的多少,同这些数据和代码发生联系的模块的多少,多的耦合强,少的耦合弱;
模块的调用方式,即模块间代码的共享方式。可分为用CALL语句调用方式和用GOTO语句直接访问方式;
模块间的耦合类型。耦合类型有以下几种方式:
①独立耦合
②数据耦合
③控制耦合
④公共耦合
⑤内容耦合
下面重点对各种类型的耦合作进一步的说明。
(1)独立耦合
指两个模块彼此完全独立,没有直接联系。它们之间的唯一联系仅仅在于它们同属于一个软件系统或同有一个上层模块。这是耦合程度最低的一种。当然,系统中只可能有一部分模块属此种联系,因为一个程序系统中不可能所有的模块都完全没有联系。
(2)数据耦合
指两个模块彼此交换数据。如一个模块的输出数据是另一个模块的输入数据,或一个模块带参数调用另一个模块,下层模块又返回参数。应该说,在一个软件系统中,此种耦合是不可避免的,且有其积极意义。因为任何功能的实现都离不开数据的产生、表示和传递。数据耦合的联系程度也较低。
(3)控制耦合
若在调用过程中,两个模块间传递的不是数据参数而是控制参数,则模块间的关系即为控制耦合。控制耦合属于中等程度的耦合,较之数据耦合模块间的联系更为紧密。但控制耦合不是一种必须存在的耦合。
当被调用模块接收到控制信息作为输入参数时,说明该模块内部存在多个并列的逻辑路径,即有多个功能。控制变量用以从多个功能中选择所要执行的部分,因而控制耦合是完全可以避免的。排除控制耦合可按如下步骤进行:
①找出模块调用时所用的一个或多个控制变量;
②在被调模块中根据控制变量找出所有的流程;
③将每一个流程分解为一个独立的模块;
④将原被调模块中的流程选择部分移到上层模块,变为调用判断。
通过以上变换,可以将控制耦合变为数据耦合。由于控制耦合增加了设计和理解的复杂程度,因此在模块设计时要尽量避免使用。当然,如果模块内每一个控制流程规模相对较小,彼此共性较多,使用控制耦合还是合算的。
(4)公共耦合
公共耦合又称公共环境耦合或数据区耦合。若多个模块对同一个数据区进行存取操作,它们之间的关系称为公共耦合。公共数据区可以是全程变量、共享的数据区、内存的公共复盖区、外存上的文件、物理设备等。当两个模块共享的数据很多,通过参数传递可能不方便时,可以使用公共耦合。公共耦合共享数据区的模块越多,数据区的规模越大,则耦合程度越强。公共耦合最弱的一种形式是:两个模块共享一个数据变量,一个模块只向里写数据,另一个模块只从里读数据。
当公共耦合程度很强时,会造成关系错综复杂,难以控制,错误传递机会增加,系统可靠性降低,可理解、维护性差。
(5)内容耦合
内容耦合是耦合程序最高的一种形式。若一个模块直接访问另一模块的内部代码或数据,即出现内容耦合。内容耦合的存在严重破坏了模块的独立性和系统的结构化,代码互相纠缠,运行错综复杂,程序的静态结构和动态结构很不一致,其恶劣结果往往不可预测。
内容耦合往往表现为以下几种形式:
①一个模块访问另一模块的内部代码或数据;
②一个模块不通过正常入口而转到另一个模块的内部(如使用GOTO语句或JMP指令直接进入另一模块内部);
③两个模块有一部分代码重迭(可能出现在汇编程序中,在一些非结构化的高级语言,如COBOL中也可能出现);
④一个模块有多个入口(这意味着一个模块有多种功能)。
一般讲,在模块划分时,应当尽量使用数据耦合,少用控制耦合(尽量转成数据耦合),限制公共耦合的范围,完全不用内容耦合。
3.块内联系的度量——内聚度
内聚度(Cohesion)是模块内部各成份(语句或语句段)之间的联系。显然,模块内部各成份联系越紧,即其内聚度越大,模块独立性就越强,系统越易理解和维护。具有良好内聚度的模块应能较好地满足信息局部化的原则,功能完整单一。同时,模块的高内聚度必然导致模块的低耦合度。理想的情况是:一个模块只使用局部数据变量,完成一个功能。
按由弱到强的顺序,模块的内聚度可分为以下7类,现分述于下。
(1)偶然内聚
块内的各个任务(通过语句或指令来实现的)没有什么有意义的联系,它们之所以能构成一个模块完全是偶然的原因。如下图3-4-1所示。
在模块T有三条语句。至少从表面上看不出这三条语句之间有什么联系,只是由于P,Q,R,S四个模块中都有这三条语句,为了节省空间才把它们作为一个模块放在一起。这完全是偶然性的。偶然内聚的模块有很多缺点:由于模块内
没有实质性的联系,很可能在某种情况下一个调用模块需要对它修改而别的模块不需要。这时就很难处理。同时,这种模块的含义也不易理解,甚至难以为它取一个合适的名字,偶然内聚的模块也难于测试。所以,在空间允许的情况下,不应使用这种模块。
(2)逻辑内聚
一个模块完成的任务在逻辑上属于相同或相似的一类(例如,用一个模块产生各种类型的输出),则该种模块内的联系称为逻辑内聚。如图3-4-2a和3-2-2b所示。
在图3-4-2a中,模块A,B,C的功能相似但不相同,如果把它们合并成一个模块ABC,如图3-4-2b所示,则这个模块就为逻辑内聚,因为它们是由于逻辑上相似而发生联系的。逻辑内聚是一种较弱的联系。实际执行时,当X,Y,Z调用合成的模块ABC时,由于原A,B,C并不完全相同,所以还要判别是执行不同功能的哪一部分。
逻辑内聚存在的问题是:
①修改困难,调用模块中有一个要对其改动,还要考虑到其它调用模块;
②模块内需要增加开关,以判别是谁调用,因而增加了块间联系;
③实际上每次调用只执行模块中的一部分,而其它部分也一同被装入内存,因而效率不高。
(3)时间内聚
时间内聚是指一个模块中包含的任务需要在同一时间内执行(如初始化,结束等所需操作)。如图3—4—3所示的模块。与偶然内聚和逻辑内聚相比,这种内聚类型要稍强些,因为至少在时间上,这些任务可以一起完成。但时间内聚和偶然内聚、逻辑内聚一样,都属低内聚度类型。
(4)过程内聚
如果一个模块内的各个处理元素是相关的,而且必须按固定的次序执行,这种内聚就叫做过程内聚。过程内聚的各模块内往往体现为有次序的流程。如图3-4-4所示的处理模块。
(5)通信内聚
若一个模块中的各处理元素需引用共同的数据(同一数据项、数据区或文件),则称其元素间的联系为通信内聚。通信内聚的各部分间是借助共同使用的数据联系在一起的,故有较好的可理解性。如图3—4—5所示。通信内聚和过程内聚都属中内聚度型模块。
(6)顺序内聚
若一个模块内的各处理元素关系密切,必须按规定的处理次序顺序执行,这样的模块为顺序内聚类型。顺序内聚的模块内,后执行的语句或语句段往往依赖先执行的语句或语句段,以先执行的部分为条件。由于模块内各处理元素间存在着这种逻辑联系,所以顺序内聚模块的可理解性很强,属高内聚度类型模块。如图3-4-6所示的例子。
(7)功能内聚
功能内聚是内聚度最高的一种模块类型。如果模块仅完成一个单一的功能,且该模块的所有部分是实现这一功能所必须的,没有多余的语句,则该模块为功能内聚。功能内聚模块的结构紧凑、界面清晰,易于理解和维护,因而可靠性强;又由于其功能单一,故复用率高。所以它是模块划分时应注意追求的一种模块类型。如图3—4—7是模块划分时得到的功能内聚模块。
在模块设计时应力争做到高内聚,并且能够辨别出低内聚的模块,加以修改使之提高内聚度并降低模块间的耦合度。具体设计时,应注意:
①设计功能独立单一的模块;
②控制使用全局数据;
③模块间尽量传递数据型信息。