<软件工程>2 结构化方法
本文最后更新于:2023年11月3日 凌晨
SE2 结构化方法
结构化方法认为一切系统都是由信息流构成的。每个信息流有其起点(数据源)与终点(数据潭),还有驱动数据流的加工。因此,所谓信息处理主要表现为信息的流动。
基于上述世界观,结构化方法提出了一些基本原则:
- 自顶向下,功能分解
- 数据抽象
- 功能、过程抽象
- 模块化
结构化方法是一种系统化的软件开发方法。结构化方法的抽象层,包括:
- 需求分析层(需求规约),对应:结构化分析方法
- 设计层(总体设计),对应:结构化设计方法
- 实现层(详细设计),对应:结构化程序设计方法
围绕结构化方法的基本原则,结构化方法给出了:完备的符号、可操作的过程、易理解的表示工具。并提供了控制信息组织复杂性的机制,例如逐层分解,数据打包等,以支持将问题空间的一个问题映射为解空间的一个解
结构化方法捕获的 “过程” 和 “数据” 是客观事物的易变性质,且解的结构没有保持原系统的结构。因此,结构化方法造成了维护、验证上的困难。
SE2.1 结构化分析方法
结构化分析方法:系统化使用问题域术语,给出该问题的模型
好的需求技术应具有以下基本特征:
- 提供方便通信的机制。比如:在不同开发阶段,使用对相关人员易于理解的语言
- 鼓励需求分析人员使用问题空间的术语思考问题,编写文档
- 提供定义系统边界的方法
- 提供支持抽象的基本机制,如 ”划分“、”功能抽象“、”结构抽象“ 等
- 为需求分析人员提供多种可供选择的方案
- 提供特定的技术、适应需求的变化等
实现软件需求分析目标的要求:
-
提供一组术语,指导需求抽象中需要关注的主要方面,并用于表达分析中所使用的信息
这些术语形成一个特定的抽象层,即需求层
-
根据术语形成的空间,给出表达模型的工具,支持表达系统功能形态
-
给出过程指导,以支持系统化使用相关信息建造系统模型
# 基本术语
-
数据流
数据是信息的载体。数据流是数据的流动,
数据流是一类术语,在使用中一般要给出标识:
graph LR A( ) --数据流--> B( )
该标识是一个名词或名词短语,往往直接使用实际问题空间中的概念。
-
加工
加工是数据的变换单元。它接受输入的数据,处理后产生输出。
graph LR A((加工))
该标识一般采用动宾结构,往往直接使用实际问题空间中的概念。
加工 是数据变换单元,因此必须关联一个或多个 输入/输出数据流。
-
数据存储
数据存储是数据的静态结构
graph LR A>数据存储]
注:本应以 双横线 表示数据存储。力有未逮,姑且以旗形代替
该标识是一个名词或名词短语,往往直接使用实际问题空间中的概念。
-
数据源、数据潭
”边界“ 是表达一个事物语义的重要因素。
数据源是数据流的起点,数据潭是数据流的归宿。数据源和数据潭是系统之外的实体
引入这两个术语的目的是未来表示系统环境。可以使用他们和相关数据流来定义系统边界
graph TB A[数据源] B[数据潭]
该标识是一个名词或名词短语,往往直接使用实际问题空间中的概念。
# 数据流图
结构化分析给出了一种表达功能模型的工具,即 数据流图(DFD 图)
一个示例:
graph LR
f
a[旅行社] --订票单--> b((订票)) --航班--> c((备票))
a1>航班目录] --> b --> d((记账))
c --机票--> e[旅客]
d --账单--> e
f>记账文件] --> d
d --> f
DFD 图是一种描述数据变换的图形化工具,其中包含的元素可以是数据流、数据存储、加工、数据源、数据潭等
使用 DFD 图时,应注意以下问题:
- 数据流起到连接其他实体的作用。在应用中,数据流和数据存储一般要给出标识。在语义清晰的情况下,才能省略标识
- 加工之间可以有多个数据流。这些数据流间可以无关。数据流图不表明其先后顺序
- 对于较大软件系统,为便于理解,常采用多层次的数据流图
# 建模过程
结构化分析方法建模的基本步骤属于 ”自顶向下,逐步求精” 的模式
-
建立系统环境图,确定系统语境
经过需求发现后,按照系统工程观点,可以确定系统的数据源和数据潭,以及相关数据流。据此,可以形成系统顶层数据流图(系统环境图)
graph LR A[旅行社] --订票单--> b((订票系统)) --机票--> c[旅客] b --账单--> c
该顶层的 “大加工” 就是待建系统
可见,结构化方法是通过系统顶层数据流图来定义系统语句的
-
自顶向下,逐步求精,建立系统的层次数据流图
在顶层数据流图基础上,按功能分解的设计思想,自顶向下对加工进行分解。
自顶向下画出各层数据流图,直至底层加工足够简单,不需要继续分解为止。这样的加工叫做叶加工。
graph LR subgraph 1 层 direction RL subgraph 1.1 c((2.1)) --> c1((2.2)) --> c4( ) c((2.1)) --> c2((2.3)) --> c4 c((2.1)) --> c3((2.4)) --> c4 end subgraph 1.2 d( ) --> d1((3.1)) --> d2((3.2)) --> d5((3.5)) --> dd( ) d1 --> d3((3.3)) --> d5 d1 --> d4((3.4)) --> ddd( ) end end subgraph 0 层 b( ) --> B((1)) --> bb((2)) bbb( ) --> B --> bbbb((3)) bbbb --> b1( ) bbbb --> b2( ) end subgraph 顶层 a( ) --> A((11)) --> aa( ) aaa( ) --> A --> aaaa( ) end
为便于管理,从 0 层起需对其中的加工进行编号。这些编号在整个系统中唯一。
由 “父图” 加工为 “子图” 的步骤如下:
-
将父图的每一加工按照功能分解为若干子加工
-
将父图的输入/输出流分配到相关的子加工
输入流需要分流时,在输入处引入判定要求
-
在各加工间建立合理的关联,必要时引入数据存储
-
-
定义数据字典
依据系统的数据流图,定义其中包含的所有数据流和数据存储的数据结构
所有数据都可以用以下三种结构表示
-
顺序结构
数据 A 是由数据 B 及数据 C 顺序构成的。记为 +
如:学生成绩 = 姓名 + 性别 + 学号 + 科目 + 成绩
-
选择结构
数据 A 是由数据 B 或数据 C 构成的,不可能既是 B 又是 C。记为 |
如:性别 = 男 | 女
-
重复结构
数据 A 是由复数个数据 B 构成的。记为 { }
如:学生成绩表 = { 学生成绩 }
-
子界
数据 A 的取值在 m 到 n 间,记为 m…n
数据结构符号表:
符号 描述 = 等价于(定义为) + 与(顺序结构) { } 重复(循环结构) | 或(选择结构) ( ) 任选 m..n 界域 在数据字典中,为方便阅读,通常按照 数据流条目、数据存储条目、数据项条目 的顺序来进行组织。
如:
销售的商品 = 商品名 + 商品编号 + 单价 + 数量 + 销售时间
-
-
描述加工
依据系统的数据流图,对其中每一项加工进行说明
需求分析的目的是定义问题,因此此处只需给出输入数据与输出数据间的关系
所有上层加工都被分解为了底层加工,只需对那些底层的叶加工进行说明
有以下 3 种表达工具
-
结构化自然语言
加工的输入/输出数据间,逻辑关系较为简单时,也能用结构化的自然语言表述
if 订票量 > 20 then 订票折扣 = 15% else 订票折扣 = 5%
-
判定表
旅游时间 7 ~ 9 月、12 月 1 ~ 6 月、10 月、11 月 订票量 ≤20 >20 ≤20 >20 折扣量 5% 15% 20% 30% -
判定树
-
# 注意细节
- 模型平衡问题:
- 每个 数据流 和 数据存储 需要在数据字典中定义,且和数据名一致
- 每个底层叶加工 需要在数据字典中描述,且和加工名一致。那些描述中的数据流也必须是数据字典定义的数据流。
- 某加工的数据流 和 分解的子图数据流 要保持一致
- 信息复杂性控制问题:
- 上层数据流可以打包。打包数据流要以 * 符号标记,上下层间的对应关系通过数据字典予以描述。
- 一副子图中的图元个数控制在 7 ± 2 个
- 尽量控制与某一加工相关的数据流数量
- 分析数据内容,确保所有数据信息都用于产出输出信息,并让加工产出的信息都能由进入该加工的信息导出
SE2.2 结构化设计
依据需求规约,在一个抽象层上建立系统软件模型,包括软件体系结构(数据和程序结构),以及详细的处理算法,产生设计规格说明书
结构化设计可以分为:总体设计、详细设计
实现软件设计目标对结构化方法的要求:
- 提供可体现 “原理/原则” 的一组术语,形成一个特定的抽象层,用于表达设计中使用的部件
- 根据术语形成的空间,给出表达模型的工具
- 给出设计的过程指导
# 总体设计
结构化方法在设计中引入了两个基本概念:模块、模块调用
总体设计阶段的基本任务是把系统的功能需求分配到一个特定的软件系统结构中。
其工具有:
-
模块结构图(MSD)
graph TB A0[产生最佳解] A0 --- A1[得到好输入] A2[计算最佳解] A0 --- A2 A3[输出结果] A0 --- A3 A1 --- A11[读输入] A1 --- A12[编辑输入] A3 --- A31[结果格式化] A3 --- A32[显示结果]
模块结构图是一种描述软件宏观结构的图形化工具。图中每个方框代表一个模块,连接上下层的线段表示其调用关系。
处于较高层次的是控制模块,低层次的是从属模块。依据控制模块的内部逻辑,一个控制模块可以调用一个或多个从属模块;一个从属模块也可能被多个控制模块调用
还可以使用带注释的箭头线来表示模块调用过程中传递的信息。尾部空心圆代表传递数据信息,实心圆代表传递控制信息
模块结构图是系统的高层蓝图,允许设计人员在较高层次上进行抽象思维
-
层次图
主要用于描述软件的层次结构。图中每个方框代表一个模块,方框间的线段表示其调用关系。
graph TB A0[正文加工系统] A0 --- A1[输入] A0 --- A2[输出] A0 --- A3[编辑] A0 --- A4[加标题] A0 --- A5[存储] A0 --- A6[检索] A0 --- A7[编目录] A0 --- A8[格式化] A2 --- A21[添加] A2 --- A22[删除] A2 --- A23[输入] A2 --- A24[修改] A2 --- A25[合并] A2 --- A26[列表]
层次图很适合在自顶向下设计软件的过程中使用
-
HIPO 图
HIPO 是 “层次图 + 输入/处理/输出” 的缩写。HIPO 图由 H 图和 IPO 图构成。
H 图即上文的层次图,但其中每个方框都带有编号。
对于 H 图的每个方框,都应有一张 IPO 图,以描述该方框所代表的模块处理逻辑。其基本形式是:在左侧的输入框中列出产生的输出数据,中间的处理框中列出主要的处理和处理次序,右边的输出框中列出产生的输出数据。此外,还会使用箭头指出数据通信的情况
结构化设计方法要将 DFD 图映射为设计层面上的 模块和模块调用。
# 总体设计步骤
基本步骤是:首先将系统的 DFD 图转化为初始的模块结构图,再基于 “高内聚低耦合” 的设计原理,通过模块化,将初始的模块结构图转化为最终的、可供详细设计使用的 模块结构图(MSD)
第一步:将 DFD 图转化为初步的 MSD 图
DFD 图可以分为以下两种:
-
变换型数据流图
具有较明显的 输入部分和变换(主加工)部分之间的界面、变换部分和输出部分间的界面 的数据流图,称为变换型数据流图
graph TB A[主控模块] A --- A1[输入模块<br/>取得 c] A --- A2[c 变成 d 或 f] A --- A3[输出模块<br/>输出 d] A --- A4[输出模块<br/>输出 f]
该类 DFD 图对应的系统,在高层次上讲,由 3 部分组成:处理输入数据的部分、数据变换部分、处理数据输出部分。
数据首先进入处理输入部分,由外部形式转换为系统内部形式。之后,进入变换部分,将其变换为待输出的数据。最后,由处理输出部分将其转换为用户需要的数据形式。
变换设计对应的软件体系结构,应由 “主控” 模块以及与该模式的 3 个部分(输入、变换、输出)对应的模块组成。
-
事务型数据流图
数据到达一个加工 T 后,该加工 T 根据数据的值,在其后若干动作序列(事务)中选一个执行,这类数据流图称为 事务型数据流图
graph TB A[事务中心] A --- A1[输入模块<br/>取得 a] A --- A2[调度模块] A --- A3[输出模块<br/>给出 h] A2 --- A21[路径 bcd] A2 --- A22[路径 efg] A2 --- A23[路径 xyz]
上面的加工 T 称为 事务中心。其完成以下任务:
- 接收输入数据
- 分析并确定对应事务
- 选取与该事务对应的一条活动路径
事务型数据流图描述系统的数据处理模式为 “集中 - 发散” 式
任何软件系统本质上都是信息的变换装置。因此,原则上任何 DFD 图都能被归为变换型。但如果其中某些部分具有事务型特征,就可以将这些部分按照事务型数据流图进行处理。
总体设计分为 3 个阶段:初始设计、精化设计、复审阶段。
基于自顶向下,功能分解的设计原则,针对两种不同的数据流图提出了两种初始设计,即 变换设计 和 事务设计。
-
变换设计
目标是将变换型数据流图转换为系统的模块设计图
-
设计准备 —— 复审并精化系统模型
-
确定 输入、变换、输出 的边界
-
第一级分解 —— 系统模块结构图 顶层、第一层 设计
变换型数据流图具有 输入、变换、输出 模块。为了协调各模块工作,还需有一个主控模块
-
第二级分解 —— 自顶向下,逐步求精
-
-
事务设计
当数据流图具有明显事务型特征时,适宜采用事务设计
- 设计准备 —— 复审并精化系统模型
- 确定事务处理中心
- 第一级分解 —— 系统模块结构图 顶层、第一层 设计
- 第二级分解 —— 自顶向下,逐步求精
第二步:将初始的 MSD 图转化为最终可供详细设计使用的 MSD 图
基于模块化原理 - 高内聚低耦合,给出设计规则 - 经验规则 - 启发式规则 用于精化初始的 MSD
-
模块:执行一个任务的过程及相关的数据结构。通常由两部分组成:
- 接口:由其他模块或例程访问的常量、变量、函数等
- 实现:接口的实现(模块功能的执行机制)
-
模块化:把系统分解成若干高内聚低耦合的模块的过程
当问题变得复杂时,解决问题的工作量也会更多。将两个问题合并解决时,其复杂性大于单独解决。因此,尽可能拆分问题,这有助于减少工作量。
考虑到集成模块的工作量也会随着模块数量增长,所以将模块划分得恰如其分时,那个工作量最少
模块化评价的原则是:高内聚、低耦合
-
耦合:不同模块间的互相依赖程度。
耦合强度的依赖因素有:
- 一个模块对另一模块的引用
- 一个模块向另一模块传递的数据量
- 一个模块施加到另一模块的控制的数量
- 模块间接口的复杂程度
耦合从类型上又分为几类(由强到弱):
-
内容耦合:一个模块直接修改或操作另一模块的数据
graph LR A{{模块A}} B{{模块B}} A--直接修改-->B
-
公共耦合:多个模块共同引用同一全局数据项
graph BT O((公共数据)) A{{模块A}} B{{模块B}} A--引用-->O B--引用-->O
-
控制耦合:一个模块向另一模块传递一个控制信号,接受信号的模块将依据该信号进行必要活动
graph LR A{{模块A}} B{{模块B}} A--控制信号-->B
-
标记耦合:一个模块向两个模块传递一个公共参数
graph LR A{{模块A}} B{{模块B}} C{{模块C}} A--传递---O((公共数据))-->B O-->C
-
数据耦合:模块间通过参数传递基本类型的数据
graph LR A{{模块A}} B{{模块B}} A--控制耦合---O((简单数据))-->B
如果模块间必须存在耦合,应尽量使用数据耦合,少用控制耦合,限制公共耦合范围,并坚决避免使用内容耦合
-
内聚:一个模块内,各成分间的相互依赖程度
好的设计应满足:
- 模块功能单一
- 模块各部分都和模块功能直接相关
- 高内聚
内聚的类型(由低到高):
- 偶然内聚:模块内各成分间没有任何关系
- 逻辑内聚:几个逻辑上相关的功能放在同一模块中
- 时间内聚:一个模块完成的功能必须在同一时间内完成,而这些功能只是因为时间因素关联在一起
- 过程内聚:各成分必须以特定次序执行
- 通信内聚:各成分都操作在同一数据集或生成统一数据集
- 顺序内聚:各成分与一个功能相关,且一个成分的输出作为另一成分的输入
- 功能内聚:模块的所有成分对完成单一功能是最基本的,且该模块对完成这一功能而言是充分必要的
-
启发式规则:是根据设计准则,从长期的软件开发实践中,总结出来的规则。
启发式规则不是设计目标,也不是设计时应该普遍遵循的原理
常见的启发式规则:
-
改进软件结构,提高模块独立性
通过模块的分解和合并,达到降低耦合,提高内聚
-
模块规模适中,每页 60 行语句
-
深度、宽度、扇入、扇出适中
深度:软件结构中控制的层数。其标示一个系统的大小和复杂程度
宽度:软件结构中同一层次上的模块总数的最大值
扇入:被多少上级模块调用。不违背模块独立性的条件下,扇入越大越好
扇出:调用多少下级模块。扇出过大则模块过分复杂,过小说明功能过度集中。
对于好的系统,应该做到:顶层扇出高,中层扇出少,底层扇入高
-
模块的作用域力争在控制域内
作用域:受该模块内一个判定影响的所有模块的集合
控制域:模块本身 + 所有直接或间接从属于它的模块集合
-
降低模块接口的复杂性
-
模块功能应该可以预测
-
SE2.3 结构化程序设计
# 详细设计
具体描述模块结构图中的每一模块,给出实现其功能的实现机制。包括一组 例程 和 数据结构。
详细设计的目标:将总体设计阶段产生的高层结构映射为以相关术语表达的低层结构,也是系统的最终结构。
结构化程序设计方法:一种基于结构的编程方法,采用 顺序结构、选择结构 和 循环结构 进行编程。其中每个结构只允许一个入口和出口。结构化程序设计的本质,是使程序的控制流程线性化,实现程序动态执行顺序符合静态书写的结构,提高程序的可读性。
# 详细设计工具
-
程序流程图
又称程序框图。是历史最悠久,使用最广泛的描述过程设计的方法。然而也是使用最为混乱的方法。
graph LR END1(结束) TOP(开始)-->A{分支判断}--true-->B(语句)-->W{循环判断}--false-->E(结束) A--false-->END1(结束) W--true-->B
-
盒图(N-S 图)
完全去掉了流程线,算法的每一步都用一个矩形框来描述,把一个个矩形框按执行的次序连接起来就是一个完整的算法描述。
(N-S图_SE2_3_2_2)
-
问题分析图(PAD 图)
执行顺序是从最左主干线的上端的结点开始,自上而下依次执行。每遇到判断或循环,就自左而右进入下一层,从表示下一层的纵线上端开始执行,宜到该纵线下端,再返回上一层的纵线的转入处。如此继续,直到执行到主干线的下端为止。
(PAD图_SE2_3_2_3)
-
类程序设计语言(PDL)
也称 伪码。用正文形式表示数据和处理过程。
PDL 有严格的关键字外部语法,用于定义控制结构和数据结构。
PDL 一般是一种混杂的语言,其使用一种语言(自然语言)的词汇,却使用另一语言(结构化程序设计语言)的语法。
# 设计规约
设计规约是一份正式性文档,其完整准确地描述满足需求规约所要求的所有功能模块,以及伴随功能模块而出现的非功能机制。设计规约包括 概要设计规约 和 详细设计规约。
-
概要设计规约
指明高层软件体系结构。包括:系统环境、软件模块结构、模块描述、文件结构和全局数据文件的逻辑结构、测试需求
-
详细设计规约
主要作为软件设计人员和程序员间交流的媒体。包括:各处理过程的算法、算法所涉及的全部数据结构的描述
SE2.4 设计评审
目前存在两种不同的评审方法:非正式评审、正式技术评审
软件设计评审细节
- 概要设计评审和详细设计评审必须分开进行,不能合并为一次评审
- 概要设计评审评价从需求到设计数据和体系结构的变换
- 详细设计评审,通常叫详细设计走查,注重算法设计的正确性
- 建立一个议事日程并遵循它
- 审计设计文档,不评审设计者
- 评审中提出的问题应该详细记录,但不要谋求当场解决
- 限制参与人数和坚持充分准备
- 除软件开发人员外,概要设计评审必须有用户代表参加,必要时可以邀请有关领域专家到会
- 详细设计评审一般不邀请用户和其他领域的代表
- 为设计文档开发一个检查表,以帮助评审人员集中在重要问题上
- 为了提高评审效率,所有评审的参加者应接受一定的正规培训
- 评审结束前,做出本次评审是否通过的结论
概要设计评审检查表
- 软件体系结构是否反映了软件需求
- 达到高的模块化吗?模块功能独立吗?
- 模块与外部系统元素接口定义了吗?
- 数据结构与软件需求一致吗?
- 考虑了可维护性吗?
- 是否直接评价了质量因素?
详细设计评审检查表
- 算法是否能完成所要求的功能
- 算法逻辑是否正确
- 接口与体系结构设计是否一致
- 逻辑的复杂性合理吗
- 是否规定了错误处理和反故障处理
- 正确定义了局部数据机构吗
- 都使用了结构化变成构造吗
- 用到是哪个操作系统或语言独立性
- 是否考虑到可维护性