资源描述
单击此处编辑母版标题样式,单击此处编辑母版副标题样式,第,5,章 总体设计,5.1,设计过程,5.2,设计原理,5.3,启发规则,5.4,描绘软件结构的图形工具,5.5,面向数据流的设计方法,5.6,小结,习题,第5章 总体设计5.1 设计过程,总体设计的基本目的就是回答“概括地说,系统应该如何实现,?”,这个问题,因此,总体设计又称为概要设计或初步设计。通过这个阶段的工作将划分出组成系统的物理元素,程序、文件、数据库、人工过程和文档等等,但是每个物理元素仍然处于黑盒子级,这些黑盒子里的具体内容将在以后仔细设计。总体设计阶段的另一项重要任务是设计软件的结构,也就是要确定系统中每个程序是由哪些模块组成的,以及这些模块相互间的关系。,总体设计的基本目的就是回答“概括地说,系统应该如何实现?”这,总体设计过程首先寻找实现目标系统的各种不同的方案,需求分析阶段得到的数据流图是设想各种可能方案的基础。然后分析员从这些供选择的方案中选取若干个合理的方案,为每个合理的方案都准备一份系统流程图,列出组成系统的所有物理元素,进行成本,/,效益分析,并且制定实现这个方案的进度计划。分析员应该综合分析比较这些合理的方案,从中选出一个最佳方案向用户和使用部门负责人推荐。如果用户和使用部门的负责人接受了推荐的方案,分析员应该进一步为这个最佳方案设计软件结构,通常,设计出初步的软件结构后还要多方改进,从而得到更合理的结构,进行必要的数据库设计,确定测试要求并且制定测试计划。,总体设计过程首先寻找实现目标系统的各种不同的方案,需求分析阶,从上面的叙述中不难看出,在详细设计之前先进行总体设计的必要性:可以站在全局高度上,花较少成本,从较抽象的层次上分析对比多种可能的系统实现方案和软件结构,从中选出最佳方案和最合理的软件结构,从而用较低成本开发出较高质量的软件系统。,从上面的叙述中不难看出,在详细设计之前先进行总体设计的必要性,总体设计过程通常由两个主要阶段组成:系统设计阶段,确定系统的具体实现方案;结构设计阶段,确定软件结构。典型的总体设计过程包括下述,9,个步骤:,1.,设想供选择的方案,在总体设计阶段分析员应该考虑各种可能的实现方案,并且力求从中选出最佳方案。在总体设计阶段开始时只有系统的逻辑模型,分析员有充分的自由分析比较不同的物理实现方案,一旦选出了最佳的方案,将能大大提高系统的性能,/,价格比。,5.1,设计过程,总体设计过程通常由两个主要阶段组成:系统设计阶段,确定系统的,需求分析阶段得出的数据流图是总体设计的极好的出发点。,设想供选择的方案的一种常用的方法是,设想把数据流图中的处理分组的各种可能的方法,抛弃在技术上行不通的分组方法,(,例如,组内不同处理的执行时间不相容,),,余下的分组方法代表可能的实现策略,并且可以启示供选择的物理系统。,需求分析阶段得出的数据流图是总体设计的极好的出发点。,2.,选取合理的方案,应该从前一步得到的一系列供选择的方案中选取若干个合理的方案,通常至少选取低成本、中等成本和高成本的三种方案。在判断哪些方案合理时应该考虑在问题定义和可行性研究阶段确定的工程规模和目标,有时可能还需要进一步征求用户的意见。,对每个合理的方案分析员都应该准备下列,4,份资料:,(1),系统流程图;,(2),组成系统的物理元素清单;,(3),成本,/,效益分析;,(4),实现这个系统的进度计划。,2. 选取合理的方案,3.,推荐最佳方案,分析员应该综合分析对比各种合理方案的利弊,推荐一个最佳的方案,并且为推荐的方案制定详细的实现计划。制定详细实现计划的关键技术是本书第,13,章中将要介绍的工程网络。,用户和有关的技术专家应该认真审查分析员所推荐的最佳系统,如果该系统确实符合用户的需要,并且是在现有条件下完全能够实现的,则应该提请使用部门负责人进一步审批。在使用部门的负责人也接受了分析员所推荐的方案之后,将进入总体设计过程的下一个重要阶段,结构设计。,3. 推荐最佳方案,4.,功能分解,为了最终实现目标系统,必须设计出组成这个系统的所有程序和文件,(,或数据库,),。对程序,(,特别是复杂的大型程序,),的设计,通常分为两个阶段完成:首先进行结构设计,然后进行过程设计。结构设计确定程序由哪些模块组成,以及这些模块之间的关系;过程设计确定每个模块的处理过程。结构设计是总体设计阶段的任务,过程设计是详细设计阶段的任务。,4. 功能分解,为确定软件结构,首先需要从实现角度把复杂的功能进一步分解。分析员结合算法描述仔细分析数据流图中的每个处理,如果一个处理的功能过分复杂,必须把它的功能适当地分解成一系列比较简单的功能。一般说来,经过分解之后应该使每个功能对大多数程序员而言都是明显易懂的。功能分解导致数据流图的进一步细化,同时还应该用,IPO,图或其他适当的工具简要描述细化后每个处理的算法。,为确定软件结构,首先需要从实现角度把复杂的功能进一步分解。分,5.,设计软件结构,通常程序中的一个模块完成一个适当的子功能。应该把模块组织成良好的层次系统,顶层模块调用它的下层模块以实现程序的完整功能,每个下层模块再调用更下层的模块,从而完成程序的一个子功能,最下层的模块完成最具体的功能。软件结构,(,即由模块组成的层次系统,),可以用层次图或结构图来描绘,第,5.4,节将介绍这些图形工具。,如果数据流图已经细化到适当的层次,则可以直接从数据流图映射出软件结构,这就是第,5.5,节中将要讲述的面向数据流的设计方法。,5. 设计软件结构,6.,设计数据库,对于需要使用数据库的那些应用系统,软件工程师应该在需求分析阶段所确定的系统数据需求的基础上,进一步设计数据库。,在数据库课中已经详细讲述了设计数据库的方法,本书不再赘述。,7.,制定测试计划,在软件开发的早期阶段考虑测试问题,能促使软件设计人员在设计时注意提高软件的可测试性。本书第,7,章将仔细讨论软件测试的目的和设计测试方案的各种技术方法。,6. 设计数据库,8.,书写文档,应该用正式的文档记录总体设计的结果,在这个阶段应该完成的文档通常有下述几种:,(1),系统说明主要内容包括用系统流程图描绘的系统构成方案,组成系统的物理元素清单,成本,/,效益分析;对最佳方案的概括描述,精化的数据流图,用层次图或结构图描绘的软件结构,用,IPO,图或其他工具,(,例如,,PDL,语言,),简要描述的各个模块的算法,模块间的接口关系,以及需求、功能和模块三者之间的交叉参照关系等等。,(2),用户手册根据总体设计阶段的结果,修改更正在需求分析阶段产生的初步的用户手册。,8. 书写文档,(3),测试计划包括测试策略,测试方案,预期的测试结果,测试进度计划等等。,(4),详细的实现计划,(5),数据库设计结果,9.,审查和复审,最后应该对总体设计的结果进行严格的技术审查,在技术审查通过之后再由使用部门的负责人从管理角度进行复审。,(3) 测试计划包括测试策略,测试方案,预期的测试结果,测试,模块是由边界元素限定的相邻程序元素(例如,数据说明,可执行的语句)的序列,而且有一个总体标识符代表它。按照模块的定义,过程、函数、子程序和宏等,都可作为模块。面向对象方法学中的对象是模块,对象内的方法(或称为服务)也是模块。模块是构成程序的基本构件。,5.2,设计原理,5.2.1,模块化,模块是由边界元素限定的相邻程序元素(例如,数据说明,可执行的,模块化就是把程序划分成独立命名且可独立访问的模块,每个模块完成一个子功能,把这些模块集成起来构成一个整体,可以完成指定的功能满足用户的需求。,有人说,模块化是为了使一个复杂的大型程序能被人的智力所管理,软件应该具备的惟一属性。如果一个大型程序仅由一个模块组成,它将很难被人所理解。下面根据人类解决问题的一般规律,论证上面的结论。,设函数,C(x),定义问题,x,的复杂程度,函数,E(x),确定解决问题,x,需要的工作量,(,时间,),。对于两个问题,P1,和,P2,,如果,C(P1)C(P2),模块化就是把程序划分成独立命名且可独立访问的模块,每个模块完,显然,E(P1)E(P2),根据人类解决一般问题的经验,另一个有趣的规律是,C(P1+P2)C(P1)+C(P2),也就是说,如果一个问题由,P1,和,P2,两个问题组合而成,那么它的复杂程度大于分别考虑每个问题时的复杂程度之和。,综上所述,得到下面的不等式,E(P1+P2)E(P1)+E(P2),这个不等式导致“各个击破”的结论,把复杂的问题分解成许多容易解决的小问题,原来的问题也就容易解决了。这就是模块化的根据。,显然E(P1)E(P2),由上面的不等式似乎还能得出下述结论:如果无限地分割软件,最后为了开发软件而需要的工作量也就小得可以忽略了。事实上,还有另一个因素在起作用,从而使得上述结论不能成立。参看图,5.1,,当模块数目增加时每个模块的规模将减小,开发单个模块需要的成本,(,工作量,),确实减少了;但是,随着模块数目增加,设计模块间接口所需要的工作量也将增加。根据这两个因素,得出了图中的总成本曲线。每个程序都相应地有一个最适当的模块数目,M,,使得系统的开发成本最小。,由上面的不等式似乎还能得出下述结论:如果无限地分割软件,最后,图,5.1,模块化和软件成本,软件工程_总体设计课件,虽然目前还不能精确地决定,M,的数值,但是在考虑模块化的时候总成本曲线确实是有用的指南。,采用模块化原理可以使软件结构清晰,不仅容易设计也容易阅读和理解。因为程序错误通常局限在有关的模块及它们之间的接口中,所以模块化使软件容易测试和调试,因而有助于提高软件的可靠性。因为变动往往只涉及少数几个模块,所以模块化能够提高软件的可修改性。模块化也有助于软件开发工程的组织管理,一个复杂的大型程序可以由许多程序员分工编写不同的模块,并且可以进一步分配技术熟练的程序员编写困难的模块。,虽然目前还不能精确地决定M的数值,但是在考虑模块化的时候总成,人类在认识复杂现象的过程中使用的最强有力的思维工具是抽象。人们在实践中认识到,在现实世界中一定事物、状态或过程之间总存在着某些相似的方面,(,共性,),。把这些相似的方面集中和概括起来,暂时忽略它们之间的差异,这就是抽象。或者说抽象就是抽出事物的本质特性而暂时不考虑它们的细节。,5.2.2,抽象,人类在认识复杂现象的过程中使用的最强有力的思维工具是抽象。人,由于人类思维能力的限制,如果每次面临的因素太多,是不可能做出精确思维的。处理复杂系统的惟一有效的方法是用层次的方式构造和分析它。一个复杂的动态系统首先可以用一些高级的抽象概念构造和理解,这些高级概念又可以用一些较低级的概念构造和理解,如此进行下去,直至最低层次的具体元素。,这种层次的思维和解题方式必须反映在定义动态系统的程序结构之中,每级的一个概念将以某种方式对应于程序的一组成分。,由于人类思维能力的限制,如果每次面临的因素太多,是不可能做出,考虑对任何问题的模块化解法时,可以提出许多抽象的层次。在抽象的最高层次使用问题环境的语言,以概括的方式叙述问题的解法;在较低抽象层次采用更过程化的方法,把面向问题的术语和面向实现的术语结合起来叙述问题的解法;最后在最低的抽象层次用可直接实现的方式叙述问题的解法。,软件工程过程的每一步都是对软件解法的抽象层次的一次精化。在可行性研究阶段,软件作为系统的一个完整部件;在需求分析期间,软件解法是使用在问题环境内熟悉的方式描述的;当由总体设计向详细设计过渡时,抽象的程度也就随之减少了;最后,当源程序写出来以后,也就达到了抽象的最低层。,考虑对任何问题的模块化解法时,可以提出许多抽象的层次。在抽象,逐步求精和模块化的概念,与抽象是紧密相关的。随着软件开发工程的进展,在软件结构每一层中的模块,表示了对软件抽象层次的一次精化。事实上,软件结构顶层的模块,控制了系统的主要功能并且影响全局;在软件结构底层的模块,完成对数据的一个具体处理,用自顶向下由抽象到具体的方式分配控制,简化了软件的设计和实现,提高了软件的可理解性和可测试性,并且使软件更容易维护。,逐步求精和模块化的概念,与抽象是紧密相关的。随着软件开发工程,逐步求精是人类解决复杂问题时采用的基本方法,也是许多软件工程技术(例如,规格说明技术,设计和实现技术)的基础。可以把逐步求精定义为:“为了能集中精力解决主要问题而尽量推迟对问题细节的考虑。”,逐步求精之所以如此重要,是因为人类的认知过程遵守,Miller,法则:一个人在任何时候都只能把注意力集中在(,72,)个知识块上。,5.2.3,逐步求精,逐步求精是人类解决复杂问题时采用的基本方法,也是许多软件工程,但是,在开发软件的过程中,软件工程师在一段时间内需要考虑的知识块数远远多于,7,。例如,一个程序通常不止使用,7,个数据,一个用户也往往有不止,7,个方面的需求。逐步求精方法的强大作用就在于,它能帮助软件工程师把精力集中在与当前开发阶段最相关的那些方面上,而忽略那些对整体解决方案来说虽然是必要的,然而目前还不需要考虑的细节,这些细节将留到以后再考虑。,Miller,法则是人类智力的基本局限,我们不可能战胜自己的自然本性,只能接受这个事实,承认自身的局限性,并在这个前提下尽我们的最大努力工作。,但是,在开发软件的过程中,软件工程师在一段时间内需要考虑的知,事实上,可以把逐步求精看作是一项把一个时期内必须解决的种种问题按优先级排序的技术。逐步求精方法确保每个问题都将被解决,而且每个问题都将在适当的时候被解决,但是,在任何时候一个人都不需要同时处理,7,个以上知识块。,逐步求精最初是由,Niklaus Wirth,提出的一种自顶向下的设计策略。按照这种设计策略,程序的体系结构是通过逐步精化处理过程的层次而设计出来的。通过逐步分解对功能的宏观陈述而开发出层次结构,直至最终得出用程序设计语言表达的程序。,Wirth,本人对逐步求精策略曾做过如下的概括说明:,事实上,可以把逐步求精看作是一项把一个时期内必须解决的种种问,“我们对付复杂问题的最重要的办法是抽象,因此,对一个复杂的问题不应该立刻用计算机指令、数字和逻辑符号来表示,而应该用较自然的抽象语句来表示,从而得出抽象程序。抽象程序对抽象的数据进行某些特定的运算并用某些合适的记号(可能是自然语言)来表示。对抽象程序做进一步的分解,并进入下一个抽象层次,这样的精细化过程一直进行下去,直到程序能被计算机接受为止。这时的程序可能是用某种高级语言或机器指令书写的。”,“我们对付复杂问题的最重要的办法是抽象,因此,对一个复杂的问,求精实际上是细化过程。我们从在高抽象级别定义的功能陈述(或信息描述)开始,也就是说,该陈述仅仅概念性地描述了功能或信息,但是并没有提供功能的内部工作情况或信息的内部结构。求精要求设计者细化原始陈述,随着每个后续求精(即细化)步骤的完成而提供越来越多的细节。,抽象与求精是一对互补的概念。抽象使得设计者能够说明过程和数据,同时却忽略低层细节。事实上,可以把抽象看作是一种通过忽略多余的细节同时强调有关的细节,而实现逐步求精的方法。求精则帮助设计者在设计过程中逐步揭示出低层细节。这两个概念都有助于设计者在设计演化过程中创造出完整的设计模型。,求精实际上是细化过程。我们从在高抽象级别定义的功能陈述(或信,应用模块化原理时,自然会产生的一个问题是:“为了得到最好的一组模块,应该怎样分解软件呢,?”,信息隐藏原理指出:应该这样设计和确定模块,使得一个模块内包含的信息,(,过程和数据,),对于不需要这些信息的模块来说,是不能访问的。,局部化的概念和信息隐藏概念是密切相关的。所谓局部化是指把一些关系密切的软件元素物理地放得彼此靠近。在模块中使用局部数据元素是局部化的一个例子。显然,局部化有助于实现信息隐藏。,5.2.4,信息隐藏和局部化,应用模块化原理时,自然会产生的一个问题是:“为了得到最好的一,实际上,应该隐藏的不是有关模块的一切信息,而是模块的实现细节。因此,有人主张把这条原理称为“细节隐藏”。,“隐藏”意味着有效的模块化可以通过定义一组独立的模块而实现,这些独立的模块彼此间仅仅交换那些为了完成系统功能而必须交换的信息。,如果在测试期间和以后的软件维护期间需要修改软件,那么使用信息隐藏原理作为模块化系统设计的标准就会带来极大好处。因为绝大多数数据和过程对于软件的其他部分而言是隐藏的,(,也就是“看”不见的,),,在修改期间由于疏忽而引入的错误就很少可能传播到软件的其他部分。,实际上,应该隐藏的不是有关模块的一切信息,而是模块的实现细节,模块独立的概念是模块化、抽象、信息隐藏和局部化概念的直接结果。,开发具有独立功能而且和其他模块之间没有过多的相互作用的模块,就可以做到模块独立。换句话说,希望这样设计软件结构,使得每个模块完成一个相对独立的特定子功能,并且和其他模块之间的关系很简单。,5.2.5,模块独立,模块独立的概念是模块化、抽象、信息隐藏和局部化概念的直接结果,为什么模块的独立性很重要呢,?,主要有两条理由:第一,有效的模块化,(,即具有独立的模块,),的软件比较容易开发出来。这是由于能够分割功能而且接口可以简化,当许多人分工合作开发同一个软件时,这个优点尤其重要。第二,独立的模块比较容易测试和维护。这是因为相对说来,修改设计和程序需要的工作量比较小,错误传播范围小,需要扩充功能时能够“插入”模块。总之,模块独立是好设计的关键,而设计又是决定软件质量的关键环节。,为什么模块的独立性很重要呢?主要有两条理由:第一,有效的模块,模块的独立程度可以由两个定性标准度量,这两个标准分别称为内聚和耦合。耦合衡量不同模块彼此间互相依赖,(,连接,),的紧密程度;内聚衡量一个模块内部各个元素彼此结合的紧密程度。以下分别详细阐述。,1.,耦合,耦合是对一个软件结构内不同模块之间互连程度的度量。耦合强弱取决于模块间接口的复杂程度,进入或访问一个模块的点,以及通过接口的数据。,模块的独立程度可以由两个定性标准度量,这两个标准分别称为内聚,在软件设计中应该追求尽可能松散耦合的系统。在这样的系统中可以研究、测试或维护任何一个模块,而不需要对系统的其他模块有很多了解。此外,由于模块间联系简单,发生在一处的错误传播到整个系统的可能性就很小。因此,模块间的耦合程度强烈影响系统的可理解性、可测试性、可靠性和可维护性。,如果两个模块中的每一个都能独立地工作而不需要另一个模块的存在,那么它们彼此完全独立,这意味着模块间无任何连接,耦合程度最低。但是,在一个软件系统中不可能所有模块之间都没有任何连接。,在软件设计中应该追求尽可能松散耦合的系统。在这样的系统中可以,如果两个模块彼此间通过参数交换信息,而且交换的信息仅仅是数据,那么这种耦合称为数据耦合。如果传递的信息中有控制信息,(,尽管有时这种控制信息以数据的形式出现,),,则这种耦合称为控制耦合。,数据耦合是低耦合。系统中至少必须存在这种耦合,因为只有当某些模块的输出数据作为另一些模块的输入数据时,系统才能完成有价值的功能。一般说来,一个系统内可以只包含数据耦合。控制耦合是中等程度的耦合,它增加了系统的复杂程度。控制耦合往往是多余的,在把模块适当分解之后通常可以用数据耦合代替它。,如果两个模块彼此间通过参数交换信息,而且交换的信息仅仅是数据,如果被调用的模块需要使用作为参数传递进来的数据结构中的所有元素,那么,把整个数据结构作为参数传递就是完全正确的。但是,当把整个数据结构作为参数传递而被调用的模块只需要使用其中一部分数据元素时,就出现了特征耦合。在这种情况下,被调用的模块可以使用的数据多于它确实需要的数据,这将导致对数据的访问失去控制,从而给计算机犯罪提供了机会。,当两个或多个模块通过一个公共数据环境相互作用时,它们之间的耦合称为公共环境耦合。公共环境可以是全程变量、共享的通信区、内存的公共覆盖区、任何存储介质上的文件、物理设备等等。,如果被调用的模块需要使用作为参数传递进来的数据结构中的所有元,公共环境耦合的复杂程度随耦合的模块个数而变化,当耦合的模块个数增加时复杂程度显著增加。如果只有两个模块有公共环境,那么这种耦合有下面两种可能:,(1),一个模块往公共环境送数据,另一个模块从公共环境取数据。这是数据耦合的一种形式,是比较松散的耦合。,(2),两个模块都既往公共环境送数据又从里面取数据,这种耦合比较紧密,介于数据耦合和控制耦合之间。,如果两个模块共享的数据很多,都通过参数传递可能很不方便,这时可以利用公共环境耦合。,公共环境耦合的复杂程度随耦合的模块个数而变化,当耦合的模块个,最高程度的耦合是内容耦合。如果出现下列情况之一,两个模块间就发生了内容耦合:,一个模块访问另一个模块的内部数据;,一个模块不通过正常入口而转到另一个模块的内部;,两个模块有一部分程序代码重叠,(,只可能出现在汇编程序中,),;,一个模块有多个入口,(,这意味着一个模块有几种功能,),。,应该坚决避免使用内容耦合。事实上许多高级程序设计语言已经设计成不允许在程序中出现任何形式的内容耦合。,最高程度的耦合是内容耦合。如果出现下列情况之一,两个模块间就,总之,耦合是影响软件复杂程度的一个重要因素。应该采取下述设计原则:,尽量使用数据耦合,少用控制耦合和特征耦合,限制公共环境耦合的范围,完全不用内容耦合。,2.,内聚,内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐藏和局部化概念的自然扩展。简单地说,理想内聚的模块只做一件事情。,设计时应该力求做到高内聚,通常中等程度的内聚也是可以采用的,而且效果和高内聚相差不多;但是,低内聚很坏,不要使用。,总之,耦合是影响软件复杂程度的一个重要因素。应该采取下述设计,内聚和耦合是密切相关的,模块内的高内聚往往意味着模块间的松耦合。内聚和耦合都是进行模块化设计的有力工具,但是实践表明内聚更重要,应该把更多注意力集中到提高模块的内聚程度上。,低内聚有如下几类:如果一个模块完成一组任务,这些任务彼此间即使有关系,关系也是很松散的,就叫做偶然内聚。有时在写完一个程序之后,发现一组语句在两处或多处出现,于是把这些语句作为一个模块以节省内存,这样就出现了偶然内聚的模块。如果一个模块完成的任务在逻辑上属于相同或相似的一类,则称为逻辑内聚。如果一个模块包含的任务必须在同一段时间内执行,就叫时间内聚。,内聚和耦合是密切相关的,模块内的高内聚往往意味着模块间的松耦,在偶然内聚的模块中,各种元素之间没有实质性联系,很可能在一种应用场合需要修改这个模块,在另一种应用场合又不允许这种修改,从而陷入困境。事实上,偶然内聚的模块出现修改错误的概率比其他类型的模块高得多。,在逻辑内聚的模块中,不同功能混在一起,合用部分程序代码,即使局部功能的修改有时也会影响全局。因此,这类模块的修改也比较困难。,时间关系在一定程度上反映了程序的某些实质,所以时间内聚比逻辑内聚好一些。,在偶然内聚的模块中,各种元素之间没有实质性联系,很可能在一种,中内聚主要有两类:如果一个模块内的处理元素是相关的,而且必须以特定次序执行,则称为过程内聚。使用程序流程图作为工具设计软件时,常常通过研究流程图确定模块的划分,这样得到的往往是过程内聚的模块。如果模块中所有元素都使用同一个输入数据和,(,或,),产生同一个输出数据,则称为通信内聚。,中内聚主要有两类:如果一个模块内的处理元素是相关的,而且必须,高内聚也有两类:如果一个模块内的处理元素和同一个功能密切相关,而且这些处理必须顺序执行,(,通常一个处理元素的输出数据作为下一个处理元素的输入数据,),,则称为顺序内聚。根据数据流图划分模块时,通常得到顺序内聚的模块,这种模块彼此间的连接往往比较简单。如果模块内所有处理元素属于一个整体,完成一个单一的功能,则称为功能内聚。功能内聚是最高程度的内聚。,耦合和内聚的概念是,Constantine,Yourdon,Myers,和,Stevens,等人提出来的。按照他们的观点,如果给上述七种内聚的优劣评分,将得到如下结果:,高内聚也有两类:如果一个模块内的处理元素和同一个功能密切相关,功能内聚,10,分时间内聚,3,分,顺序内聚,9,分逻辑内聚,1,分,通信内聚,7,分偶然内聚,0,分,过程内聚,5,分,事实上,没有必要精确确定内聚的级别。重要的是设计时力争做到高内聚,并且能够辨认出低内聚的模块,有能力通过修改设计提高模块的内聚程度降低模块间的耦合程度,从而获得较高的模块独立性。,功能内聚10分时间内聚3分,人们在开发计算机软件的长期实践中积累了丰富的经验,总结这些经验得出了一些启发式规则。这些启发式规则虽然不像上一节讲述的基本原理和概念那样普遍适用,但是在许多场合仍然能给软件工程师以有益的启示,往往能帮助他们找到改进软件设计提高软件质量的途径。下面介绍几条启发式规则。,5.3,启发规则,人们在开发计算机软件的长期实践中积累了丰富的经验,总结这些经,1.,改进软件结构提高模块独立性,设计出软件的初步结构以后,应该审查分析这个结构,通过模块分解或合并,力求降低耦合提高内聚。例如,多个模块公有的一个子功能可以独立成一个模块,由这些模块调用;有时可以通过分解或合并模块以减少控制信息的传递及对全程数据的引用,并且降低接口的复杂程度。,1. 改进软件结构提高模块独立性,2.,模块规模应该适中,经验表明,一个模块的规模不应过大,最好能写在一页纸内,(,通常不超过,60,行语句,),。有人从心理学角度研究得知,当一个模块包含的语句数超过,30,以后,模块的可理解程度迅速下降。,过大的模块往往是由于分解不充分,但是进一步分解必须符合问题结构,一般说来,分解后不应该降低模块独立性。,过小的模块开销大于有效操作,而且模块数目过多将使系统接口复杂。因此过小的模块有时不值得单独存在,特别是只有一个模块调用它时,通常可以把它合并到上级模块中去而不必单独存在。,2. 模块规模应该适中,3.,深度、宽度、扇出和扇入都应适当,深度表示软件结构中控制的层数,它往往能粗略地标志一个系统的大小和复杂程度。深度和程序长度之间应该有粗略的对应关系,当然这个对应关系是在一定范围内变化的。如果层数过多则应该考虑是否有许多管理模块过分简单了,能否适当合并。,宽度是软件结构内同一个层次上的模块总数的最大值。一般说来,宽度越大系统越复杂。对宽度影响最大的因素是模块的扇出。,3. 深度、宽度、扇出和扇入都应适当,扇出是一个模块直接控制,(,调用,),的模块数目,扇出过大意味着模块过分复杂,需要控制和协调过多的下级模块;扇出过小,(,例如总是,1),也不好。经验表明,一个设计得好的典型系统的平均扇出通常是,3,或,4(,扇出的上限通常是,5,9),。,扇出太大一般是因为缺乏中间层次,应该适当增加中间层次的控制模块。扇出太小时可以把下级模块进一步分解成若干个子功能模块,或者合并到它的上级模块中去。当然分解模块或合并模块必须符合问题结构,不能违背模块独立原理。,扇出是一个模块直接控制(调用)的模块数目,扇出过大意味着模块,一个模块的扇入表明有多少个上级模块直接调用它,扇入越大则共享该模块的上级模块数目越多,这是有好处的,但是,不能违背模块独立原理单纯追求高扇入。,观察大量软件系统后发现,设计得很好的软件结构通常顶层扇出比较高,中层扇出较少,底层扇入到公共的实用模块中去,(,底层模块有高扇入,),。,一个模块的扇入表明有多少个上级模块直接调用它,扇入越大则共享,4.,模块的作用域应该在控制域之内,模块的作用域定义为受该模块内一个判定影响的所有模块的集合。模块的控制域是这个模块本身以及所有直接或间接从属于它的模块的集合。例如,在图,5.2,中模块,A,的控制域是,A,、,B,、,C,、,D,、,E,、,F,等模块的集合。,在一个设计得很好的系统中,所有受判定影响的模块应该都从属于做出判定的那个模块,最好局限于做出判定的那个模块本身及它的直属下级模块。,4. 模块的作用域应该在控制域之内,图,5.2,模块的作用域和控制域,软件工程_总体设计课件,5.,力争降低模块接口的复杂程度,模块接口复杂是软件发生错误的一个主要原因。应该仔细设计模块接口,使得信息传递简单并且和模块的功能一致。,接口复杂或不一致,(,即看起来传递的数据之间没有联系,),,是紧耦合或低内聚的征兆,应该重新分析这个模块的独立性。,6.,设计单入口单出口的模块,这条启发式规则警告软件工程师不要使模块间出现内容耦合。当从顶部进入模块并且从底部退出来时,软件是比较容易理解的,因此也是比较容易维护的。,5. 力争降低模块接口的复杂程度,7.,模块功能应该可以预测,模块的功能应该能够预测,但也要防止模块功能过分局限。,如果一个模块可以当做一个黑盒子,也就是说,只要输入的数据相同就产生同样的输出,这个模块的功能就是可以预测的。带有内部“存储器”的模块的功能可能是不可预测的,因为它的输出可能取决于内部存储器,(,例如某个标记,),的状态。由于内部存储器对于上级模块而言是不可见的,所以这样的模块既不易理解又难于测试和维护。,7. 模块功能应该可以预测,如果一个模块只完成一个单独的子功能,则呈现高内聚;但是,如果一个模块任意限制局部数据结构的大小,过分限制在控制流中可以做出的选择或者外部接口的模式,那么这种模块的功能就过分局限,使用范围也就过分狭窄了。在使用过程中将不可避免地需要修改功能过分局限的模块,以提高模块的灵活性,扩大它的使用范围;但是,在使用现场修改软件的代价是很高的。,以上列出的启发式规则多数是经验规律,对改进设计,提高软件质量,往往有重要的参考价值;但是,它们既不是设计的目标也不是设计时应该普遍遵循的原理。,如果一个模块只完成一个单独的子功能,则呈现高内聚;但是,如果,层次图用来描绘软件的层次结构。在图,5.2,中已经非正式地使用了层次图。虽然层次图的形式和第,3.7,节中介绍的描绘数据结构的层次方框图相同,但是表现的内容却完全不同。层次图中的一个矩形框代表一个模块,方框间的连线表示调用关系而不像层次方框图那样表示组成关系。图,5.3,是层次图的一个例子。,5.4,描绘软件结构的图形工具,5.4.1,层次图和,HIPO,图,层次图用来描绘软件的层次结构。在图5.2中已经非正式地使用了,图,5.3,正文加工系统的层次图,软件工程_总体设计课件,层次图很适于在自顶向下设计软件的过程中使用。,HIPO,图是美国,IBM,公司发明的“层次图加输入,/,处理,/,输出图”的英文缩写。为了能使,HIPO,图具有可追踪性,在,H,图,(,层次图,),里除了最顶层的方框之外,每个方框都加了编号。编号规则和第,2.4,节中介绍的数据流图的编号规则相同,例如,图,5.3,加了编号后得到图,5.4,。,和,H,图中每个方框相对应,应该有一张,IPO,图描绘这个方框代表的模块的处理过程。,HIPO,图中的每张,IPO,图内都应该明显地标出它所描绘的模块在,H,图中的编号,以便追踪了解这个模块在软件结构中的位置。,层次图很适于在自顶向下设计软件的过程中使用。,图,5.4,带编号的层次图,(H,图,),软件工程_总体设计课件,Yourdon,提出的结构图是进行软件结构设计的另一个有力工具。结构图和层次图类似,也是描绘软件结构的图形工具,图中一个方框代表一个模块,框内注明模块的名字或主要功能;方框之间的箭头,(,或直线,),表示模块的调用关系。因为按照惯例总是图中位于上方的方框代表的模块调用下方的模块,即使不用箭头也不会产生二义性,为了简单起见,可以只用直线而不用箭头表示模块间的调用关系。,5.4.2,结构图,Yourdon提出的结构图是进行软件结构设计的另一个有力工具,在结构图中通常还用带注释的箭头表示模块调用过程中来回传递的信息。如果希望进一步标明传递的信息是数据还是控制信息,则可以利用注释箭头尾部的形状来区分:尾部是空心圆表示传递的是数据,实心圆表示传递的是控制信息。图,5.5,是结构图的一个例子。,以上介绍的是结构图的基本符号,也就是最经常使用的符号。此外还有一些附加的符号,可以表示模块的选择调用或循环调用。图,5.6,表示当模块,M,中某个判定为真时调用模块,A,,为假时调用模块,B,。图,5.7,表示模块,M,循环调用模块,A,、,B,和,C,。,在结构图中通常还用带注释的箭头表示模块调用过程中来回传递的信,图,5.5,结构图的例子,产生最佳解的一般结构,软件工程_总体设计课件,图,5.6,判定为真时调用,A,,为假时调用,B,软件工程_总体设计课件,图,5.7,模块,M,循环调用模块,A,、,B,、,C,软件工程_总体设计课件,注意,层次图和结构图并不严格表示模块的调用次序。虽然多数人习惯于按调用次序从左到右画模块,但并没有这种规定,出于其他方面的考虑,(,例如为了减少交叉线,),,也完全可以不按这种次序画。此外,层次图和结构图并不指明什么时候调用下层模块。通常上层模块中除了调用下层模块的语句之外还有其他语句,究竟是先执行调用下层模块的语句还是先执行其他语句,在图中丝毫没有指明。事实上,层次图和结构图只表明一个模块调用那些模块,至于模块内还有没有其他成分则完全没有表示。,注意,层次图和结构图并不严格表示模块的调用次序。虽然多数人习,通常用层次图作为描绘软件结构的文档。结构图作为文档并不很合适,因为图上包含的信息太多有时反而降低了清晰程度。但是,利用,IPO,图或数据字典中的信息得到模块调用时传递的信息,从而由层次图导出结构图的过程,却可以作为检查设计正确性和评价模块独立性的好方法。传送的每个数据元素都是完成模块功能所必须的吗,?,反之,完成模块功能必须的每个数据元素都传送来了吗,?,所有数据元素都只和单一的功能有关吗,?,如果发现结构图上模块间的联系不容易解释,则应该考虑是否设计上有问题。,通常用层次图作为描绘软件结构的文档。结构图作为文档并不很合适,面向数据流的设计方法的目标是给出设计软件结构的一个系统化的途径。,在软件工程的需求分析阶段,信息流是一个关键考虑,通常用数据流图描绘信息在系统中加工和流动的情况。面向数据流的设计方法定义了一些不同的“映射”,利用这些映射可以把数据流图变换成软件结构。因为任何软件系统都可以用数据流图表示,所以面向数据流的设计方法理论上可以设计任何软件的结构。通常所说的结构化设计方法,(,简称,SD,方法,),,也就是基于数据流的设计方法。,5.5,面向数据流的设计方法,面向数据流的设计方法的目标是给出设计软件结构的一个系统化的途,
展开阅读全文