3. 3 结构化设计方法
3.3.1 软件设计的基本概念 3.3.2 概要设计 3.3.3 详细设计
3.3.1 软件设计的基本概念
1. 软件设计的基础 软件设计是软件工程的重要阶段,是一个把软件需求转换为软件表示的过程。软件设计的基本目标是用比较抽象概括的方式确定目标系统如何完成预定的任务,即软件设计是确定系统的物理模型。 软件设计的重要性和地位概括为以下几点: ①软件开发阶段(设计、编码、测试)占据软件项目开发总成本绝大部分,是在软件开发中形成质量的关键环节; ②软件设计是开发阶段最重要的步骤,是将需求准确地转化为完整的软件产品或系统的惟一途径; ③软件设计作出的决策,最终影响软件实现的成败; ④设计是软件工程和软件维护的基础。 从技术观点来看,软件设计包括软件结构设计、数据设计接口设计、过程设计。其中,结构是定义软件系统各主要部件之间的关系;数据设计是将分析时创建的模块转化为数据结构的定义;接口设计是描述软件内部、软件和协作系统之间以及软件与人之间如何通信;过程设计则是把系统结构部件转化为软件的过程性描述。 从工程管理角度来看,软件设计分两步来完成:概要设计和详细设计。概要设计(又称结构设计)将软件需求转化为软件体系结构、确定系统级接口、全局数据结构或数据库模式;详细设计确立每个模块的实现算法和局部数据结构,用适当方法表示算法和数据结构的细节。 软件设计的一般过程是:软件设计的过程是一个迭代的过程:先进行高层次的结构设计;后进行低层次的过程设计;穿插进行数据设计和接口设计。 2 .软件设计的基本原理 软件设计遵循软件工程的基本目标和原则,建立了适用于在软件设计中应该遵循的基本原理和与软件设计有关的概念。 抽象 抽象是一种思维工具,就是把事物本质的共同特性提取出来而不考虑其他细节。软件设计中考虑模块解决方案时,可以定出多个抽象级别。抽象的层次从概要设计到详细设计逐步降低。在软件概要设计中的模块分层也是由抽象到具体逐步分析和构造出来的。 模块化 模块是把一个待开发的软件分解成若干小的简单的部分。如高级语言中过程、函数、子程序等。每个模块可以完成一个特定的子功能,各个模块可以按一定的方法组装起来成为一个整体,从而实现整个系统的功能。 模块化是指解决一个复杂问题时自定向下逐层把软件系统划分成若干模块的过程。 为了解决复杂的问题,在软件设计中必须把整个问题进行分解来降低复杂性,这样就可以减少开发工作量并降低开发成本和提高软件生产率。但是划分模块并不是越多越好,因为这会增加模块之间接口的工作量,所以划分模块的层次和数量应该避免过多或过少。 信息隐蔽 信息隐蔽是指在一个模块内包含的信息(过程或数据),对于不需要这些信息的其他模块来说是不能访问的 模块独立性 模块独立性是指每个模块只完成系统要求的独立的子系统,并且与其他模块的联系最少且接口简单。 模块的独立程度是评价设计好坏的重要度量指标。衡量软件的模块独立性使用耦性和内聚性两个定性的度量标准。 ① 内聚性:内聚性是一个模块内部各个元素间彼此结合的紧密程度的度量。内聚是从功能角度来度量模块内的联系。 内聚有如下的种类,他们之间的内聚性由弱到强排列为: 偶然内聚:指一个模块内的各处理元素之间没有任何联系。 逻辑内聚:指模块内执行几个逻辑上相关的功能,通过参数 确定该模块完成哪一个功能。 时间内聚:把需要同时或顺序执行的动作组合在一起形成的模块为时间内聚模块。比如初始化模块,它顺序为变量置初值。 过程内聚:如果一个模块处理元素相关的,而且必须以特定次序执行则称为过程内聚。 通信内聚:指模块内所有处理功能都通过使用公用数据而发生关系。这种内聚也具有过程内聚的特点。 顺序内聚:指一个模块中各个处理元素和同一个功能能密切相关,而且这些处理必须顺序执行,通常前一个处理元素的输出就是下一个处理元素的输入。 功能内聚:指模块内所有元素共同完成一个功能,缺一不可,模块已不再可分。这是最强的内聚。 内聚性是信息隐蔽和局部化概念的自然扩展。一个模块的内聚性越强则该模块的模块的独立性越强。作为软件设计的设计原则,要求每一个模块的内部具有很强的内聚性,它的各个组成部分彼此都密切相关。 ② 耦合性:耦合性是模块间互相连接的紧密程度的度量。 耦合性取决于各个模块间接口的复杂度、调用方式以及哪些信息通过接口。耦合可以分为下列几种,它们之间的耦合度由高到低排列为: 内容耦合:如一个模块直接访问另一模块的内容,则这两个模块称为内容耦合。 公共耦合:如一组模块都访问同一全局数据结构,则它们之间的耦合称为公共耦合。 外部耦合:一组模块都访问同一全局简单变量(而不是同一全局数据结构),且不通过参数表传递该全局变量的信息,则称为外部耦合。 控制耦合:若一模块明显的把开关量、名字等信息送入另一模块,控制另一模块的功能,则称为控制耦合。 标记耦合:若两个以上的模块都需要其余某一数据结构子结构时,不使用其余全局变量的方式而是用记录传递的方式,即两模块间通过数据结构交换信息,这样的耦合合称为标记耦合。 数据耦合:若一个访问另一个模块,被访问模块的输入和输出都是数据项参数,即两模块间通过数据参数交换信息,则这两个模块为数据耦合。 非直接耦合:若两个模块没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的,则称这两个模块为非直接耦合。非直接耦合独立性最强。 上面仅是对耦合机制进行的一个分类。可见,一个模块与其他模块的耦合性越强则该模块的模块独立性越弱。原则上讲,模块化设计总是希望模块之间的耦合表现为非直接耦合方式。但是,由于问题所有的复杂性和结构设计的原则,非直接耦合往往是不存在的。 耦合性与内聚性是模块的两个定性标准,耦合与内聚是相关的。在程序结构中,各模块的内聚性越强,则耦合性越弱。一般较优秀的软件设计,应尽量做到高内聚,低耦合,即减弱模块之间的耦合性和提高模块内的内聚性,有利于提高模块的独立性。 3 .结构化设计方法 与结构化需求分析方法相对应的是结构化设计方法。结构化设计就是采用最佳的可能的方法设计系统的各个组成部分以及各成分之间的内部联系的技术。也就是说,结构化设计是这样的一个过程,它决定用哪些方法把哪些部分联系起来,才能解决好某个具体有清楚的问题。 结构化设计的基本思想是将软件设计成由相对独立、单一化功能的模块组成的结构。下面重点以面向数据流的结构化方法为例讨论结构化设计方法。
3.3.2 概要设计
1. 概要设计的任务 软件概要设计的基本任务是: (1) 设计软件系统结构 在需求分析阶段,已经把系统分解成层次结构,而在概要设计阶段,需要进一步分解,划分为模块以及模块的层次结构。划分的具体过程: 采用某种设计方法,将一个复杂的系统按功能划分成模块。 确定每个模块的功能。 确定模块之间的调用关系。 确定模块之间的接口,即模块之间传递的信息。 评价模块结构的质量。 (2)数据结构及数据库设计 数据设计是实现需求定义和规格说明过程中提取的数据对象的逻辑表示。数据设计的具体任务是:确定输入、输出文件的详细数据结构:结合算法设计,确定算法所必要的逻辑数据结构及其操作;确定对逻辑数据结构所必须的那些操作的程序模块,限制和确定各个数据设计决策的影响范围:需要和操作系统或调度程序接口所必要的控制表进行数据交换时,确定其详细的数据结构和使用规则:数据的保护性设计:防卫性、一致性、冗于性设计。 数据设计中应该注意掌握以下设计原则: 用于功能和行为的系统分析原则也应用于数据。 应该标识所有的数据结构以及其上的操作。 应当建立数据字典,并用于数据设计和程序设计。 低层的设计决策应该推迟到设计过程的后期。 只有那些直接使用数据结构、内部数据的模块才看到该数据的表示。 应该开发一个由有用的数据结构和应用于其上的操作组成的库。 软件设计和程序设计语言该支持抽象数据类型的规格说明和实现。 (3)编写概要设计文档。在概要设计阶段,需要编写的文档有,概要设计说明书、数据库说明书、集成测试计划等。 (4)概要设计文档评审。在概要设计中,对设计部分是否完整地实现了需求中规定的功能、性能等要求,设计方案的可行性,关键的处理及内部接口定义的正确性,各部分之间的一致性等都要进行评审,以免在以后的设计中出现大的问题而返工。 常用的软件结构设计工具是结构图( SC Strtucture Chart ),也称程序结构图。使用结构图描述软件系统的层次和分块结构关系,它反映了整个系统的功能实现以及模块与模块之间的联系与通讯,是未来程序中的控制层次体系。 结构图是描述软件结构的图形工具。结构图的基本符号如图 3.8 所示。 图3.8 结构图基本图符 模块用一个矩形表示,矩形内注明模块的功能和名字;箭头表示模块间的调用关系。在结构图中还可以用带注解的箭头表示模块调用过程中来回传递的信息。如果希望进一步标明传递的信息是数据还是控制信息,则可以用带实心圆的箭头表示传递的是控制信息,用带空心圆的箭心表示传递的是数据。 根据结构化设计思想,结构图构成的基本形式如图 3.9 所示。 图3.9 结构图构成的基本形式 经常使用的结构图有四种模块类型:传入模块、传出模块、变换模块和协调模块。其表示形式和含义如图 3.10 所示。 图3.10 传入、传出模块、变换模块和协调模块的表示形式和含义 下面通过图 3.11 进一步了解程序结构图的有关术语。 深度:表示控制的层数。 上级模块、从属模块:上、下两层模块 a 和 b ,且有 a 调用 b ,则 a 是上级模块, b 是从属模块。 宽度:整体控制跨度(最大模块数的层)的表示。 扇入:调用一个给定模块的模块个数。 扇出:一个模块直接调用的其他模块数。 原子模块:树中位于叶子结点的模块。
图3.11 简单财务帐务管理系统结构图 2. 面向数据流的设计方法 在需求分析阶段,主要是分析信息在系统中加工和流动的情况。面向数据流的设计方法定义了一些不同的映射方法,利用这些映射方法可以把数据流图变换成结构图表示的软件结构。首先需要了解数据流图表示的数据处理的类型,然后针对不同类型分别进行分析处理。 数据流类型 典型的数据流类型有两种:变换型和事务型。 变换型。变换型是指信息沿输入通路进入系统,同时由外部形式变换成内部形式,进入系统的信息通过变换中心,经过加工处理以后再沿输出通路变换成外部形式离开软件系统。变换型数据处理问题的工作过程大致分为三步,即取得数据、变换数据和输出数据,如图 3.12 所示。相应于取得数据、变换数据、输出数据的过程,变换型系统结构图由输入、中心变换和输出等三部分组成,如图 3.13 所示。 图3.12 变换型数据流结构图 图3.13 变换型数据流结构的组成 图3.14 变换型数据流系统结构图 ②事务型。在很多软件应用中,存在某种作业数据流,它可以引发一个或多个处理,这些处理能够完成该作业要求的功能,这种数据流就叫做事务。事务型数据流的特点是接受一项事务,根据事务处理的特点和性质,选择分派一个适当的处理单元(事务处理中心),然后给出结果。这类数据流归为特殊的一类,称为事务型数据流,如图 3.15 所示,在一个事务行数据流中,事务中心接收数据,分析每个事务以确定它的类型,根据事务类型选取一条活动通路。 图3.15 事务型数据流结构 事务型数据流图映射的结构如图 3.16 所示。 图3.16 事务型数据流图映射的结构 在事务型数据流系统结构图中,事务中心模块按所接受的事务类型,选择某一事务处理模块执行,各事务处理模块并列。每个事务处理模块可能要调用若干个操作模块,而操作模块又可能调用若干个细节模块。 ( 2 )面向数据流设计方法的实施要点与设计过程 面向数据流的结构设计过程和步骤是: 第 1 步:分析、确认数据流图的类型,区分事务型还是变换型。 第 2 步:说明数据流的边界。 第 3 步:把数据流图映射为程序结构。对于事务流区分中心和数据接收通路,将它映射成事务结构。对于变换流,区分输出和输入分支,并将其映射成变换结构。 第 4 步:根据设计准则对产生的结构进行细化和求精。 下面分别讨论变换型和事务型数据流图转换成程序结构图的实施步骤。 ① 变换型 将变换型映射成结构图,又称为变换分析。其步骤如下: 第 1 步:确定数据流图是否具有变换性。一般地说,一个系统中所有的信息流都可以认为是变换流,但是,当遇有明显的事务特性的信息流时,建议采用事务分析方法进行设计。在这时应该观察在整个数据流图中哪种属性占优势,先确定数据流的全局特性。此外还应把具有全局特性的不同特点的局部区域孤立出来,根据这些子数据流的特点作部分的处理。 第 2 步:确定输入流和输出流的边界,划分出输入、变换和输出,独立出变换中心。 第 3 步:进行第一步分解,将变换型映射成软件结构(参见图 3.15 ),其中输入数据处理模块协调对所有输入数据的接收;变换中心控制模块管理对内部形式的数据的所有操作;输出数据处理控制模块协调输出信息的产生过程。 第 4 步:按上述步骤如出现事务流的映射方式对各个子流进行逐级分解,直至分解到基本功能。 第 5 步:对每个模块写一个简要说明,内容包括该模块的接口描述、模块内部的信息、过程陈述、包括的主要判定点及任务等。 第 6 步:利用软件结构的设计原则对软件结构进一步转化。 ② 事务型 将事务型映射成结构图,又称为事务分析。其步骤如下: 事务分析的设计步骤与变换分析设计步骤大致类似,主要差别仅在于由数据流图到软件结构的映射方法不同(参见图 3.14 和 3.16 )。它是将事务中心影射成为软件结构中发送分支的调度模块,将接收通路映射成软件结构的接收分支。 ( 3 )设计的准则 大量软件设计的实践证明,以下的设计准则是可以借鉴为设计的指导和软件结构图进行优化。这些准则是: 提高模块独立性。对软件结构应着眼于改善模块的独立性,依据降低耦合提高内聚的原则,通过把一些模块取消或合并来修改程序的结构。 模块规模适中。经验表明,当模块增大时,模块的可理解性迅速下降。但是当对大的模块分解时,不应降低模块的独立性。因为,当对一个大的模块分解时,有可能会增加模块间的依赖。 深度、宽度、扇入和扇出适当。如果深度过大,则说明有的控制模块可能简单了。如果宽度过大,则说明系统的控制过于集中。而扇出过大则意味着模块过分复杂,需要控制和协调过多的下级模块,这时应适当增加中间层次。扇出太小则可以把下级模块进一步分解成若干个功能模块,或者合并到上级模块中去。扇入越大则共享该模块的上级模块数目越多。 经验表明,好的软件设计结构通常顶层高扇出,中间扇出较少,低层高扇入。 使模块的作用域在该模块的控制域内。模块的作用域是指模块内一个判定的作用范围,凡是受这个判定影响的所有模块都属于这个判定域。模块的控制域是指这个模块本身以及所以直接或间接从属于它的模块的集合。在一个设计得很好的系统中,所有受某个判定影响的模块应该都从属于做出判定的那个模块,最好局限于做出判定的那个模块本身及它的直属下级模块。对于那些不满足这一条件的软件结构,修改的办法是:将判定点上移或者将那些在作用范围内但是不在控制范围内的模块移到控制范围以内。 应该减少模块的接口和界面的复杂性。模块的接口复杂是软件容易发生错误的一个主要原因。应该仔细设计模块接口,使得信息传递简单并且和模块的功能一致。 设计成单入口、单出口的模块。 设计功能可预测的模块。如果一个模块可以当作一个“黑盒”,也就是不考虑模块的内部结构和处理过程。则这个模块的功能就是可以预测的。
3.3.3 详细设计
详细设计的任务,是为软件结构图中的每一个模块确定实现算法和局部数据结构,用某种选顶的表达工具表示算法和数据结构的细节。表达工具可以由设计人员自由选择,但它应该具有描述过程细节的能力,而且能够使程序员在编程时便于直接翻译成程序设计语言的源程序。本节重点对过程设计进行讨论。 在过程设计阶段,要对每个模块规定的功能以及算法的设计,给出适当的算法描述,即确定模块内部详细执行过程,包括局部数据组织、控制流、每一步具体处理要求和各种实现细节等。其目的是确定应该怎样来具体实现所要求的系统。 常见的过程设计工具有: 图形工具:程序流程图, N-S , PAD , HIPO 表格工具:判定表 语言工具: PDL (伪码) 下面讨论其中主要的几种工具: 1.程序流程图 程序流程图是一种传统的、应用广泛的软件过程设计表示工具,通常也称为程序框图,程序流程图表达直观、清晰、易于学习掌握,且独立于任何一种程序设计语言。 构成程序流程图的最基本图符及含义如图 3.17 所示。 图3.17 程序流程图的最基本图符 按照结构化程序设计的要求,程序流程图构成的任何程序描述限制为如图 3.18 所示的 5 种控制结构。 图3.18 程序流程图构成的5种控制结构 图 3.18 所示的程序流程图构成的 5 种控制结构的含义是: 顺序型:几个连续的加工步骤依次排列构成; 选择型:由某个逻辑判断式的取值决定选择两个加工中的一个; 先判断重复型:先判断循环控制条件是否成立,成立则执行循环体语句; 后判断重复型:重复执行某些特定的加工,直到控制条件成立; 多分支选择型:列举多种加工情况,根据控制变量的取值,选择执行其中之一。 通过把程序流程图的 5 中基本控制结构相互组合或嵌套,可以构成任何复杂的程序流程图。 例如,下面是简单托运货物运费计算的问题。 设货物重量 x ,客户信息 y ,输入 x 、 y 后、计算运费的具体要求是: 如果 0 < x ≤ 15 (设为条件 1 ),则用公式 1 计算后,循环 3 次完成同样的“记帐”和“输出”操作,然后程序结束; 如果 x>15 (设为条件 2 ),则用公式 2 计算后,循环 3 次完成同样的“记帐”和“输出”操作,然后程序结束; 该问题程序的程序流程图描述如图 3.19 。 图3.19 程序流程图示例 程序流程图虽然简单易学,但是若程序员不受任何约束,随意转移控制,会破坏结构化设计的原则,而且程序流程图不易表示数据结构。 2.N-S 图 为了避免流程图在描述程序逻辑时的随意性与灵活性, 1973 年 Nossi 和 Shneiderman 发表了题为“结构化程序的流程图技术”的文章,提出了用方框图来代替传统的程序流程图,通常也把这种图称为 N-S 图。 N-S 图的基本图符及表示的 5 种基本控制结构图图 3.20 所示。 图3.20 N-S图图符与构成的5种控制结构 例如,上述问题程序的N-S图描述如图3.21所示 图3.21 N-S图示例 N-S 图有以下特征: ( 1 )每个构件具有明确的功能域; ( 2 )控制转移必须遵守结构化设计要求; ( 3 )易于确定局部数据和(或)全局数据的作用域; ( 4 )易于表达嵌套关系和模块的层次结构; 3. PAD 图 PAD 图是问题分析图( problem Analysis Diagram )的英文缩写。它是继程序流程图和方框之后,提出又一种主要用于描述软件详细设计的图形表示工具。 PAD 图的基本图符及表示的 5 种基本控制结构,如图 3.22 所示。 图3.22 PAD图图符与构成的5种控制结构 例如,上述问题程序的 PAD 图描述如图 3.23 所示。 图3.23 PAD图示例 PAD 图有以下特征: 结构清晰,结构化程度高; 易于阅读; 最左端的纵线是程序主干线,对应程序的第一层结构;每增加一层 PAD 图向右扩展一条纵线,故程序是纵线数等于程序层次数。 程序执行:从 PAD 图最左主干线上端结点开始,自上而下,自左而右依次执行,程序终止于最左主干线。 4.PDL(Procedure Design Language) 过程设计语言( PAD )也称为结构化的英语和伪码,它是一种混合语言,采用英语的词汇和结构化程序设计语言的语法,类似编程语言。 用 PDL 表示的基本控制结构的常用词汇如下: 顺序: 条件: IF/THEN/ELSE/ENDIF 循环: DO WHILE/ENDO 循环: REPEAT UNTIL/ENDREPEAT 分支: CASE_OF/WHEN/SELECT/WHEN/SELECT/ENDCASE 例如,上述问题程序的描述如下,它是类似 C 语言的 PAD 。 /* 计算运费 */ count ( ); { 输入 x; 输出 y; if (o<x<=15) 条件 1 { 公式 1 计算 ;call sub;} else if (x>15) { 公式 2 计算 ;call sub;} } sub ( ); { for(i=1,3,i++) do{ 记账;输出; } } PAD 可以由编程语言转换得到,也可以是专门为过程描述而设计的。但应具备以下特征: 有为结构化构成元素、数据说明和模块化特征提供的关键词语法; 处理部分的描述采用自然语言语法; 可以说明简单和复杂的数据结构; 支持各种接口描述的子程序定义和调用技术。