本文最后更新于 2025-03-13,文章内容可能已经过时。

软件工程:第四章 总体设计

导学目标

  1. 了解总体设计目的、必要性、基本任务等

  2. 了解总体设计的两个阶段、9个步骤

  3. 掌握总体设计原理,耦合内聚是重中之重

  4. 掌握启发式规则

  5. 掌握层次图、HIPO图、结构图等

  6. 掌握面向数据流的设计方法,变换事务是重中之重

总体设计

总体设计的必要性

  • 全局性分析,选择最佳方案和最合理的软件结构

    • 总体设计又叫做概要设计初步设计

  • 开发阶段的开始:决定怎么做(框架)

  • 基本目的:概括的说系统该如何实现

  • 重要任务是设计软件的结构,也就是确定系统中每个程序是由哪些模块组成,以及这些模块相互之间的关系

总体设计阶段的主要任务

  • 划分物理元素

    • 物理元素的组成包括:程序、文件、数据库、人工过程和文档等

    • 每个物理元素处于黑盒子级

  • 设计软件结构

    • 确定系统中的模块组成与模块之间的关系

第一节 设计过程

4.1.1 总体设计过程共分为两个阶段

  • 系统设计:确定系统的实现方案

  • 结构设计:确定软件结构

4.1.2 总体设计的过程包含9个步骤

1-3步骤为系统设计

4-9步骤为结构设计

1、设想供选择的方案
  • 综合考虑各种方案,选择出最佳方案

  • 从数据流图DFD出发

  • 区分DFD的各种处理,并分组

  • 考虑各种处理的实现策略

  • 设想并列出方案,但不评价

实例讲解:客房管理系统局部方案选择

image-20240730195609661

预定请求类型的选择
    只接受电话预定
    只接受网上预定
    只接受上门预定
    接受上述方案中任意组合预定
    
夜审时间与餐费列入方法的确定
    中午12点
    早晨8点
    餐费列入住宿费
    
。。。。
2、选择合理的方案
  • 系统分析员应该提供4份资料

    • 系统流程图

    • 组成系统的物理元素清单

    • 成本效益分析

    • 实现这个系统的进度计划

  • 考虑需求规则说明书的要求并进一步征求用户的意见

实例讲解:口算高手

物理元素清单:
    一个程序
    六个模块:输入模块、校验模块、退出模块、计算模块、打印模块、显示模块
    
成本效益分析:
    货币的时间价值
    投资回报期
    纯收入
    投资回收率
    
实现进度计划:
    可行性分析0.5天
    需求分析1天
    总体设计1天
    详细设计与编码1天
    软件测试3天
    文档资料编写、审议1天
3、推荐最佳方案
  • 分析员对比各种合理方案的利弊,推荐最佳方案

  • 用户和技术专家审查通过

  • 使用部门负责人认可

  • 完成系统设计,进入结构设计阶段

注意:以上都为总体设计过程的系统设计阶段

4、功能分解
  • 先进行结构设计,确定程序由哪些模块组成以及这些模块之间的关系,是总体设计阶段的任务

  • 后进行过程设计,确定每个模块的处理过程,是详细设计阶段的任务

  • 把复杂的功能进一步分解成一些简单的功能,对大多数程序员都是浅显易懂的

实例讲解:口算高手

  • 模块组成

    image-20240730200754328

5、设计软件结构
  • 把模块组织成良好的层次系统:顶层模块调用下层模块,下层模块调用更下层模块

  • 软件结构可以由层次图或者结构图来描绘

6、设计数据库
  • 模式设计

  • 子模式设计

  • 完整性和安全性设计

  • 优化:模式和子模式优化,利于存取

7、制定测试计划
  • 测试方法选择:白盒测试/黑盒测试

  • 测试内容设计:模块测试、功能测试、性能测试......

  • 测试条件:人员/设备......

  • 测试用例设计

  • 测试人员安排

  • 测试时间进度

8、书写文档
  • 系统说明

    • 系统流程图:描绘系统的构成方案

    • 组成的物理元素清单

    • 成本效益分析

    • 最佳方案概括描述

    • 精化的数据流图

    • 软件结构:层次图或结构图

    • 模块算法:IPO等工具

  • 用户手册:修正/更改初步用户手册

  • 测试计划

    • 测试策略

    • 测试方案

    • 预期的测试结果

    • 测试进度计划

  • 详细的实现计划

  • 数据库设计结果

9、审查和复审

第二节 设计原理

4.2.1 模块化

模块

  • 是数据说明和可执行语句的序列,每个模块单独命名并且可以通过名字对模块进行访问

模块化设计

  • 把大型软件按照规定的原则划分为一个较小的、相对独立但又相关的模块的设计方法

模块化设计的指导思想

  • 功能分解、信息隐藏、模块的独立性

模块示例

  • C语言子程序

  • 函数

  • 对象

  • 方法

模块化分解示例

  • 设函数C(x)定义问题x的复杂程度,函数E(x)确定问题x需要的工作量(时间),对于两个问题P1P2,如果C(P1) > C(P2),显然E(P1)>E(P2)

  • 根据人类解决一般问题的经验,如果一个问题由P1和P2两个问题组合而成,那么它的复杂程序大于分别考虑每个问题的复杂度之和,即 C(P1+P2) > C(P1) + C(P2)

  • 综上所述,E(P1+P2) > E(P1) + E(P2)

  • 把复杂问题分解成许多容易的小问题,原来的问题也就容易解决了,这也是模块化原理的根据

模块化和软件成本

image-20240730202958233

控制结构(程序结构)

  • 控制结构是软件模块间关系的表示

    image-20240730203041644

  • 控制结构的层次规则

    • 只有一个顶层(0层)模块

    • 除顶层外,任一模块都会在它的邻层存在一模块与它相关

    • 同层模块间不存在联系

软件结构度量术语

  • 软件结构度量术语解释

    • 宽度:软件结构内同一层次上的模块总数的最大值

    • 深度:一个模块包含自身及其他模块的层数

    • 扇出:一个模块直接调用的模块数,平均扇出3-4

    • 扇入:有多少个上级模块调用它

  • 软件结构度量图示例

image-20240730203152893

  • 软件结构度量示例

    image-20240730203428714

4.2.1 抽象

  • 抽象就是抽象出事物的本质特性而暂时不考虑它们的实现细节

  • 当考虑对任何问题的模块化解法时,可以考虑多层次抽象

    • 最高层:使用问题的环境语言,概括的叙述问题的解法

    • 较低层:采用更过程化的方法,将面向问题和面向实现结合起来叙述问题的解法

    • 最低层:直接实现的方式,叙述问题的解法

4.2.3 逐步求精

  • 定义

    • 集中精力解决主要问题而尽量推迟对问题细节的考虑

  • 作用

    • 帮助软件工程师集中精力在与当前开发最相关的那些方面上,忽略那些对整体解决方法必要但是暂时不需要考虑的细节

  • 实质

    • 将一段时期内的问题按照优先级排序,逐个细化

4.2.4 信息隐藏和局部化

  • 定义

    • 模块内部的数据与过程,对不需要了解这些数据与过程的模块隐藏起来。只有那些为了完成总体功能在模块间必须交换的信息,才允许在模块间进行传递

  • 原理

    • 使一个模块的信息(数据和过程)对于不需要这些信息的模块来说是不能访问的

  • 隐藏

    • 意味着有效的模块化可以通过定义一组独立的模块而实现,这些独立的模块彼此间仅仅交换那些为了完成系统功能而必须交换的信息。目的是提高软件的独立性,即当修改或者维护模块时减小把一个错误传播到其他模块中去的机会

  • 局部化

    • 把一些关系密切的软件元素物理的放的彼此靠近,比如局部变量

  • 实例

    image-20240730204516094

4.2.5 模块独立

  • 概念

    • 模块化、抽象、信息隐藏和局部化概念的直接结果

      • 完成相对独立的子功能

      • 模块之间的关系很简单

  • 重要性

    • 独立的模块化比较容易开发

    • 独立的模块化比较容易测试和维护

  • 度量标准(衡量模块的独立程度)

    • 内聚:衡量一个模块内部各个元素彼此结合的紧密程度

    • 耦合:衡量不同模块彼此间互相依赖(连接)的紧密程度

4.2.5.1 耦合
  • 耦合强弱取决于模块间接口的复杂程度,进入或访问一个模块的点以及通过接口的数据等

  • 应该尽可能追求松散耦合的系统,因为模块间的耦合程度强烈影响着系统的可理解性、可测试性、可靠性和可维护性

  • 设计规则:尽量使用数据耦合,少用控制耦合和特征耦合,限制公共环境耦合的范围,完全不用内容耦合

  • 耦合的7种类型

    image-20240730205325191

  • 耦合的强度

    • 非直接耦合/无耦合:最低(不可能发生的)

    • 数据耦合:低耦合,一般来说,一个系统内可以只包含数据耦合

    • 控制耦合:中耦合,模块分解后通常可以用数据耦合代替它

    • 公共环境耦合:可以是全程变量、共享通信区、内存的公共覆盖区、任何存储介质上的文件、物理设备等。复杂程度随耦合模块个数变化。一读一取,松耦合;既读又去,介于数据耦合和控制耦合之间。

    • 内容耦合:最高程度的耦合

实例说明

1、无直接耦合
  • 两个模块之间完全独立,无任何连接

  • 这里的模块1和模块2就是无直接耦合

    image-20240730205744256

  • Max_Value和Min_Value就是无直接耦合

    image-20240730205819013

2、数据耦合
  • 一个模块在调用另一模块时,被调用模块的输入和输出都是一些简单的数据,属于松散耦合

  • 示例

    image-20240730205947160

    image-20240730210007051

3、特征耦合
  • 两个模块通过传递数据结构(不是简单的数据,而是记录、数组等)加以联系或者都与同一个数据结构有关系

  • 实例

    • 若将住户情况分别改为用水量和用电量则可以将特征耦合变为数据耦合

    • 住户情况是一个数据结构,图中的模块都与该数据结构有关系计算水费和计算电费本来没有关系,但是由于都使用了住户情况这同一数据结构而产生依赖关系,他们之间就是特征耦合

image-20240730210107382

4、控制耦合
  • 模块向下属模块传递的信息(如开关、标志等)控制了被调用模块的内部逻辑

image-20240730210254133

  • 去除模块间控制耦合的方法

    • 控制耦合增加了理解和编程的复杂性,调用模块必须知道被调用模块的内部逻辑,增加了相互依赖

    • 将被调用模块内的判定上移到调用模块中进行

    • 将被调用模块分解成若干单一功能模块

  • 将控制耦合改为数据耦合的例子

    image-20240730210425385

5、外部耦合
  • 一组模块均与同一外部环境关联,(例如I/O模块与特定的设备、格式、通信协议相关联),它们之间就存在外部耦合

  • 外部耦合必不可少,但这种模块的数目应该尽可能少

6、公共耦合

  • 一组模块引用同一个公用数据区(也称全局数据区、公共数据环境)

  • 公共数据区指:全局数据区、共享通信区、内存公共覆盖区等

  • 公共耦合示例:模块A、B、C存在错综复杂的联系

    image-20240730210558072

  • 公共耦合存在的问题

    • 软件可理解性降低

    • 定位错误比较困难

    • 软件可维护性差

    • 软件可靠性差

公共数据区及全程变量无保护措施,因此要慎用

7、内容耦合
  • 发生情况

    • 一个模块访问另一个模块的内部数据

    • 一个模块不通过正常入口,转到另一个模块内部

    • 两个模块有一部分程序代码重叠(只可能出现在汇编程序中)

    • 一个模块有多个入口(一个模块有多种功能)

  • 图示

    image-20240730210847985

4.2.5.2 内聚
  • 标志着一个模块内各个元素彼此结合的紧密程度

  • 简单的说,理想内聚模块只做一件事情。设计时应该力求做到高内聚,通常中等程度的内聚也是可以的,而且效果和高内聚差不多。但是坚决不要使用低内聚

  • 内聚和耦合是紧密相关的,模块内的高内聚往往意味着模块间的松耦合,内聚和耦合都是进行模块化设计的有力工具,但是实践表明内聚更重要,应该把注意力集中到提高模块的内聚程度上

  • 内聚设计原则:

    • 力求高内聚

    • 中等内聚也可以使用

    • 低内聚不要用

  • 与耦合的关系:高内聚意味着低耦合

  • 实践证明,内聚更重要,应该把更多注意力集中在提高模块的内聚程度上

  • 内聚类型

    image-20240730211431773

1、偶然内聚
  • 如果一个模块完成一组任务,这些任务即使彼此间有关系,关系也是很松散的,并且修改困难,0分

  • 偶然内聚实例

    image-20240730211549078

    • 模块M中的三个语句没有任何关系

  • 缺点:可理解性差、可修改性差

2、逻辑内聚
  • 把几种逻辑上相似的功能组合在一模块内,每次调用由传给模块的参数来确定执行那种功能且修改困难,1分

  • 逻辑内聚示例

    image-20240730211645951

  • 缺点:增强了耦合程度(控制耦合),不易修改,效率低

3、时间内聚
  • 模块完成的功能必须在同一时间内完成,时间内聚比逻辑内聚要好一些,3分

  • 时间内聚示例

    • 比如初始化系统模块、系统结束模块、紧急故障处理模块等

4、过程内聚:
  • 使用程序流程图作为工具设计软件时得到的模块且模块内各处理成分相关,且必须以特定顺序执行,属于中内聚,评分是5分

  • 过程内聚示例:

    image-20240730211810874

5、通信内聚
  • 模块中所有元素都使用同一个输入数据或产生同一个输出数据,属于中内聚,评分7分

  • 通讯内聚示例

    • image-20240730211851753

6、顺序内聚
  • 使用数据流图工具设计软件时得到的模块,也叫作信息内聚。模块完成多个功能,各功能都在同一数据结构上操作,每一功能有唯一入口,属于高内聚,评分9分

  • 顺序内聚示例:

    image-20240730211950882

7、功能内聚
  • 最高内聚,是理想内聚,只做一件事情

    • 模块仅包括为了完成某个功能所必须的所有成分,缺一不可;功能内聚属于高内聚,内聚性最强,评分10分

  • 功能内聚示例:

    image-20240730212046450

第三节 启发规则

在长期开发过程中累积出的经验,虽不能普遍使用,但仍具有好的启示

常用的启发规则

  1. 改进软件结构,提高模块的独立性

    • 降低耦合、提高内聚

  2. 模块规模应该适中

    • 模块太大,分解不充分;模块太小,开销大于有效操作且接口复杂

  3. 深度、宽度、扇出和扇入要适中

    • 深度:表示软件结构中控制的层数,粗略的表示一个系统的大小和复杂程度,深度为该模块包含自身其他模块的层数

    • 宽度:软件结构内同一层次上的模块总数的最大值,宽度越大,系统越复杂,对宽度影响最大的是扇出

      • 扇出:一个模块直接调用的模块数,平均扇出3-4

        • 好的软件结构通常顶层扇出较高,中间扇出较少,底层扇入较高

    • 扇入:表明一个模块有多少个直接上级调用它

  4. 模块的作用域应该在控制域之内

    • 作用域:受该模块内一个判断影响的所有模块集合

    • 控制域:这个模块本身以及直接或者间接从属于它的模块集合

      • 改变作用域与控制域的方式:判断点上移或者作用域对象下移

    • 实例:

      • A的控制域是A,B,C,D,E,F

      • 修改作用域和控制域:A中判断点上移或者将G移到A下面

      image-20240730213149191

  5. 力争降低模块接口的复杂程度

  6. 设计单入口单出口模块

    • 容易理解、容易维护

  7. 模块功能应该可以预测

第四节 描绘软件结构的图形工具

4.4.1 层次图
  • 用来描绘软件的层次结构,也成H图

  • 与层次方框图的关系

    • 层次图中一个矩形代表一个模块

    • 方框间的连线表示调用关系而不是组成关系

    • 其他与层次方框图类似

  • 适于自顶向下设计软件的过程中使用

  • 示例

    image-20240730213825388

4.4.2 HIPO图
  • 是IBM公司发明的“层次图加输入/处理/输出”的简称

  • 为了使HIPO图具有可追踪性,在H图里处理最顶层的方框外,其他方框都加了编号

  • 示例

    image-20240730213936737

4.4.3 结构图
  • 结构图和层次图类似,一个方框代表一个模块,箭头或直线代表调用关系,带注释的箭头表示模块调用过程中来回传递的消息

  • 示例

    image-20240730214005190

结构化设计方法
  • 首先研究分析、审查数据流图,弄清数据的加工过程

  • 根据数据流图确定问题类型(变换型or 事务型),分别处理

  • 由数据流图推导出系统的初始结构图

  • 利用一些试探性规则改进系统的初始结构图,直到符合要求为止

  • 修改和补充数据字典

  • 制定测试计划

结构化设计方法符号约定

image-20240730214208768

结构图中的附加符号

image-20240730214306365

第五节 面向数据流的设计方法

结构化设计方法就是基于数据流的设计方法

面向数据流的设计方法把信息流(数据流图)映射成软件结构

数据流图的两种类型;根据数据流类型的不同,系统对应的系统结构也不同

  • 变换型数据流——>变换型结构

  • 事务型数据流——>事务型结构

系统结构基本模型

image-20240731121906748

数据流图基本模型

image-20240731122023007

变换型数据流示例

image-20240731122102733

事务型数据流示例

image-20240731122159361

在大型DFD中,变换型和事务型结构往往共存

image-20240731122228005

4.5.1 面向数据流设计方法的设计步骤

  1. 精化DFD

  2. 确定DFD类型

  3. 把DFD映射到系统模块结构,设计出模块结构的上层

  4. 基于DFD逐步分解高层模块,设计出下层模块

  5. 根据模块独立化原理,精化模块结构

  6. 模块接口描述

4.5.2 结构化设计的两种映射方法

SC:结构图

image-20240731122517697

初始的结构图

image-20240731122553338

4.5.3 变换分析方法

步骤

  1. 区分传入、变换中心、传出部分,在DFD上标明分界线

  2. 第一级分解,建立初始结构图,设计顶层和第一层模块

  3. 第二级分解,自顶向下分解,设计出每个分支的中下层模块

示例

  • 变换中心

image-20240731122838556

  • 第一级分解

    image-20240731123330867

  • 第一级分解后的SC

    image-20240731123401949

  • 第二级分解:传入分支的分解

    image-20240731123505313

    image-20240731123519678

  • 第二级分解:传出分支的分解

    image-20240731123556859

  • 第二级分解:中心加工分支的分解

    image-20240731123625191

4.5.4 事务分析方法

虽然在任何时候都可以使用变换分析,但是当数据流具有明显的事务特点时,也就是有一个明显的发射中心(事务中心时),采用事务分析会更合适

步骤

  1. 在DFD上确定事务中心、发送部分、接受部分

  2. 画出结构图框架,把DFD上的三部分分别映射为事务控制模块、发送动作模块和接收模块

  3. 分解细化接受分支和发送分支,完成初始结构图

示例

image-20240731123809591

4.5.5 设计优化

  • 在设计早期阶段尽量对软件结构进行精化

  • 在有效模块化前提下,使用最少的模块和最简单的数据结构