软件工程导论—总体设计(上)

简介: 软件工程导论—总体设计

文章目录


1. 设计过程

1.1. 软件设计简述

1.2. 设计过程

2. 设计原理

2.1. 模块化

2.2. 抽象

2.3. 逐步求精

2.4. 信息隐藏和局部化

2.5. 模块独立

2.5.1. 耦合

2.5.2. 内聚

3. 启发规则

4. 描绘软件结构的图形工具

4.1. 层次图和HIPO图

4.2. 结构图

5. 面向数据流的设计方法

5.1. 概述

5.2. 变换分析

5.3. 事务分析

5.4. 设计优化


正文


1. 设计过程


1.1. 软件设计简述


软件设计阶段从不同的角度有不同的划分方法,下面举三个典型的设计阶段划分的例子:


从工程管理的角度,可以将软件设计分为概要设计阶段和详细设计阶段;

从技术的角度,传统的结构化方法将软件设计划分为体系结构设计、数据设计、接口设计和过程设计4部分;

面向对象软件设计方法,则将软件设计划分为体系结构设计、类设计/数据设计、接口设计和构件级设计4部分。

1.png

结构化分析和设计是最常用的软件需求分析和软件设计方法,他们的关系可以用下面的一张图表示:

2.png

对于总体设计来说,它的设计过程按照如下的步骤进行:首先寻找实现目标系统的各种不同的方案;然后分析员从这些供选择的方案中选取若干个合理的方案,从中选出一个最佳方案向用户和使用部门负责人推荐;分析员应该进一步为这个最佳方案设计软件结构,进行必要的数据库设计,确定测试要求并且制定测试计划。


总体设计可以站在全局高度上,花较少成本,从较抽象的层次上分析对比多种可能的系统实现方案和软件结构,从中选出最佳方案和最合理的软件结构,从而用较低成本开发出较高质量的软件系统。


1.2. 设计过程


典型的总体设计过程由两个主要阶段组成:


1、系统设计阶段,确定系统的具体实现方案


设想供选择的方案

根据需求分析阶段得出的数据流图考虑各种可能的实现方案,力求从中选出最佳方案.

选取合理的方案

从前一步得到的一系列供选择的方案中选取若干个合理的方案。对每个合理的方案分析员都应该准备下列4份资料:(1)系统流程图;(2)组成系统的物理元素清单;(3)成本/效益分析;(4)实现这个系统的进度计划。

推荐最佳方案

分析员应该综合分析对比各种合理方案的利弊,推荐一个最佳的方案,并且为推荐的方案制定详细的实现计划。


2、结构设计阶段,确定软件结构


功能分解

首先进行结构设计,然后进行过程设计。结构设计确定程序由哪些模块组成,以及这些模块之间的关系,属于总体设计阶段的任务;过程设计确定每个模块的处理过程,属于详细设计阶段的任务。

设计软件结构

通常程序中的一个模块完成一个适当的子功能,应该把模块组织成良好的层次系统,可以用层次图或结构图来描绘软件结构。如果数据流图已经细化到适当的层次,则可以直接从数据流图映射出软件结构,这就是面向数据流的设计方法。

设计数据库

对于需要使用数据库的那些应用系统,软件工程师应该在需求分析阶段所确定的系统数据需求的基础上,进一步设计数据库。

制定测试计划

在软件开发的早期阶段考虑测试问题,能促使软件设计人员在设计时注意提高软件的可测试性。

书写文档

应该用正式的文档记录总体设计的结果,在这个阶段应该完成的文档通常有下述五种:(1) 系统说明 ;(2) 用户手册;(3) 测试计划;(4) 详细的实现计划;(5) 数据库设计结果

审查和复审

最后应该对总体设计的结果进行严格的技术审查和管理复审。


2. 设计原理


2.1. 模块化


模块是由边界元素限定的相邻程序元素的序列,有一个总体标识符代表它。


所谓模块化,就是把程序划分成独立命名且可独立访问的模块,每个模块完成一个子功能,把这些模块集成起来构成一个整体,可以完成指定的功能满足用户的需求。


之所以提倡模块化,是因为它可以使一个复杂的大型程序,能被人的智力所管理;相反,如果一个大型程序仅由一个模块组成,它将很难被人所理解。总的来说,模块化有以下四个作用:


采用模块化原理可以使软件结构清晰,不仅容易设计也容易阅读和理解;

模块化使软件容易测试和调试,因而有助于提高软件的可靠性;

模块化能够提高软件的可修改性;

模块化有助于软件开发工程的组织管理。

以上四个作用都有助于减少软件开发的工作量,进而降低软件开发成本

设问题P PP的复杂程度为C ( P )解决问题P 所需的工作量为E ( P ),如果有C ( P 1 ) > C ( P 2 )根据人类解决一般问题的经验:

3.png

这个公式说明了模块化对于减少工作量有着积极的作用。

在长期实践的过程中,开发者们总结出了模块化和软件成本之间的关系,如下图所示:

4.png

从图中可以看出,每个程序都相应地有一个最适当的模块数目M MM,使得系统的开发成本处于最小成本区间。


对于一个设计方法来说,可以从以下五个方面评价它的定义模块能力:


模块可分解性;

模块可组装性 ;

模块可理解性;

模块连续性 ;

模块保护性。


2.2. 抽象


Grady Boach:“抽象是人类处理复杂问题的基本方法之一。”

现实世界中一定事物、状态或过程之间总存在着某些相似的方面(共性)。把这些相似的方面集中和概括起来,暂时忽略它们之间的差异,这就是抽象。


简而言之,抽象就是抽出事物本质特性而暂时不考虑细节。


处理复杂系统的惟一有效的方法是用层次的方式构造和分析它。一个复杂的动态系统首先可以用一些高级的抽象概念构造和理解,这些高级概念又可以用一些较低级的概念构造和理解,如此进行下去,直至最低层次的具体元素,例如过程抽象和数据抽象的过程。


将上面的抽象过程应用到软件工程中,可以看到,软件工程抽象过程的每一步都是对软件解法的抽象层次的一次精化。在可行性研究阶段,软件作为系统的一个完整部件;在需求分析期间,软件解法是使用在问题环境内熟悉的方式描述的;当由总体设计向详细设计过渡时,抽象的程度也就随之减少了;最后,当源程序写出来以后,也就达到了抽象的最低层。


2.3. 逐步求精


一个人在任何时候都只能把注意力集中在(7±2)个知识块上。——Miller法则


为了能集中精力解决主要问题而尽量推迟对问题细节的考虑。逐步求精是人类解决复杂问题时采用的基本方法,也是许多软件工程技术的基础。


逐步求精能帮助软件工程师把精力集中在与当前开发阶段最相关的那些方面上,而忽略那些对整体解决方案来说虽然是必要的,然而目前还不需要考虑的细节。


逐步求精方法确保每个问题都将被解决,而且每个问题都将在适当的时候被解决,但是,在任何时候一个人都不需要同时处理7个以上知识块。


其实逐步求精在软件工程中很好理解也很常见,例如先写伪代码再实现为真实代码,先写设计框架再逐步完善等等。


展开来说就是,对一个复杂的问题不应该立刻用计算机指令、数字和逻辑符号来表示,而应该用较自然的抽象语句来表示,从而得出抽象程序。


抽象程序对抽象的数据进行某些特定的运算并用某些合适的记号(可能是自然语言)来表示。对抽象程序做进一步的分解,并进入下一个抽象层次,这样的精细化过程一直进行下去,直到程序能被计算机接受为止。这时的程序可能是用某种高级语言或机器指令书写的。


2.4. 信息隐藏和局部化


信息隐藏可以使一个模块内包含的信息(过程和数据)对于不需要这些信息的模块来说,是不能访问的。


局部化的概念和信息隐藏概念是密切相关的。所谓局部化是指把一些关系密切的软件元素物理地放得彼此靠近。显然,局部化有助于实现信息隐藏。


信息隐藏和局部化意味着有效的模块化可以通过定义一组独立的模块而实现,这些独立的模块彼此间仅仅交换那些为了完成系统功能而必须交换的信息。使用信息隐藏原理作为模块化系统设计的标准就会带来极大好处。因为绝大多数数据和过程对于软件的其他部分而言是隐藏的,在修改期间由于疏忽而引入的错误就很少可能传播到软件的其他部分。


2.5. 模块独立


模块独立的概念是模块化、抽象、信息隐藏和局部化概念的直接结果。希望这样设计软件结构,使得每个模块完成一个相对独立的特定子功能,并且和其他模块之间的关系很简单。


有效模块化(即具有独立的模块)的软件比较容易开发出来。这是由于能够分割功能而且接口可以简化,当许多人分工合作开发同一个软件时,这个优点尤其重要。相对说来,独立的模块比较容易测试和维护,单个模块修改设计和程序需要的工作量比较小,错误传播范围小,需要扩充功能时能够“插入”模块。


模块独立程度有两个定性标准度量:


耦合,衡量不同模块彼此间互相依赖(连接)的紧密程度。耦合要低,即每个模块和其他模块之间的关系要简单;

内聚,衡量一个模块内部各个元素彼此结合的紧密程度。内聚要高,每个模块完成一个相对独立的特定子功能。


2.5.1. 耦合


耦合是对一个软件结构内不同模块之间互连程度的度量。在软件设计中应该追求尽可能松散耦合的系统。


模块间的耦合程度强烈影响系统的可理解性、可测试性、可靠性和可维护性。 模块间联系简单,发生在一处的错误传播到整个系统的可能性就很小,联系简单可以方便研究、测试或维护任何一个模块,而不需要对系统的其他模块有很多了解;


耦合程度分为以下几个度量等级:


非直接耦合/完全独立(no direct coupling)

如果两个模块中的每一个都能独立地工作而不需要另一个模块的存在,那么它们完全独立。这是一个理想等级,在一个软件系统中不可能所有模块之间都没有任何连接。

5.png

数据耦合(data coupling)

如果两个模块彼此间通过参数交换信息,而且交换的信息仅仅是数据,那么这种耦合称为数据耦合。数据耦合是理想的目标,系统中至少必须存在这种耦合。一般说来,一个系统内可以只包含数据耦合。它维护更容易,对一个模块的修改不会是另一个模块产生退化错误。

6.png

控制耦合(control coupling)

如果两个模块彼此间传递的信息中有控制信息,这种耦合称为控制耦合。 被调用的模块需知道调用模块的内部结构和逻辑,降低了重用的可能性 。但一般来说控制耦合往往是多余的,把模块适当分解之后通常可以用数据耦合代替它。

7.png

特征耦合(stamp coupling)

当把整个数据结构作为参数传递而被调用的模块只需要使用其中一部分数据元素时,就出现了特征耦合。被调用的模块可使用的数据多于它确实需要的数据,这将导致对数据的访问失去控制,从而给计算机犯罪提供了机会,当把指针作为参数进行传递时,应该仔细检查该耦合。

公共环境耦合(common coupling)

当两个或多个模块通过一个公共数据环境相互作用时,它们之间的耦合称为公共环境耦合。公共环境可以是全程变量、共享的通信区、内存的公共覆盖区、任何存储介质上的文件、物理设备等等。

公共环境耦合有两种类型: (1)一个模块往公共环境送数据,另一个模块从公共环境取数据。数据耦合的一种形式,是比较松散的耦合;(2)两个模块都既往公共环境送数据又从里面取数据,这种耦合比较紧密,介于数据耦合和控制耦合之间。

8.png

公共环境耦合的模块难于重用,必须提供一个全局变量的清单。如果在一个模块中对一个全局变量的声明进行修改,必须修改能够访问该全局变量的每一个模块。并且它潜在的危险很大,模块暴露出必需要更多的数据,难以控制数据存取,甚至会导致计算机犯罪。即使模块本身不改变,它和产品中其他模块之间公共环境耦合的实例数也会变化非常大。最重要的是,公共环境耦合与结构化编程矛盾,生成的代码完全不可读,只有在少数的特殊情况下公共环境耦合才可以发挥比较好的效果。

内容耦合(content coupling)

最高程度的耦合是内容耦合。如果出现下列情况之一,两个模块间就发生了内容耦合:(1)一个模块访问另一个模块的内部数据;(2)一个模块不通过正常入口转到另一个模块的内部;(3)两个模块有一部分程序代码重叠;(4)一个模块有多个入口。

9.png

耦合是影响软件复杂程度的一个重要因素,在实际设计中,应该采取下述设计原则:(1)尽量使用数据耦合;(2)少用控制耦合和特征耦合;(3)限制公共环境耦合的范围;(4)完全不用内容耦合。


2.5.2. 内聚


内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐藏和局部化概念的自然扩展。简单地说,理想内聚的模块只做一件事情。设计时应该力求做到高内聚,通常中等程度的内聚也是可以采用的,而且效果和高内聚相差不多;但是,低内聚不要使用。


内聚和耦合是密切相关的,模块内的高内聚往往意味着模块间的松耦合。实践表明内聚更重要,应该把更多注意力集中到提高模块的内聚程度上。


内聚程度分为以下几个度量等级:


偶然内聚(coincidental cohesion)

如果一个模块完成一组任务,这些任务彼此间即使有关系,关系也是很松散的,就叫做偶然内聚。

在偶然内聚这个等级,模块内各元素之间没有实质性联系,很可能在一种应用场合需要修改这个模块,在另一种应用场合又不允许这种修改,从而陷入困境。这导致程序的可理解性差,可维护性产生退化,模块也是不可重用的。


10.png

逻辑内聚(logical cohesion)

如果一个模块完成的任务在逻辑上属于相同或相似的一类,则称为逻辑内聚。但逻辑内聚完成多个操作的代码互相纠缠在一起,即使局部功能的修改有时也会影响全局,导致严重的维护问题,难以重用。同时导致接口难以理解,造成整体上不易理解。

11.png

要解决代码纠缠的问题,可以进行模块分解。

12.png

时间内聚(temporal cohesion)

如果一个模块包含的任务必须在同一段时间内执行,就叫时间内聚。

时间关系在一定程度上反映了程序某些实质,所以时间内聚比逻辑内聚好一些;模块内操作之间的关系很弱,与其他模块的操作却有很强的关联,并且时间内聚的模块不太可能重用。


过程内聚(procedural cohesion)

如果一个模块内的处理元素是相关的,而且必须以特定次序执行,则称为过程内聚。使用程序流程图作为工具设计软件时,常常通过研究流程图确定模块的划分,这样得到的往往是过程内聚的模块。过程内聚比时间内聚要好一些,至少操作之间是过程关联的,仍是弱连接,不太可能重用模块。


通信内聚(communicational cohesion)

如果模块中所有元素都使用同一个输入数据和(或)产生同一个输出数据,则称为通信内聚。即在同一个数据结构上操作。模块中各操作紧密相连,比过程内聚更好,但是它会导致模块不能重用。


顺序内聚(sequential cohesion)

如果一个模块内的处理元素和同一个功能密切相关,而且这些处理必须顺序执行,则称为顺序内聚。根据数据流图划分模块时,通常得到顺序内聚的模块,这种模块彼此间的连接往往比较简单。


功能内聚(functional cohesion)

如果模块内所有处理元素属于一个整体,完成一个单一的功能,则称为功能内聚。功能内聚是最高程度的内聚。功能内聚可隔离错误,使得维护更容易,也更易扩展。


所有的内聚类型可以分为:高内聚、中内聚和低内聚三种类型


高内聚:功能内聚、 顺序内聚;

中内聚:通信内聚、过程内聚;

低内聚:时间内聚、逻辑内聚、偶然内聚。

设计时力争做到高内聚,并且能够辨认出低内聚的模块。


3. 启发规则


改进软件结构提高模块独立性

通过模块分解或合并,降低耦合提高内聚。主要从两个方面改进:(1)模块功能完善化。一个完整的模块包含:执行规定的功能的部分、出错处理的部分、返回一个“结束标志”;(2)消除重复功能,改善软件结构,这里要区分完全相似和局部显示的功能。

13.png

14.png

2. 模块规模应该适中

经验表明,一个模块的规模不应过大,最好能写在一页纸内。通常规定50~100行语句,最多不超过500行。数字只能作为参考,根本问题是要保证模块的独立性。


过大的模块往往是由于分解不充分,但是进一步分解必须符合问题结构,一般说来,分解后不应该降低模块独立性。过小的模块开销大于有效操作,而且模块数目过多将使系统接口复杂。

3. 深度、宽度、扇出和扇入都应适当

深度:软件结构中控制的层数,它往往能粗略地标志一个系统的大小和复杂程度;

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

15.png

扇出:一个模块直接控制(调用)的模块数目;

扇入:有多少个上级模块直接调用它。

16.png

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

模块的作用域定义为受该模块内一个判定影响的所有模块的集合。

模块的控制域是这个模块本身以及所有直接或间接从属于它的模块的集合。

在一个设计得很好的系统中,所有受判定影响的模块应该都从属于做出判定的那个模块,最好局限于做出判定的那个模块本身及它的直属下级模块,例如:

17.png

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

模块接口复杂是软件发生错误的一个主要原因。应该仔细设计模块接口,使得信息传递简单并且和模块的功能一致。

例如解一元二次方程的函数QUAD_ROOT(TBL,X);,其中数组TBL传送方程的系数、数组X送回求得的根,这样是不够合理的,应该写成:QUAD_ROOT(A,B,C,ROOT1,ROOT2);

设计单入口单出口的模块

警告软件工程师不要使模块间出现内容耦合。当从顶部进入模块并且从底部退出来时,软件是比较容易理解的,因此也是比较容易维护的。

模块功能应该可以预测

模块的功能应该能够预测,但也要防止模块功能过分局限。功能可预测是指如果一个模块可以当做一个黑盒子,只要输入的数据相同就产生同样的输出,那就可以说这个模块的功能是可以预测的。

相关文章
|
2月前
|
监控 数据可视化 数据建模
软件工程之设计分析(2)
软件工程之设计分析(2)
28 0
软件工程之设计分析(2)
|
9月前
|
算法 程序员 Go
[软件工程导论(第六版)]第6章 详细设计(复习笔记)
[软件工程导论(第六版)]第6章 详细设计(复习笔记)
|
2月前
|
测试技术
【软件工程】高效需求分析在软件工程中的精要
【软件工程】高效需求分析在软件工程中的精要
75 0
|
2月前
|
设计模式 关系型数据库 UED
软件工程之设计分析(1)
软件工程之设计分析(1)
30 0
|
9月前
|
算法 数据挖掘 数据库
[软件工程导论(第六版)]第5章 总体设计(复习笔记)
[软件工程导论(第六版)]第5章 总体设计(复习笔记)
|
9月前
|
算法 数据库 开发者
[软件工程导论(第六版)]第3章 需求分析(复习笔记)
[软件工程导论(第六版)]第3章 需求分析(复习笔记)
|
10月前
|
算法 人机交互 数据库
软件工程之总体设计
软件工程之总体设计
103 1
|
12月前
|
调度
软件工程导论—总体设计(下)
软件工程导论—总体设计(下)
|
12月前
|
机器学习/深度学习 算法 数据格式
软件工程导论—详细设计(下)
软件工程导论—详细设计(下)
|
12月前
|
算法 程序员