第4章总体设计4.1模块化设计方法4.2结构化设计(SD)方法4.3Parnas方法4.4Jackson方法4.5总体设计的其他工作4.6详细设计4.7编程第4章总体设计返回主目录第4章总体设计第4章总体设计4.1模块化设计方法1.模块设计的基本概念较大的软件系统,一般是由许多具有特定功能的较小的单元组成,这种单元就称为模块。一个模块具有输入和输出、特定功能、内部数据和程序代码等四个特性。输入和输出模块是需要处理和产生信息的功能模块。输入输出和特定功能构成了一个模块的外貌,即模块的外部特性。程序代码用来完成模块的功能。内部数据是仅供该模块本身引用的数据,内部数据和程序代码是模块的内部特性。对模块的外部环境来说,只需了解它的外部特性就足够了。第4章总体设计2.模块设计的主要任务模块设计的任务是把一个较大的软件系统分解成许多较小的具有特定功能的模块,由它们共同完成软件系统的整体功能。具体来说,就是:第一,将软件系统划分成模块;第二,决定各个模块的功能;第三,决定模块间的调用关系;第四,决定模块间的界面。所以,模块设计的主要工作是完成模块分解,确定软件系统中模块的层次结构。第4章总体设计模块设计技术上有相当的困难,它需要有一定的方法来指导,从而使设计人员可以获得较好的方案。20世纪70年代以来,出现了许多设计方法来支持模块设计,其中具有代表性的有结构化设计、Parnas方法、Jackson方法、Warnier方法等。这些方法都采用了模块化、由顶向下逐步细化的基本思想。它们的差别在于构成模块的原则不同。结构化设计方法以数据流图为基础构成模块结构;Parnas方法以信息隐蔽为原则建立模块结构;而Jackson方法则以数据结构为基础建立模块结构。当然这些方法也可以结合起来使用。第4章总体设计4.2结构化设计(SD)方法在众多的设计方法中,结构化设计(StructuredDesign,简称SD)是最受人注意的、也是使用最广泛的一个。1.结构化设计(SD)方法的基本思想SD方法的基本思想是将系统设计成由相对独立、单一功能的模块组成的结构。用SD方法设计的程序系统,由于模块之间是相对独立的,所以每个模块可以独立地被理解、编程、测试、排错和修改,这就使复杂的研制工作得以简化。此外,模块的相对独立性也能有效地防止错误在模块之间扩散蔓延,因而提高了系统的可靠性。第4章总体设计所以我们可以说,SD方法的长处来自于模块之间的相对独立性,它提高了系统的质量(可理解性、可维护性、可靠性等),也减少了研制软件所需的人工。2.块间联系和块内联系如何衡量模块之间的相对独立性呢?SD方法提出了块间联系和块内联系这两个标准(如图4.1所示)。块间联系(Coupling,又称耦合度)是指模块之间的联系,它是对模块独立性的直接衡量,如图4.1(a)所示。块间联系越小就意味着模块的独立性越高,所以这是一个最基本的标准。块内联系(Cohesion,又称聚合度)是指一个模块内部各成分(语句或语句段)之间的联系,如图4.1(b)所示。块内联系大了,模块的相对独立性势必会提高。第4章总体设计图4.1块间联系与块内联系M3M4M5M6M1M2块间联系块间联系块间联系(a)块间联系M3M4M5M6M1M2(b)块内联系第4章总体设计SD方法的目标是使块间联系尽量小,块内联系尽量大。事实上,块间联系和块内联系是同一件事的两个方面。程序中各组成成分间是有联系的,如果将密切相关的成分分散在各个模块中,就会造成很高的块间联系;反之,如果密切相关的一些成分组织在同一模块中,块内联系高了,则块间联系势必也就少了。3.描述方式SD方法使用的描述方式是结构图(StructureChart),它描述了程序的模块结构,并反映了块间联系和块内联系等特性。结构图中的主要成分有:第4章总体设计模块──用方框表示,方框中写有模块的名字,一个模块的名字应适当地反映这个模块的功能,这就在某种程度上反映了块内联系。调用──从一个模块指向另一模块的箭头,表示前一模块中含有对后一模块的调用。数据──调用箭头边上的小箭头,表示调用时从一个模块传送给另一模块的数据,小箭头也指出了数据传送的方向。设计员应该为结构图中的每一个成分(模块和数据)适当地命名,使人能直观地理解其含义。如果能在一个课题组中约定一些命名规则,将会是有所帮助的。为使读者容易理解,本书有时在模块的方框内用中文说明该模块的功能。第4章总体设计除上述基本符号外,结构图中可以再加上一些辅助性的符号,如有条件地调用符号、循环调用符号、现成的模块符号等。设计员可根据具体情况决定是否有必要画出这些辅助性的符号。画结构图的一般习惯是:输入模块在左,输出模块在右,而计算模块居中。必须指出:“结构图”和“框图”(即程序流程图)是不同的。一个程序有层次性和过程性两方面的特点,通常“层次性”反映的是整体性质,“过程性”反映的是局部性质,所以我们一般是先决定程序的层次特性,再决定其过程特性。“结构图”描述的是程序的层次特性,即某个模块负责管理哪些模块,这些模块又依次负责管理哪些模块等。(我们可以看出:结构图也可以用来描述现实生活中的组织管理结构,如学校中的系、教研室、教学小组等层次结构。)第4章总体设计4.3Parnas方法Parnas认为软件设计对软件质量有着决定性的影响,又因为模块设计是确定模块的界面,一个界面往往影响着多个模块,所以设计中的缺陷影响很大,纠正设计错误所付出的代价也很大,因而设计时应该特别予以重视。正因为如此,Parnas主张设计时应预先估计到整个生命期可能发生的种种情况,以便事先采取相应的措施来提高软件的可靠性和可维护性。1.提高可靠性的技术──防护性检查复杂的软件结构容易隐含错误,尤其是在将来修改时更易造成错误,所以软件结构对可靠性影响很大。第4章总体设计在设计时使软件结构尽量简单清晰,这是避免错误、提高可靠性的根本手段。Parnas主张设计时应预计到将来可能发生的种种意外,采取以下措施以提高系统的健壮性:(1)考虑到硬件有可能出现意外故障,所以接近硬件的模块应该对硬件的行为进行检查,以便及时发现硬件的错误,例如对磁带中的文件配上检查以供核对,或存储几份副本以供比较等。(2)考虑到操作人员有可能失误(也可能有人会故意破坏),负责接受操作人员输入的模块应该对输入数据进行合理性检查,辨认非法、越权的操作要求,同时也要为操作员提供合适的纠错手段。第4章总体设计(3)考虑到软件本身也会有错误,所以模块之间要加强检查,防止错误的蔓延。例如开平方根的模块应检查输入的变量是否大于等于零,而不应假设这一点必然成立;又如操作系统中应考虑万一发生死锁,该怎么处理;或者有一个进程发生了故障,连续不断地申请资源而不再归还时,系统如何发现和处理这种异常。2.提高可维护性的技术──信息隐蔽大型软件系统是多个版本的,在整个生命期中要经历多次修改,设计时如何划分模块,才能使将来修改的影响范围尽量小呢?Parnas提出了信息隐蔽(InformationHiding)的原则。根据信息隐蔽原则,设计时应首先列出可能发生变化的因素,在划分模块时将一个可能发生变化的因素包含在某个模块的内部,使其他模块与这个因素无关。这样,将来这些因素发生变化时,我们只需要修改一个模块就够了,而其他模块则不受这个因素的影响。第4章总体设计也就是说,在设计模块结构时,将某个因素隔离在某个模块内部,这个因素的变化不致于传播到所在模块的边界之外。下面用两个例子解释信息隐蔽原则。[例4.1]某程序输入一个数X,对它进行合理性检查,然后进行计算,最后将计算结果存入存储器中。根据实际情况,我们估计到以下三个因素将来可能要发生变化:(1)输入数据X的格式。目前X的格式是六个字符的字符串,其中有符号位、三位整数、小数点和一位小数,将来会有变化。(2)对X作合理性检查的规则。目前只要X的数值范围在-500~+500之间就认为它是合理的,将来打算采用某些更精确的检查规则。(3)目前将计算结果存入磁带中,将来存入磁盘中,存储格式也有变化。第4章总体设计使用Parnas方法,我们可以将程序设计成如图4.2所示的结构。其中模块GetanX隐含了上述第一个因素,模块Validate隐含了第二个因素,模块Store隐含了第三个因素。而其他模块则不必了解这些因素。将来某个因素发生变化时,仅仅修改一个模块就行了。读者可以想象,如果不将Validate独立出来,而是同GetaValidX混合在一起,则第二个因素发生变化时影响的范围就比现在大了。[例4.2]用数组实现了一个栈,有10个模块要对栈作存取操作。一种设计方案是将数组、栈顶、深度等作为全程变量,由各个模块自行对栈作存取操作(如图4.3(a)所示),也就是说10个模块都需了解栈的具体结构细节。第4章总体设计图4.2使用Parnas方法的程序结构图MainXComputeGetavalidXStoreGetanXValidateOK第4章总体设计这个方案的缺点是:如果将来栈的具体结构需作修改(如深度增加),这10个模块都必须作相应的修改。另一个设计方案是根据信息隐蔽原则,先构造两个模块Push和Pop,它们分别负责对栈进行存取操作,10个模块通过调用Push和Pop实现栈操作(如图4.3(b)所示),此时,栈的具体结构是隐含在Push和Pop两个模块内部的,其他10个模块完全不了解这些细节,“栈”对它们来说是一个“抽象”的数据结构。这个方案的优点是:如果将来需要修改栈的具体结构,只需改动Push和Pop两个模块就够了,其余10个模块完全不受影响。第4章总体设计图4.3模块对栈的存取操作MStackM2M1M10…MStack…M2M1M10StackStack(a)(b)第4章总体设计通过上面两个例子的讨论,我们可以体会到信息隐蔽的目的是使修改造成的影响尽量局限在一个或少数几个模块内部,从而降低软件维护的开支。又因为修改极易引起错误,所以修改影响范围越小,修改引起错误的可能性越小,系统的可靠性也就提高了。为了达到上述目标,根据信息隐蔽原则,模块分解时应该做到以下几点:(1)每个模块功能简单、容易理解。(2)修改一个模块的内部实现不会影响其他模块的行为。(3)将可能变化的因素在设计方案中作如下安排:①最可能发生的修改,不必改动模块界面就能完成;第4章总体设计②不太可能发生的修改,可以涉及少量模块和不太用的模块之界面;③极不可能发生的修改,才需改动常用模块的界面。Parnas在1972年提出的信息隐蔽原则已被软件界广泛接受,“信息隐蔽”(也就是“抽象”)已成为软件工程学中的一个重要原则,“抽象数据类型”以及诸如Ada语言中的Package等有关概念就是信息隐蔽原则的体现。Parnas的研究工作虽然同SD方法的研究是独立的,但其结论颇为接近:将信息隐蔽在模块的内部,就是将模块作为一个黑盒,使其他模块不了解这个模块的内部细节,这同模块之间要相对独立、块间联系要小等想法是一致的。第4章总体设计Parnas方法对模块分解提出了深刻的见解,但遗憾的是,它没有给出明确的工作步骤,所以这个方法一般仅作为其他方法的补充手段。第4章总体设计4.4Jackson方法另一种方法,即面向数据结构的方法,其具有代表性的是Jackson和Warnier提出的LCP(LogicalConstructionofPrograms)方法,本节将主要介绍Jackson方法。Jackson方法是由英国的M.Jackson提出的,它在西欧率先流行。这个方法适用于数据处理类问题,特别是企事业管理类的软件系统。Jackson方法的目标是获得简单清晰的设计方案,因为这样的方案易于理解,易于修改。为了达到这个目标,Jackson方法的设计原则是:使程序结构同数据结构相对应。Jackson方法主张程序结构与问题结构相对应。对一般数据处理系统而言,问题的结构可用它所处理的数据结构来表示,大多数处理系统是具有层次结构的数据,如文件由记录组成,记录又由数据项组成(如图4.4(a)所示,图中*号表示重复)。第4章总体设计Jackson方法以此为基