独立博客链接地址:http://zhuyanfeng.com/archives/221
模块概念
软件结构设计是对组成系统的各个子系统的进一步分解与规划。例如,将子系统按照其功能要素分解成具有一定的功能边界的模块,然后以模块为单位来构造软件。需求分析阶段已经建立起的有关系统的功能模型、数据模型或状态机模型,可以作为软件结构设计的前提依据。
(1)确定构造子系统的模块元素。
(2)定义每个模块的功能。
(3)定义模块接口,设计接口的数据结构。
(4)确定模块之间的调用与返回关系。
(5)评估软件结构质量,进行结构优化。
1.模块化
模块概念产生于结构化程序设计思想,这时的模块被作为构造程序的基本单元,例如函数、过程。
在结构化方法中,模块是一个功能单位,因此模块可大可小。它可以被理解为所建软件系统中的一个子程序系统,也可以是子程序系统内一个涉及多项任务的功能程序块,并可以是功能程序块内的一个程序单元,例如函数、过程。也就是说,模块实际上体现出了系统所具有的功能层次结构。
模块可以使软件系统按照其功能组成进行分解,而通过对软件系统进行分解,则可以使一些大的复杂的软件问题分解成诸多小的简单的软件问题。从软件开发的角度来看,这必然有利于软件问题的有效解决。
模块化可以使软件问题简化。但是,得出这个结论需要有下面的前提条件:大模块被分解成诸多小模块之后,小模块基本上处于相互隔离的独立状态,它们之间没有太多的通信。否则,小模块之间由于存在太多的通信,将会导致小模块接口复杂起来,其结果是,虽然每个模块内部简化了,但整个软件问题反而复杂起来。
2.抽象化
概要设计中的功能模块往往被看成是一个抽象化的功能黑盒子,其特征如下图所示。虽然它已是一个与软件实现直接相关的实体单元,可以看到它清晰的外观,但是却看不到它内部更加具体的实现细节。
应该说,概要设计中的结论,对于软件内部构造而言则仍是抽象的。比如概要设计中的功能模块,尽管它有惟一的名称标识,有明确的功能定义,并设置有数据的输入输出接口。但是,模块的内部实现细节则处于待定状态,需要等到详细设计完成以后才能得出更加具体的算法结论。这种由抽象到具体的不断演变也一直贯穿于软件工程过程之中,这就是自顶向下、逐步细化。
3.信息隐蔽
信息隐蔽是指每个模块的内部实现细节对于其他模块来说是隐蔽的。也就是说,模块中所包含的信息,例如,模块内部的数据、语句或过程等,不允许其他不需要这些信息的模块使用。显然,信息隐蔽有利于模块相互之间的隔离,可以使每个模块更加具有独立性,并可以使模块之间的通信受到限制。例如,模块之间只能传递那些对于其功能实现而言是必须的信息。
通过限制模块需要使用的数据的作用范围,例如,定义模块内部局部变量,可以产生出模块内部信息隐蔽的效果。
模块内部信息隐蔽的好处是可以使软件系统更加健壮,更加方便维护。
以模块中的错误为例,假如模块内部信息是隐蔽的,则模块中存在的这个错误将比较难于扩散到其他模块。否则,系统可能因为一个小错误的扩散,而使整个系统崩溃。实际上,信息隐蔽还使软件错误定位更加方便。由于软件出错位置容易发现,因此,整个软件纠错工作的效率、质量都会随之提高。
一些模块的功能有时需要根据用户需求的变更而适时地进行一些功能改造。当需要对模块进行功能改造时,模块内部的信息隐蔽会使对模块改造所带来的影响限制在需要改造的模块之内,而与其他模块无关。显然,这将使软件系统的局部修改变得更加便利。
模块的独立性
模块的独立性是指软件系统中每个模块都只涉及自己特定的子功能,并且模块接口简单,与软件中其他模块没有过多的联系。
模块独立性是衡量软件中模块质量最重要的指标,是设计与优化软件结构时必须考虑的重要因素。当软件中的每个模块都具有很好的独立性时,软件系统不仅更加容易实现,并且会使今后的维护更加方便。
模块的独立性一般采用耦合和内聚这两个定性的技术指标进行度量。其中,耦合用来反映模块之间互相连接的紧密程度,模块之间的连接越紧密,联系越多,耦合性就越高。内聚用来反映模块内部各个元素彼此结合的紧密程度,一个模块内部各个元素之间结合越紧密,则它的内聚性就越高。显然,为了使模块具有较强的独立性,要求模块是高内聚、低耦合。
1.耦合
模块之间的耦合形式主要有:非直接耦合、数据耦合、控制耦合、公共耦合和内容耦合。
其中,非直接耦合和数据耦合是较弱的耦合,控制耦合和公共耦合是中等程度的耦合,内容耦合则是强耦合。
模块化设计的目标是尽量建立模块间耦合松散的系统。因此,在设计软件结构时一般也就要求尽量采用非直接耦合和数据耦合,少用或限制使用控制耦合和公共耦合,绝对不能使用内容耦合。
(1)非直接耦合
如果两个模块之间没有直接关系,它们之间的联系仅限于受到共同主模块的控制与调用,则称这种耦合为非直接耦合。由于非直接耦合的模块之间没有数据通信,因此模块的独立性最强。
(2)数据耦合
当一个模块访问另一个模块时,如果彼此之间是通过模块接口处的参数实现通信,并且参数所传递的数据仅用于计算,而不会影响传入参数模块的内部程序执行路径,则称这种耦合为数据耦合。
数据耦合是一种松散的耦合。依靠这种类型的耦合,模块之间既能实现通信,又有比较强的独立性。因此,软件结构设计中提倡使用这类耦合。
(3)控制耦合
当一个模块访问另一个模块时,如果彼此之间是通过模块接口处的参数实现通信,并且参数传递了开关、标志、名字等控制信息,由此影响了传入参数模块的内部程序执行路径,则称这种耦合为控制耦合。
由于接口参数影响着内部程序执行路径,因此控制耦合比数据耦合要强一些,会使模块的独立性有所下降。当需要通过一个单一的接口传递模块内多项功能的选择信息时,往往需要用到控制耦合。显然,在控制耦合形式下,对所控制模块的修改,需要受到控制参数的限制。
(4)
公共耦合是一种通过访问公共数据环境而实现通信的模块耦合形式,例如,独立于模块而存在的数据文件、数据表集、公共变量等,都可以看作为公共数据环境。相比依靠接口的耦合形式,公共耦合必然会使模块的独立性下降。实际应用中,只有在模块之间需要共享数据,并且通过接口参数传递不方便时,才使用公共耦合。
上图中(a)是一个模块只往公共数据环境里送进数据,另一个模块只从公共数据环境中取出数据,这是一种比较松散的公共耦合;而图(b)则是两个模块都可以向公共数据环境里送进数据,又都可以从公共数据环境中取出数据,这就是一种比较紧密的公共耦合。
另外,公共耦合还会带来以下一些方面的影响:
• 由于所有公共耦合模块都会与某一个公共数据环境有关,因此修改公共数据环境中某个数据的结构,将会影响到所有被耦合的模块。
• 由于没有办法控制各个模块对公共数据的存取,因此公共耦合会影响软件模块的可靠性和适应性。
• 由于公共数据环境需要被许多模块使用,因此不得不使用具有公共意义的数据名称。显然,这会使得程序的可读性有所下降。
(5)内容耦合
如果发生下列情形,两个模块之间就发生了内容耦合。
• 一个模块直接访问另一个模块的内部数据。
• 一个模块不通过正常入口转到另一模块内部。
• 两个模块有一部分程序代码重叠。
• 一个模块有多个入口。
内容耦合是一种非常强的耦合形式,严重影响了模块独立性。当模块之间存在内容耦合时,模块的任何改动都将变得非常困难,一旦程序有错则很难修正。因此,设计软件结构时,也就要求绝对不要出现内容耦合。
2.内聚
内聚是对模块内部各个元素彼此结合的紧密程度的度量。模块内部各个元素之间的联系越紧密,则它的内聚程度就越高。模块的设计目标是尽量使模块的内聚程度高,以达到模块独立、功能集中的目的。
模块内聚的主要类型有:功能内聚、信息内聚、通信内聚、过程内聚、时间内聚、逻辑内聚和偶然内聚,其中,功能内聚和信息内聚属于高内聚,通信内聚和过程内聚属于中等程度的内聚,时间内聚、逻辑内聚和偶然内聚则属于低内聚。
内聚所体现的是模块的内部功能构造,耦合所体现的是模块之间的联系。一般情况下,内聚和耦合是相互关联的,模块的内聚程度越高,则模块间的耦合程度就会越低,但这也不是绝对的。
(1)偶然内聚
当模块内各部分之间没有联系,或即使有联系,这种联系也很松散时,将会出现偶然内聚。偶然内聚往往产生于对程序的错误认识或没有进行软件结构设计就直接编程。例如,一些编程人员可能会将一些没有实质联系,但在程序中重复多次出现的语句抽出来,组成一个新的模块,这样的模块就是偶然内聚模块。偶然内聚模块由于是随意拼凑而成,模块内聚程度最低、功能模糊,很难进行维护。
(2)逻辑内聚
逻辑内聚是把几种相关的功能组合在一起形成为一个模块。在调用逻辑内聚模块时,可以由传送给模块的判定参数来确定该模块应执行哪一种功能。
逻辑内聚模块比偶然内聚模块的内聚程度要高,因为它表明了各部分之间在功能上的相关关系。另外,在调用逻辑内聚模块时,需要进行控制参数的传递,由此增加了模块间的耦合。
(3)时间内聚
时间内聚模块一般是多功能模块,其特点是模块中的各项功能的执行与时间有关,通常要求所有功能必须在同一时间段内执行。例如初始化模块,其功能可能包括给变量赋初值、连接数据源、打开数据表、打开文件等,这些操作要求在程序开始执行的最初一段时间内全部完成。时间内聚模块比逻辑内聚模块的内聚程度又稍高一些,其内部逻辑比较简单,一般不需要进行判定转移。
(4)过程内聚
如果一个模块内的处理是相关的,而且必须以特定次序执行,则称之为过程内聚模块。在使用流程图设计程序的时侯,常常通过流程图来确定模块划分,由此得到的就往往是过程内聚模块。例如,可以根据流程图中的循环部分、判定部分和计算部分将程序分成三个模块,这三个模块就是过程内聚模块。过程内聚模块的内聚程度比时间内聚模块的内聚程度更强一些,但过程内聚模块仅包括完整功能的一部分,因此模块之间的耦合程度比较高。
(5)通信内聚
如果一个模块内各功能部分都使用了相同的输入数据或产生了相同的输出数据,则称之为通信内聚模块。例如下图中的处理工资模块。通信内聚模块由一些独立的功能组成,因此其内聚程度比过程内聚程度要高。
(6)顺序内聚
如果一个模块内的诸多功能元素都和某一个功能元素密切相关,而且这些功能元素必须顺序安排(前一项功能的数据输出作为后一项功能的数据输入),则称之为顺序内聚模块。当根据数据流图划分模块时,由此得到的通常就是顺序内聚模块。
(7)功能内聚
如果一个模块中各个部分都是完成某一具体功能必不可少的组成部分,各个部分协同工作、紧密联系、不可分割,则称该模块为功能内聚模块。功能内聚模块的特征是功能单一、接口简单,因此其容易实现、便于维护。与其他内聚类型相比,功能内聚具有最高的内聚程度,软件结构设计时应以其作为追求目标。
结构化设计建模
软件结构设计涉及模块功能、模块接口与模块调用关系等问题,为了使这些问题能够集中清晰地表达出来,软件结构设计需要借助于一定的图形工具来建立设计模型,例如软件结构图、HIPO 图。
1.软件结构图
软件结构图使用矩形框表示模块(框内注明模块的名字或主要功能),使用带箭头的直线段连接上下级模块,以表示上级模块对下级模块的调用。此外,软件结构图还可以在调用箭头旁使用带注释的箭头,以表示上级模块在调用下级模块时参数的传递与结果的返回,其基本图形符号如下图所列。
下图是一个自动阅卷系统的软件结构图。
软件结构图是一种画法比较灵活的软件结构设计工具,能够对复杂软件系统进行有效的描述,比较适合于设计人员在设计过程中对软件结构进行构思与设计说明。但软件结构图需要依靠带箭头的附加信息表示模块调用时的数据流和控制流,当软件结构图中包含了太多附加信息时,无形中会降低整个构图的清晰度。因此,软件结构图不太适合在正式文档中使用。
2.HIPO 图
HIPO 图是美国IBM 公司推出的“H 图”与“IPO 图”的组合。
(1)H 图
H 图是软件层次图的简称,用于描述软件结构上的分层调用关系,作用类似于软件结构图,但不涉及调用时的数据流、控制流等附加信息。
H 图的优点是清晰度高,能够用于正式文档中对软件结构的描述。下图是自动阅卷系统的H 图,可以看出,它比软件结构图要清晰得多。为了能使H 图中的模块具有可追踪性,由此可以与它所对应的IPO 图联系起来。图中模块除了最顶层的之外,其他的模块都需要按照一定的规则编号,以方便检索。
(2)IPO 图
IPO 图是“输入—处理—输出图”的简称,其中的“I”是指输入,“O”是指输出,“P”是指处理。可以使用IPO 图对模块进行外部特征描述,涉及输入、输出接口和基本加工步骤等。在HIPO 图中,需要针对H 图中的每个模块给出IPO 图描述。
如下图是“2. 阅卷处理”模块的IPO 图。
软件结构优化
软件结构设计往往需要经历多次反复,其作用是可以不断地改进软件结构,下面是一些优化设计的原则。
1.使模块功能完整
一个完整的功能模块,不仅能够完成指定的功能,而且还应当能够将完成任务的状态通知使用者。具体说来,一个完整的功能模块应当包含以下几个部分的内容:
(1)执行规定功能的部分。
(2)出错处理的部分。当模块不能完成规定的功能时,必须回送出错标志,并向它的调用者报告出现这种例外情况的原因。
(3)如果需要返回一系列数据给它的调用者,在完成数据加工时,应当给它的调用者返回一个该模块执行是否正确结束的“标志”。
2.使模块大小适中
许多情况下,可以用模块中所含语句的数量的多少来衡量模块的大小。有人认为限制模块的大小也是减少模块复杂性的有效手段之一,因而要求把模块的大小限制在一定的范围之内,例如将模块内的语句限制在50~100 行左右。当然,这只能作为参考。
一般说来,模块过大的原因往往是模块的功能太多,因此有必要对大模块做进一步的分解。大多数情况下,可以从大模块中分解出一些下级模块或同层模块。软件结构设计时,也有可能出现模块过小的问题。如果模块太小,则要注意这个模块的功能是否完整,是否将一个完整的功能分解到多个模块中去了。许多情况下,可以考虑将较小的模块与调用它的上级模块合并。如果模块虽小但功能内聚性好,或者它为多个模块所共享,或者调用它的上级模块很复杂,则不要贸然将小模块与其他模块合并,而是需要做更加细致的分析。
3.使模块功能可预测
一个功能可预测的模块可以被看成是一个“黑箱”,无论内部处理细节如何,只要输入数据是相同的,就总能产生相同的输出结果。
例如,在模块中使用了静态变量。因为静态变量会在模块内部产生较难预见的存储,而如果这个静态变量又被用为多项功能的选择标记,则可能会使得模块的功能难以被调用者预知。由于这个原因,设计者要特别慎重地使用静态变量。模块功能可预测还意味着,模块的功能必须明确清晰地定义,应该使模块具有很好的内聚性。
4.尽量降低模块接口的复杂程度
模块接口是模块与外界进行通信的通道,较复杂的接口往往会带来较高的耦合。因此,应努力降低模块接口的复杂程度。对此,可以从以下两个方面作出考虑。
(1)接口参数尽量采用简单数据类型,以使接口容易理解。
(2)限制接口参数的个数。过多的参数往往意味着模块还有进一步分解的必要。
5.使模块作用范围限制在其控制范围之内
模块的控制范围包括它本身及其所有从属于它的直接或间接下级模块。模块的作用范围则是指模块内一个判定的影响范围,凡是受这个判定影响的所有模块都属于这个判定的作用范围。其中,如果一个判定的作用范围包含在这个判定所在模块的控制范围之内,则这种结构是简单的;否则,其结构就是不简单的,需要进行结构调整。
例如,下图中的模块F。如果它做出一个判定之后,接着需要模块G 工作,由于模块G不在模块F 控制范围之内,因此模块F 必须返回一个信号给模块B,再由B 把信号传送给模块G。显然这不是一个好的设计,它增加了模块之间数据的传送量,并使模块之间出现了控制耦合,因此需要对其结构进行调整。
在设计过程中,如果发现模块的作用范围不在控制范围之内时,可以采用如下办法进行结构调整,把模块的作用范围移到其控制范围之内。
(1)将判定所在的模块合并到它的父模块中去,或上移到层次较高的位置上去,由此可使判定处于一个较高的位置,以达到有效的控制。例如,将模块F 与模块B 合并,由此可使模块G 受到控制。
(2)将受判定影响的模块下移到控制范围以内。例如,将模块G 下移到模块F 之下,由此可使模块G 受到模块F 控制。需要注意的是:在改进模块的结构时,应当根据具体情况通盘考虑,既要尽量小范围地调整结构,使改进后的软件结构能够最好地体现问题的原来结构,又要考虑改进后的结构在实现上的可行性。
6.深度、宽度、扇出和扇入应当适当
软件的深度是指软件结构的层数。软件的宽度是指软件结构同一个层次上模块的总个数的最大值。软件结构上的深度、宽度,在一定程度上反映出了软件系统的规模和复杂程度。也就是说,软件规模越大越复杂,其深度、宽度也会越大。一般说来,深度、宽度会受模块的扇出、扇入影响,并应该有一个合适的大小。深度、宽度太小,往往表示模块分解得还不够细,需要进一步分解。而深度、宽度太大,则可能表示模块分解得太细小了,或功能冗余模块太多了,以致软件结构变得复杂起来。一般说来,软件结构越复杂,模块之间的耦合就会越大。因此,假如软件结构过于复杂,就有必要将一些模块进行适当的合并,将那些功能相同或相似的模块合并起来作为公共模块使用。
模块的扇出是指模块直接调用的下级模块的个数。比较适当的扇出数为2~5,一般不要超过9。如果一个模块的扇出过大,就表明该模块具有过分复杂的控制功能,需要协调和控制过多的下属模块。对此,应当适当增加中间层次的控制模块,将比较集中的控制分解开来。但扇出过小也不好,这样将使得软件结构的深度大大增加,会带来过多的调用和返回的时间开销,由此降低软件的工作效率。
模块的扇入是指模块受到了多少个直接上级模块的调用。一个模块的扇入越大,则共享该模块的上级模块的数目就越多。如果一个模块的扇入数太大,而它又不是公用模块,则说明该模块可能具有多项功能。在这种情况下,应当对它做进一步的分析,并将其功能做进一步的分解。
一般说来,各个不同层次的模块具有以下特点:顶层模块起全局控制作用;中间层次的模块起局部控制作用,并适当承担一些简单的操作提示功能;底层模块担任具体加工任务。因此,一个设计得比较好的软件结构通常应具有这样的特征:顶层模块高扇出,中层模块低扇出,底层模块高扇入。
以上内容整理自《软件工程》,作者:曾强聪