结构化设计方法

(第二部分:结构化分析与设计方法,第4章)

李欣

Created: 2022-06-07 Tue 19:55

0.1. 互动课堂

Click to host the seminar.

0.2. 本次课的目标

第二部分:结构化分析与设计方法

第4章:结构化设计方法

  • 软件设计的概念及原则
  • 结构化设计
  • 体系结构设计
  • 接口设计
  • 数据设计
  • 过程设计
  • 软件设计规格说明
  • 软件设计评审

1. 软件设计的概念及原则

1.1. 软件设计的概念

1.1.1. 什么是设计

设计 是一项核心的工程活动。

在20世纪90年代早期, Lotus 1-2-3 的发明人 Mitch KaporDr. Dobbs 杂志上发表了 软件设计宣言 ,其中指出:

什么是 设计 ?设计是你站在两个世界 — 技术世界 和人类的 目标世界 , 而你尝试将这两个世界 结合 在一起……

1.1.2. 设计良好的建筑与软件

罗马建筑批评家 Vitruvius 提出了这样一个观念:

设计良好的建筑应该展示出 坚固适用 和令人 赏心悦目

good-design-software.png

1.2. 软件设计的原则

1.2.1. 分而治之

  • 分而治之 是人们解决 大型复杂问题 时通常采用的 策略 。 将大型复杂的问题分解为许多容易解决的小问题,原来的问题也就容易解决了。
  • 软件的 体系结构设计模块化设计 都是分而治之策略的 具体表现

尽管模块分解可以简化要解决的问题,但模块分解 并不是越小越好

  • 当模块 数目增加 时,每个模块的 规模将减小 ,开发单个模块的 成本确实减少 了;
  • 但是,随着 模块数目增加 ,模块之间关系的 复杂程度也会增加 , 设计模块间接口所需要的 工作量也将增加 ,如图所示。

module-size-number-cost.png

Figure 1: 模块大小、模块数目与成本的关系

1.2.2. 模块独立性

模块的独立性 是指软件系统中 每个模块 涉及软件要求的具体的 子功能 , 而与软件系统中 其他模块接口简单 的。

例如,若一个模块只具有 单一的功能与其他模块没有太多的联系 , 那么,我们则称此模块具有 模块独立性

  • 一般采用 两个准则 度量模块独立性。即 模块间耦合模块内聚
    • 耦合模块之间相对独立性(互相连接的紧密程度) 的度量。 模块之间的连接越紧密 ,联系越多, 耦合性就越高 ,而其模块 独立性就越弱
    • 内聚模块 功能强度(一个模块内部各个元素彼此结合的紧密程度) 的度量。 一个模块 内部各个元素之间的联系越紧密 ,则它的 内聚性就越高 , 相对地,它与其他模块之间的耦合性就会降低,而模块 独立性就越强

模块独立性比较强的模块应是怎样的模块?


参与人数 0
高度内聚、松散耦合的模块 0
低度内聚、紧密耦合的模块 0
, 高度内聚、松散耦合的模块 , 低度内聚、紧密耦合的模块 , 0 , 0

1.2.3. 提高抽象层次

抽象 是指 忽视 一个主题中与 当前目标 无关的方面 , 以便更充分地 注意当前目标 有关的方面 。 当我们进行软件设计时,设计开始时应尽量提高软件的抽象层次, 按抽象级别从高到低进行软件设计。

将软件的体系结构按自顶向下方式, 对各个层次的 过程细节数据细节 逐层细化, 直到用程序设计语言的语句能够实现为止, 从而最后确立整个系统的体系结构。 这也是我们常说的 自顶向下、逐步细化 的设计过程。

  • 最高的层次 上,使用问题 所处环境的语言 概括地描述问题的解决方案;
  • 较低的抽象层次 上,采用更过程化的方法,将 面向问题的术语面向实现的术语 结合 起来描述问题的解法;
  • 最低的抽象层次 上,用某种 程序设计语言 来描述问题的解法。

过程抽象数据抽象 是两种常用的抽象手段。

1.2.4. 复用性设计

复用 是指同一事物 不做修改稍加修改 就可以 多次重复使用 。 将复用的思想用于软件开发,称为 软件复用 。 我们将 软件的重用部分 称为 软构件

也就是说,在构造新的软件系统时不必从零做起, 可以直接使用 已有的软构件 即可 组装 (或加以合理 修改 )成新的系统。

  • 复用性设计 有两方面的含义:
    • 尽量使用 已有的构件 ,包括 开发环境提供的以往开发类似系统时创建的
    • 如果确实需要创建 新的构件 ,则在设计时应该考虑 将来的可重复使用性

1.2.5. 灵活性设计

保证软件 灵活性设计 的关键是 抽象

面向对象系统中的 类结构 类似一座金字塔,越接近金字塔的 顶端抽象程度越高

抽象 的反义词是 具体 。 理想情况下,一个系统的任何 代码逻缉概念 在这个系统中都应该是 唯一 的, 也就是说 不存在重复的代码

  • 在设计中引入灵活性的方法有:
    • 降低耦合提高内聚 (易于提高替换能力)
    • 建立 抽象 (创建有多态操作的接口和父类)
    • 不要将代码写死 (消除代码中的常数)
    • 抛出异常 (由操作的调用者处理异常)
    • 使用并创建 可复用 的代码

2. 结构化设计

2.1. 结构化软件设计的任务

结构化软件设计 的主要任务是要解决 如何做 的问题, 要在 需求分析 的基础上建立各种 设计模型 , 并通过对设计模型的 分析评估 ,来确定这些模型 是否能够满足需求

软件设计 是将 用户需求 准确地转化成为最终的 软件产品唯一 途径, 在 需求构造 之间起到了 桥梁 作用。

在软件设计阶段,往往存在 多种设计方案 , 通常需要在多种设计方案之中进行 决策折中 , 并使用选定的方案进行后续的开发活动。

software-design-categories-engineering.png

software-design-categories-technique.png

管理技术 两个不同的角度对 设计阶段设计内容 的认识,可以用下图表示。

software-design-categories.png

Figure 2: 设计阶段及设计内容

  • 体系结构设计 定义软件 模块 及其之间的 关系 ,通常也称为 模块设计
  • 接口设计 包括 外部接口设计 (依据分析模型中的 顶层数据流图 )和 内部接口设计
    • 外部接口 包括 用户界面目标系统其他硬件设备软件系统外部接口
    • 内部接口 是指系统内部 各种元素之间接口
  • 数据设计 根据需求阶段所建立的 实体—关系图(ER图) 来确定软件涉及的 文件系统结构数据库表结构
  • 过程设计 是确定 软件各个组成部分内的算法内部数据结构 , 并选定某种表达形式来描述各种 算法

2.2. 结构化设计与结构化分析的关系

2.2.1. 结构化设计与结构化分析的关系

结构化分析结果结构化设计 提供了最基本的输入信息, 两者的关系如图所示。

relationship-analysis-design.png

Figure 3: 结构化设计与结构化分析的关系

2.2.2. 结构化设计方法的实施要点

  1. 研究分析审查 数据流图
  2. 根据 数据流图 决定问题的 类型变换型事务型 。 针对两种不同的类型分别进行分析处理。
  3. 数据流图 推导出系统的 初始结构图
  4. 利用一些 启发式原则 来改进系统的 初始结构图 ,直到得到 符合要求结构图 为止。
  5. 根据分析模型中的 实体关系图数据字典 进行数据设计, 包括 数据库设计数据文件 的设计。
  6. 在上面设计的基础上,并依据分析模型中的 加工规格说明状态转换图控制规格说明 进行 过程设计
  7. 制定 测试计划

2.3. 模块结构及表示

  • 软件的结构包括两部分
    • 一部分为软件的 模块结构
    • 另一部分为软件的 数据结构

一般通过 功能划分 过程来完成 软件结构设计

功能划分过程从 需求分析 确立的 目标系统的模型 出发,对整个问题进行分割, 每一部分用 一个几个软件模块 加以解决,这样整个问题就解决了。

这个过程可以形象地用右图来表示, 该图表明了从 软件需求分析软件设计 的过渡。

software-structure-formation.png

Figure 4: 软件结构的形成

2.3.1. 模块

一个软件系统通常由很多模块组成, 结构化程序设计中的 函数子程序 都可称为 模块 , 它是 程序语句逻辑关系 建立起来的 组合体

  • 模块矩形框 表示,并 用模块的名字标记 它。
  • 模块的名字 应当能够表明该模块的 功能
  • 对于 现成的模块 ,则以 双纵边矩形框 表示

module-representation.png

Figure 5: 模块的表示

对于 大的模块 ,一般还可以 继续分解 或划分为 功能独立较小的模块 。 我们称 不能再分解的模块原子模块

  • 如果一个软件系统:
    • 全部 实际加工 (即 数据计算处理 )都由 原子模块 来完成
    • 其他所有 非原子模块 仅仅执行 控制协调 功能

这样的系统就是 完全因子分解 的系统,完全因子分解的系统被认为是 最好的系统 。 但实际上,这只是我们力图达到的目标,大多数系统做不到完全因子分解。

一般地,模块可以按照 在软件系统中的功能 划分为4种类型。

module-categories.png

Figure 6: 模块的分类

  • 传入模块 取得数据或输入数据 ,经过某些处理,再将其 传送给其他模块 。 传送的数据流A叫做 逻辑输入数据流
  • 传出模块 输出数据 ,在输出之前可能进行某些处理,数据可能被输出到系统的外部, 也可能会输出到其他模块做进一步的处理,但最终的目标是 输出到系统的外部 。 传送的数据流D叫做 逻辑输出数据流
  • 变换模块 也叫做加工模块,它从 上级调用模块取得数据 ,进行特定的处理, 转换成其他形式,再将加工结果 返回给调用模块 。 它加工的数据流叫做 变换数据流 ,如将B变换为C。
  • 协调模块 本身一般 不对数据进行加工 ,如数据X和Y, 主要功能是通过 调用协调管理 其他模块来完成 特定的功能 , 如结构化程序设计中的主程序。

2.3.2. 模块结构

模块结构最普通的形式就是 树状结构网状结构 ,如图所示。

module-structures.png

Figure 7: 模块的树状结构和网状结构

你认为哪种模块结构更优?


参与人数 0
树状结构 0
网状结构 0
, 树状结构 , 网状结构 , 0 , 0

两种模块结构比较

  • 树状结构 只有一个 顶层模块上层模块 调用 下层模块同一层模块之间不相互调用
  • 网状结构 由于 不存在上级模块和下属模块 的关系, 也就分不出层次来。任何两个模块都是平等的, 没有从属关系任意两个模块间都可以有调用关系

在软件模式设计时,建议采用 树状结构 , 但往往可能在最底层存在一些 公共模块 (大多数为 数据操作模块 ), 使得实际软件的模块结构不是严格意义上的树状结构,这属于正常情况。

不加限制的 网状结构 ,由于模块间相互关系的任意性, 使得整个结构 十分复杂 ,处理起来势必引起许多麻烦。 这与原来 划分模块以便于处理 的意图相矛盾。

所以在软件开发的实践中,人们 通常 采用树状结构,而不采用网状结构

2.3.3. 结构图

结构图(structure chart, SC) 是精确表达 模块结构 的图形表示工具。 它不仅严格地定义了各个 模块的名字功能接口 , 而且还集中地反映了 设计思想

(1)模块的 调用关系和接口

在结构图中,两个模块之间用 单向箭头 连接。 箭头从 调用模块 指向 被调用模块 ,表示:

  1. 调用模块 调用了 被调用模块
  2. 被调用模块 执行完成之后, 控制 又返回到 调用模块

有些结构图中模块间的调用关系将 箭头 简单地画为 连线 , 这时只要调用与被调用模块的 上下位置 保持就是允许的。

(2)模块间的 信息传递

当一个模块调用另一个模块时, 调用模块数据控制信息 传送给 被调用模块 , 以使被调用模块能够运行。 而 被调用模块 在执行过程中又把它产生的 数据控制信息 回送给 调用模块

为了表示在模块之间传递的 数据控制信息 , 在联结模块的箭头旁边给出 短箭头 , 并且用尾端带有 空心圆 的短箭头表示 数据信息 , 用尾端带有 实心圆 的短箭头表示 控制信息 。 通常在短箭头附近应注有 信息的名字

module-call-interface.png

Figure 8: 模块间的调用关系和接口表示

(3)两个辅助符号

当模块A 有条件地 调用另一个模块B时, 在模块A的箭头尾部标以一个 菱形 符号; 当一个模块A 反复地 调用模块C和模块D时, 在调用箭头尾部则标以一个 弧形 符号。

在结构图中,这种 条件调用 所依赖的 条件循环调用 所依赖的 循环控制条件 通常都 无需注明

condition-loop-call.png

Figure 9: 条件调用和循环调用的表示

(4)结构图的形态特征

在结构图中, 上级模块 调用 下级模块 ,它们之间存在 主从关系 , 即 自上而下主宰 关系, 自下而上从属 关系。 而同一层的模块之间并没有这种主从关系。

structure-chart-example.png

Figure 10: 结构图示例

2.4. 数据结构及表示

  • 数据结构 是数据的各个元素之间 逻辑关系 的一种表示。
  • 数据结构设计 应确定 数据组织存取方式相关程度 以及 信息 的不同 处理方法
  • 数据结构的 组织方法复杂程度 可以灵活多样, 但 典型的数据结构 种类是有限的, 它们是构成一些更 复杂结构基本构件块

general-data-structure.png

Figure 11: 典型的数据结构

3. 体系结构设计

3.1. 基于数据流方法的设计过程

基于数据流的设计方法 可以很方便地将 数据流图 中表示的 数据流 映射成 软件结构 , 其主要过程包括:

  1. 复查并精化数据流图 :对需求分析阶段得出的数据流图认真 复查 ,并在必要时进行 精化
  2. 确定数据流图中数据流的类型 :典型的数据流类型有 变换型数据流事务型数据流 , 数据流类型决定了映射的方法。
  3. 导出初始的软件结构图 。根据数据流类型, 应用 变换型映射方法事务型映射方法 得到 初始的软件结构图
  4. 逐级分解 :对软件结构图进行逐级分解,一般需要进行 一级分解二级分解 , 如果需要,也可以进行更多级的分解。
  5. 精化软件结构 :使用 设计度量启发式规则 对得到的软件结构 进一步精化
  6. 导出接口描述和全局数据结构 :对每一个模块,给出进出该模块的信息,即该模块的 接口描述 。 此外,还需要对所使用的 全局数据结构 给出描述。

design-process-based-on-data-flow.png

Figure 12: 基于数据流方法的设计过程

3.2. 典型的数据流类型和系统结构

  • 典型的数据流类型有 变换型数据流事务型数据流 , 数据流的类型不同,得到的系统结构也不同。
  • 通常,一个系统中的 所有数据流都可以认为是 变换 , 但是,当遇到 有明显事务特性的数据流时,建议采用 事务 型映射方法 进行设计。

3.2.1. 变换型数据流与变换型系统结构图

变换型数据处理 问题的 工作过程 大致分为3步

  • 取得数据 : 预处理工作
  • 变换数据 : 核心工作
  • 给出数据 : 后处理工作

transform-flow.png

Figure 13: 变换型数据流

变换型系统结构图 由以下3部分组成

  • 输入
  • 中心变换
  • 输出

transform-type-system-structure.png

Figure 14: 变换型系统结构图

3.2.2. 事务型数据流与事务型系统结构图

事务型数据处理 问题的 工作过程 如下:

  • 接受一项事务,
  • 根据事务处理的特点和性质,选择分派一个适当的处理单元,
  • 然后给出结果。

完成选择分派任务的部分叫做 事务处理中心 ,或 分派部件

transaction-flow.png

Figure 15: 事务型数据流

事务型数据流图所对应的系统结构图就是 事务型系统结构图

transaction-type-system-structure.png

Figure 16: 事务型系统结构图

事务型系统结构图可以有多种不同的形式。 例如,有 多层操作层没有操作层事务型系统结构图的简化形式 是把 分析作业调度 都归入 事务中心 模块。

transaction-type-system-structure-lite.png

Figure 17: 简化的事务型系统结构图

事务型系统结构图在 数据处理 中经常遇到, 但是更多的是 变换型事务型 系统结构图的 结合 。 例如,变换型系统结构中的某个变换模块本身又具有事务型的特点。

3.3. 变换型映射方法

通常,系统数据处理问题的 处理流程 总能表示为 变换型数据流图 , 进一步可采用 变换型映射方法 建立系统的 结构图 。 但也可能遇到明显的事务数据处理问题,这时可采用 事务型映射方法

设计人员应当根据 数据流图主要问题类型 , 选择一个 面向全局的,即涉及整个软件范围的 问题处理类型。 此外,在 局部范围 内是 变换型 还是 事务型 ,可 具体研究 ,区别对待。

  • 变换分析方法 由以下4步组成:
    • 重画 数据流图
    • 区分 有效(逻辑)输入有效(逻辑)输出中心变换 部分
    • 进行 一级分解 ,设计 上层模块
    • 进行 二级分解 ,设计 输入输出中心变换部分的中、下层模块

3.3.1. 重画数据流图

需求分析阶段 得到的数据流图侧重于描述 系统如何加工数据 , 而 重画数据流图 的出发点是描述 系统中的数据是如何流动的

  • 因此,重画数据流图应注意以下几个要点:
    1. 以需求分析阶段得到的数据流图为基础重画数据流图时, 可以从 物理输入物理输出 ,或者相反。 还可以从 顶层加工框 开始, 逐层向下
    2. 在图上 不要 出现 控制逻辑(如判断、循环) 。 用箭头表示的 是数据流,而不是控制流
    3. 不要 去关注 系统的开始和终止 。( 假定系统在不停地运行
    4. 省略 每一个加工框的简单 例外处理
    5. 当数据流 进入离开 一个加工框时,要仔细地 标记 它们, 不要重名
    6. 如有必要, 可以使用逻辑运算符 (逻辑与,异或)。
    7. 仔细 检查每层数据流的正确性

3.3.2. 在数据流图上区分系统的逻辑输入、逻辑输出和中心变换部分

在这一步,可以暂时不考虑数据流图的一些支流,例如错误处理等。 根据经验, 几股数据流汇集的地方 往往是系统的 中心变换 部分。

data-flow-input-processing-output.png

Figure 18: 数据流图中的输入、中心变换与输出部分

  • 物理输入 端到 逻辑输入 ,构成系统的 输入部分
    • 逻辑输入 是离 物理输入最远 的但 仍被看作系统输入数据流
  • 物理输出 端到 逻辑输出 ,构成系统的 输出部分
    • 逻辑输出 是离 物理输出最远 的但 仍被看作系统输出数据流
  • 夹在 输入部分输出部分 中间的部分是 中心变换部分

3.3.3. 进行一级分解,设计系统模块结构的顶层和第一层

自顶向下 设计的关键是找出系统 树形结构图的根顶层模块

  • 首先设计一个 主模块 ,并用程序的名字为它命名,然后将它画在与 中心变换 相对应的位置上。
    • 作为系统的 顶层 ,它的功能是 调用下一层模块,完成系统所要做的各项工作
  • 主模块设计好之后,下面的 模块结构 就可以按照 输入中心变换输出 等分支来处理。 模块结构的 第一层 可以这样来设计:
    • 为每个 逻辑输入 设计一个 输入模块 ,它的功能是 为主模块提供数据
    • 为每个 逻辑输出 设计一个 输出模块 ,它的功能是 将主模块提供的数据输出
    • 中心变换 设计一个 变换模块 ,它的功能是 将逻辑输入转换成逻辑输出

3.3.4. 进行二级分解,设计中、下层模块

这一步工作是 自顶向下逐步细化 , 为每一个 输入模块输出模块变换模块 设计它们的从属模块。

设计下层模块的顺序是 任意 的。但 一般 是先设计 输入模块 的下层模块。

structure-chart-from-transform-flow.png

Figure 19: 变换型数据流导出的结构图

3.4. 事务型映射方法

变换分析 一样, 事务分析 也是从分析 数据流图 开始, 自顶向下,逐步分解,建立系统的结构图。

structure-chart-from-transaction-flow.png

Figure 20: 事务型数据流导出的系统结构图

3.4.1. 事务分析方法的步骤

  1. 识别事务源 :利用 数据流图数据词典 , 从 问题定义需求分析 的结果中,找出各种 需要处理的事务
  2. 规定适当的事务型结构 : 在确定了该数据流图具有事务型特征之后,根据 模块划分理论 ,建立适当的 事务型结构
  3. 识别各种事务和它们定义的操作
  4. 注意利用公用模块
  5. 建立事务处理模块 :对 每一事务 ,或 联系密切的一组事务 ,建立一个 事务处理模块
  6. 对事务处理模块规定它们全部的下层操作模块
  7. 对操作模块规定它们的全部细节模块

3.4.2. 混合型问题的结构图

一般,一个大型的软件系统是 变换型结构事务型结构混合结构 。 所以,我们通常利用以 变换分析 为主, 事务分析 为辅的方式进行 软件结构设计

structure-chart-mixed-version.png

Figure 21: 一个典型的变换-事务混合型问题的结构图

3.5. 模块间的耦合与内聚

3.5.1. 耦合

耦合 是程序结构中 各个模块之间 相互 关联 的度量, 它取决于各个模块之间

  • 接口的 复杂程度
  • 调用 模块的 方式
  • 通过接口的 信息类型

从耦合的 机制 上进行分类,按照相对的 耦合松紧程度 的排列, Myers\(^*\) 给出的模块之间可能的 耦合方式 有7种类型, 给设计人员在设计 程序结构 时提供了一个 决策准则

\(*\) Stevens, Wayne P.; Myers, Glenford J.; Constantine, Larry LeRoy (June 1974). "Structured design". IBM Systems Journal. 13 (2): 115–139. https://doi.org/10.1147/sj.132.0115.

Table 1: 耦合的类型
耦合性
非直接耦合 数据耦合 标记耦合 控制耦合 外部耦合 公共耦合 内容耦合
模块独立性

开始时两个模块之间的耦合 不只是一种类型,而是多种类型的混合 。 这就要求设计人员按照Myers提出的方法进行 分析 , 逐步加以 改进 ,以 提高 模块的 独立性

(1)内容耦合(Content coupling)

如果发生下列情形,两个模块之间就发生了 内容耦合

  • 一个模块直接 访问 另一个模块的 内部数据
  • 一个模块不通过正常入口 转到 另一个 模块内部
  • 两个模块有一部分程序 代码重叠 (只可能出现在汇编语言中)
  • 一个模块有 多个入口

在内容耦合的情形下,被访问模块的 任何变更 , 或者用 不同的编译器 对它再编译,都会造成 程序出错

它一般出现在 汇编语言 程序中, 目前大多数高级程序设计语言已经设计成 不允许 出现 内容耦合 。 这种耦合是 模块独立性最弱 的。

(2)公共耦合(Common coupling)

若一组模块都访问同一个公共数据环境,则它们之间的耦合就称为 公共耦合 。 公共的数据环境可以是

  • 全局 数据结构
  • 共享 的通信区
  • 内存的 公共 覆盖区
  • ……

common-coupling-image.png

Figure 22: 公共耦合示意图

(3)外部耦合(External Coupling)

若一组模块都

  • 访问 同一全局简单变量不是同一全局数据结构
  • 而且 不是通过参数表传递该全局变量的信息

则称之为 外部耦合

(4)控制耦合(Control coupling)

如果一个模块 传递 给另一个模块的 参数 中包含了 控制信息 , 该控制信息用于控制接收模块中的 执行逻辑 , 则它们之间的耦合称为 控制耦合

这种耦合的实质是在 单一接口 上选择 多功能模块 中的 某项功能 。 因此,对被控制模块的 任何修改 ,都会 影响 控制模块。 另外,控制耦合也意味着控制模块 必须知道 被控制模块 内部 的一些 逻辑关系 , 这些都会 降低 模块的 独立性

control-coupling-example.png

Figure 23: 控制耦合举例

(5)标记耦合(Stamp Coupling)

如果一组模块通过 参数表 传递 记录信息 , 则称它们之间的耦合为 标记耦合 。 事实上,这组模块共享了这个记录, 它是某一数据结构的子结构,而不是简单变量。

这要求这些模块都 必须清楚 该记录的 结构 , 并 按结构要求 对此记录进行 操作 。 在设计中应尽量 避免 这种耦合,它使在数据结构上的操作 复杂化 了。

如果我们采取 信息隐蔽 的方法, 把在数据结构上的操作全部 集中 在一个模块中, 就可以 消除 这种耦合。

stamp-coupling-example.png

Figure 24: 标记耦合举例

(6)数据耦合(Data coupling)

两个模块之间仅通过参数表传递简单数据,则称这种耦合为 数据耦合 。 由于限制了 只通过参数表 传递简单数据, 所以按数据耦合开发的程序 界面简单安全可靠 。 数据耦合是 松散的耦合 ,模块之间的 独立性 比较 。 在软件程序结构中至少 必须有这类耦合

control-coupling-to-data-coupling.png

Figure 25: 改控制耦合为数据耦合举例

stamp-coupling-to-data-coupling.png

Figure 26: 改标记耦合为数据耦合举例

(7)非直接耦合

如果两个模块之间 没有直接关系 , 即它们之间的联系完全是通过 主模块控制调用 来实现的, 这就是 非直接耦合 。 这种耦合的模块 独立性 最强

耦合是不好的,设计模块时不应该让模块间产生耦合。 这种说法是否正确?


参与人数 0
正确 0
不正确 0
, 正确 , 不正确 , 0 , 0

In software engineering, coupling is

  • the degree of interdependence between software modules;
  • a measure of how closely connected two routines or modules are;
  • the strength of the relationships between modules.

Coupling (computer programming)

coupling-vs-cohesion.png

Figure 27: Coupling Vs. Cohesion

使用耦合的原则

对耦合的应用原则:

  • 不在于禁止耦合,而是 充分了解 各种耦合的 特点不足
  • 做到能 在需要时使用 它们并 预见可能产生的问题

耦合是影响软件复杂程度的一个重要因素。 应该采取下述设计原则:

  • 尽量使用 数据耦合
  • 少用 控制耦合
  • 限制 公共环境耦合 的范围
  • 完全不用 内容耦合

3.5.2. 内聚

内聚 是一个 模块内部各个元素 彼此结合的 紧密程度 的度量。 在理想情况下,一个内聚性高的模块应当 只做一件事情 。 一般模块的内聚性分为7种类型。

Table 2: 内聚的类型
内聚性
功能内聚 信息内聚 通信内聚 过程内聚 时间内聚 逻辑内聚 巧合内聚
模块独立性

在上面的关系中可以看到,

  • 位于 高端 的几种内聚类型 最好
  • 位于 中段 的几种内聚类型是 可以接受 的,
  • 位于 低端 的内聚类型很不好,一般 不能使用

因此,人们总是希望一个模块的内聚类型向 内聚程度高 的方向靠。 模块的内聚 在系统的 模块化设计 中是一个关键的因素。

(1)巧合内聚(Coincidental cohesion)

巧合内聚 又称为 偶然内聚 。 当模块内各部分之间 没有联系 ,或者即使有联系,这种联系也很 松散 , 则称这种模块为巧合内聚模块,它是 内聚程度最低 的模块。

例如,一些 没有任何联系 的语句可能在许多模块中重复多次, 程序员为了节省存储,把它们抽出来组成一个新的模块, 这个模块就是 巧合内聚模块

(2)逻辑内聚(Logical cohesion)

逻辑内聚 模块把几种相关的功能组合在一起,每次被调用时, 由 传送给模块的判定参数 来确定该模块应执行哪一种功能。

例如,根据输入的 控制信息 ,或从文件中读入一个记录, 或向文件中写一个记录。这种模块是 单入口多功能模块 , 例如 错误处理 模块,它接收 出错信号 , 对 不同类型 的错误打印出 不同的错误信息

(3)时间内聚(Temporal cohesion)

时间内聚 又称为 经典内聚 。 这种模块大多为 多功能模块 ,但模块的各个功能的执行与 时间 有关, 通常要求所有功能必须在 同一时间段 内执行。

例如初始化模块和终止模块。

window.onload = function() {
    var room = getParameterByName("room") || generateToken(32);
    var link = addParam(window.location.href, "room", room );
    document.getElementById("room").innerHTML = link;
    document.getElementById("room").href = link;
    var qrcode = new QRious({ element: document.getElementById('qrcode'), level: 'H', padding: 0, size: 500, value: link, backgroundAlpha: 0 });
    document.querySelector("#host").addEventListener('click', function(e) {
        e.preventDefault();
        RevealSeminar.open_or_join_room(document.getElementById('password').value);
    });
}

(4)过程内聚(Procedural cohesion)

如果一个模块内的处理是 相关的 ,而且必须以 特定次序 执行, 则称这个模块为 过程内聚 模块。 这类模块的 内聚程度时间内聚 模块的 内聚程度更强 一些。 另外,因为过程内聚模块 仅包括完整功能的一部分 , 所以它的 内聚程度仍然比较低 ,模块间的耦合程度还比较高。

(5)通信内聚(Communicational cohesion)

如果一个模块内各功能部分都使用了 相同的输入数据 , 或产生了 相同的输出数据 ,则称之为 通信内聚 模块。 通信内聚模块的 内聚程度过程内聚 模块的 内聚程度要高 , 因为在通信内聚模块中包括了许多 独立的功能 。 但是,由于模块中各功能部分使用了 相同的输入/输出缓冲区 , 因而 降低了整个系统的效率

(6)信息内聚(Informational cohesion)

信息内聚 模块完成 多个功能 ,各个功能都在 同一数据结构 上操作, 每一项功能有一个 唯一的入口点

例如对某个数据表的 增加修改删除查询 功能, 这个模块将 根据不同的要求 确定该执行哪一个功能。 由于这个模块的所有功能都是基于 同一个数据结构 (数据表), 因此,它是一个信息内聚的模块。

信息内聚模块可以看成是 多个功能内聚模块的组合 ,并且达到 信息的 隐蔽 。 即把某个 数据结构资源设备 隐蔽 在一个模块内, 不为别的模块所知晓。

(7)功能内聚(Functional cohesion)

一个模块中各个部分 都是完成某一具体功能必不可少的组成部分 , 或者说该模块中所有部分 都是为了完成一项具体功能协同工作紧密联系不可分割 的, 则称该模块为 功能内聚 模块。

功能内聚模块的 优点 是它们 容易修改和维护 , 因为它们的 功能是明确的 ,模块间的 耦合是简单的

功能内聚模块的 内聚程度最高 。 在把一个系统分解成模块的过程中, 应当 尽可能 使模块 达到功能内聚 这一级。

3.6. 软件模块结构的改进方法

3.6.1. 模块功能的完善化

一个完整的功能模块,不仅能够完成 指定的功能 , 而且还应当能够告诉使用者完成 任务的状态 ,以及 不能完成的原因 。 也就是说,一个完整的模块应当有以下几部分:

  1. 执行规定的功能 部分。
  2. 出错处理 部分。当模块不能完成规定的功能时,必须回送 出错标志 , 向它的调用者 报告 出现这种例外情况的 原因
  3. 如果需要 返回一系列数据 给它的调用者,在 完成数据加工或结束 时, 应当给它的调用者 返回 一个 结束标志

所有上述部分,都应当看作是一个模块的 有机组成部分不应分离到其他模块中去 ,否则将会增大模块间的耦合程度。

3.6.2. 消除重复功能,改善软件结构

在得出系统的初始结构图之后,应当 审查分析 这个结构图。 如果 发现几个模块的功能有相似之处 ,可以加以 改进

  • 完全相似 :在结构上完全相似,可能只是在数据类型上不一致。 此时可以采取 完全合并 的方法, 只需在 数据类型的描述 上和 变量定义 上加以改进就可以了。
  • 局部相似 :此时, 不可以 把两者 合并为一 , 因为这样在合并后的 模块内部 必须设置许多 查询开关 , 势必把模块降低到 逻辑内聚 一级。

similar-modules-merge-solutions.png

Figure 28: 相似模块的各种合并方案

3.6.3. 模块的作用范围应在控制范围之内

  • 模块的控制范围 包括它 本身 及其所有的 从属模块
  • 模块的作用范围 是指模块内一个 判定作用范围 , 凡是 受这个判定影响的所有模块 都属于这个判定的 作用范围

如果一个判定的 作用范围 包含在 这个判定所在模块的 控制范围 之内, 则这种结构是 简单 的。

relationship-between-action-control-scope.png

Figure 29: 模块的作用范围与控制范围的关系

  • 如果在设计过程中,发现 作用范围 不在 控制范围 内, 可采用如下办法 把作用范围移到控制范围之内
    1. 将判定所在模块合并到父模块中, 使判定处于较高层次
    2. 受判定影响的模块下移 到控制范围内。
    3. 判定上移 到层次中较高的位置。

3.6.4. 尽可能减少高扇出结构,随着深度增大扇入

  • 模块的扇出数 是指模块 调用子模块的个数 。 如果一个模块的扇出数 过大 ,就意味着该模块过分 复杂 , 需要协调和控制 过多 的下属模块。 一般说来,出现这种情况是由于 缺乏中间层次 。 所以应当适当 增加中间层次 的控制模块。
  • 一个 模块的扇入数 越大 ,则共享该模块的上级模块数目 越多 。 扇入大,是有好处的,但如果一个模块的扇入数太大, 例如超过8,而它又不是公用模块,说明该模块 可能具有多个功能 。 在这种情况下应当对它进一步分析并将其 功能分解

fan-in-fan-out-breakdown.png

Figure 30: 高扇入和高扇出的分解

3.6.5. 避免或减少使用病态连接

应限制使用如下3种病态连接:

  • 直接病态连接 :模块A 直接从模块B内部取出某些数据 ,或者把某些数据 直接送到模块B内部
  • 公共数据域病态连接 :模块A和模块B 通过公共数据域直接传送或接收数据 ,而不是通过它们的上级模块。
  • 通信模块连接 :模块A和模块B 通过通信模块 传送数据。

sick-connections.png

Figure 31: 限制使用的病态连接

3.6.6. 模块的大小要适中

模块的大小 ,可以用模块中所含 语句的数量的多少 来衡量。

通常规定其语句行数在 50~100 左右, 保持在 一页纸 之内,最多不超过 500行

3.6.7. 实例研究

针对第3章的 银行储蓄系统 ,开发软件的 结构图

第一步

对银行储蓄系统的 数据流图 进行 复查精化 , 得到如图所示的数据流图。

bank-system-dataflow.png

Figure 32: 银行储蓄系统的数据流图

第二步

确定数据流图具有 变换特性 还是 事务特性 。 通过对第一步得到的 数据流图 进行 分析 , 可以看到整个系统是对 存款取款 两种 不同的事务 进行处理, 因此具有 事务特性

第三步

确定 输入流输出流边界 ,如图所示。

bank-system-dataflow-boundary.png

Figure 33: 数据流的边界

第四步

完成 第一级分解 。 分解后的结构图如图所示。

bank-system-structure-chart-top-level.png

Figure 34: 第一级分解后的结构图

第五步

  • 完成 第二级分解 。 对第一级分解后的结构图中的 输入数据输出数据调度 模块进行分解, 得到未经精化的 输入结构输出结构事务结构

bank-system-structure-chart-parts.png

Figure 35: 第二级分解过程

  • 输入结构输出结构事务结构 3部分合在一起, 得到初始的软件结构。

bank-system-software-structure-draft.png

Figure 36: 初始的软件结构

第六步

对软件结构进行精化。

  • 由于调度模块下 只有两种事务 ,因此,可以将调度模块 合并 到上级模块中,如图所示。

bank-system-structure-chart-1.png

Figure 37: 将调度模块合并到上级模块后的软件结构

  • 检查密码 模块的作用范围不在其控制范围之内 (即 输入密码 模块不在 检查密码 模块的控制范围之内), 需对其进行调整,如图所示。

bank-system-structure-chart-2.png

Figure 38: 对输入密码模块进行调整后的软件结构

  • 提高模块的独立性,并对 输入事务 模块进行细化。 也可以将 检查密码 功能合并到其上级模块中。

bank-system-structure-chart-3.png

Figure 39: 对模块独立性进行调整后的软件结构

4. 接口设计

4.1. 接口设计概述

接口设计 的依据是 数据流图 中的 自动化系统边界

  • 接口设计主要包括3个方面:
    • 模块软件构件 间的接口设计
    • 软件 与其他 软硬件系统 之间的接口设计
    • 软件人(用户) 之间的交互设计

人机交互(用户)界面人机交互 的主要方式。

4.2. 人机交互界面

为了设计好人机交互界面,设计者需要

  • 了解 用户界面应具有的特性
  • 认真研究 使用软件的用户 ,包括 用户是什么人用户怎样学习与新的计算机系统进行交互用户需要完成哪些工作
  • ……

4.2.1. 用户界面应具备的特性

  • 可使用性
    • 使用简单
    • 界面一致
    • 拥有 Help帮助功能
    • 快速的 系统响应 和低的 系统成本
    • 具有 容错能力
    • ……
  • 灵活性 :考虑到用户的 特点能力知识水平 , 应当使用户接口 满足不同用户的要求
  • 可靠性 :用户界面的可靠性是指 无故障使用的间隔时间 。 用户界面应能保证用户 正确可靠 地使用系统, 保证 有关程序和数据的 安全性

4.2.2. 用户类型

  • 外行型 :以前 从未使用 过计算机系统的用户。
  • 初学型 :尽管对新的系统 不熟悉 ,但对计算机还有一些使用经验的用户。
  • 熟练型 :对一个系统有相当多的 经验 ,能够 熟练 操作的用户。
  • 专家型 :这一类用户了解系统的 内部构造 ,有关于系统工作机制的 专业知识 ,具有 维护和修改基本系统的能力 。 专家型 需要 为他们提供能够 修改扩充 系统能力的复杂界面。

4.2.3. 界面设计类型

user-interface-design-types.png

Figure 40: 界面设计的类型

在选用 界面形式 的时候,应当考虑每种类型的 优点限制 。 可以从以下几个方面来考察抉择:

  1. 使用的难易程度 :对于没有经验的用户,该界面使用的难度有多大。
  2. 学习的难易程度 :学习该界面的命令和功能的难度有多大。
  3. 操作速度 :在完成一个指定操作时,该界面在操作步骤、击键和反应时间等方面效率有多高。
  4. 复杂程度 :该界面提供了什么功能,能否用新的方式组合这些功能以增强界面的功能。
  5. 控制 :人机交互时,是由计算机还是由人发起和控制对话。
  6. 开发的难易程度 :该界面设计是否有难度,开发工作量有多大。

4.2.4. 设计详细的交互

  • 人机交互的设计有若干准则,包括以下内容:
    1. 一致性 :采用 一致的术语一致的步骤一致的活动
    2. 操作步骤少 :使 击键点击鼠标次数减到最少 , 甚至要 减少 做某些事所需的 下拉菜单的距离
    3. 不要哑播放 :每当用户要等待系统完成一个动作时, 要给出一些 反馈信息 ,说明工作正在进展及取得了多少进展。
    4. 提供Undo功能 :用户的操作错误很难免,对于基本的操作应提供 恢复功能 ,或至少是部分恢复。
    5. 减少人脑的记忆负担不应该要求 人们从一个窗口中 记住 某些信息,然后在另一个窗口中使用。
    6. 提高学习效率 :为高级特性 提供联机帮助 ,以便用户在需要时容易找到。

5. 数据设计

数据软件系统 中的重要组成部分, 在 设计阶段 必须对要存储的 数据 及其 结构 进行设计。

目前,大多数设计者都会采用成熟的 关系数据库管理系统(DBMS)存储管理 数据,由于关系数据库已经相当成熟, 如果应用开发中选择关系数据库, 在数据存储和管理方面可以省去很大的开发工作量。 虽然如此, 在某些情况下,选择文件保存方式仍有其优越性

5.1. 文件设计

以下几种情况适合于选择文件存储:

  • 数据量较大的非结构化数据 :多媒体信息
  • 数据量大、信息松散 的情况:历史记录、档案文件等
  • 非关系层次化数据 :系统配置文件
  • 对数据的存取速度要求极高 的情况
  • 临时存放的数据

文件设计的主要工作就是根据

  • 使用要求
  • 处理方式
  • 存储的信息量
  • 数据的活动性
  • 所能提供的设备条件
  • ……

  • 确定文件类别
  • 选择文件媒体
  • 决定文件组织方法
  • 设计文件记录格式
  • 估算文件的容量

一般要根据文件的特性,来确定文件的组织方式:

  1. 顺序文件 :这类文件分两种,一种是 连续文件 ,另一种是 串联文件
  2. 直接存取文件 :可根据记录关键字的值,通过计算直接得到 记录存放地址
  3. 索引顺序文件 :其基本数据记录按 顺序文件 组织, 记录排列顺序必须按关键字值 升序降序 安排, 且具有 索引部分 ,索引部分也按同一关键字进行索引。
  4. 分区文件 :这类文件主要用于存放程序。 它由若干称为成员的顺序组织的 记录组索引 组成。 每一个成员就是一个程序,由于各个程序的长度不同,所以各个成员的大小也不同, 需要利用索引给出各个成员的程序名、开始存放位置和长度。
  5. 虚拟存储文件 :这是基于操作系统的请求页式存储管理功能而建立的 索引顺序文件
  6. 此外,还有适合于侯选属性查找的 倒排文件 等等。

5.2. 数据库设计

  • 根据数据库的组织,可以将数据库分为
    • 网状数据库
    • 层次数据库
    • 关系数据库
    • 面向对象数据库
    • 文档数据库
    • 多维数据库
    • ……

关系数据库 最成熟,应用也最广泛,一般情况下,大多数设计者都会选择关系数据库。 在结构化设计方法中,很容易将结构化分析阶段建立的 实体—关系 模型 映射 到关系数据库中。

5.2.1. 数据对象(实体)的映射

  • 一个数据对象(实体)可以映射为一个表或多个表, 当分解为多个表时,可以采用 横切竖切 的方法。
    • 竖切 常用于 实例较少属性很多 的对象。 通常将经常使用的属性放在主表中,而将其他一些次要的属性放到其他表中。
    • 横切 常常用于记录与 时间相关 的对象。 往往在主表中只记录最近的对象,而将以前的记录转到对应的历史表中。

5.2.2. 关系的映射

  • 一对一关系的映射 : 可以在两个表中都引入 外键 ,进行 双向导航 。 也可以将两个数据对象组合成一张单独的表。
  • 一对多关系的映射 : 可以将关联中的 端毫无变化地映射到 一张表 , 将关联中的 端上的数据对象映射到带有 外键另一张表 , 使外键满足 关系引用 的完整性。
  • 多对多关系的映射 : 为了表示多对多关系,关系模型必须引入一个 关联表 , 将两个数据实体之间的 多对多 关系转换成两个 一对多 关系。

6. 过程设计

概要设计 的任务完成后,就进入 详细设计 阶段,也就是 过程设计 阶段。 在这个阶段,要 决定各个模块的实现算法 , 并使用 过程描述工具 精确地 描述这些算法

表达 过程规格说明 的工具称为 过程描述工具 ,可以将过程描述工具分为以下3类。

  • 图形工具 : 把 过程的细节图形方式 描述出来,如
    • 程序流程图
    • N-S图
    • PAD
    • 决策树
    • ……
  • 表格工具 : 用一张表来表达过程的细节。 这张表列出了各种 可能的操作 及其 相应的条件 , 即描述了 输入处理输出 信息,如 决策表
  • 语言工具 :用某种 类高级语言(也称为伪代码) 来描述过程的细节, 如很多数据结构教材中使用 类Pascal类C语言 来描述算法。

6.1. 结构化程序设计

由于 软件开发维护 中存在的一系列严重问题, 导致 20世纪60年代 爆发了 软件危机 。 很多人将软件危机的 一个原因 归咎于 GOTO语句的滥用 , 由此引发了关于GOTO语句的争论。

1965年, E. W. Dijkstra 在一次会议上提出,应当将GOTO语句从高级语言中取消。

1966年, BohmJacopini 证明了 任何 单入口 单出口 的没有 死循环 的程序 都能由3种 最基本的控制结构 构造出来:

  • 顺序结构
  • 选择结构
  • 循环结构

20世纪70年代, E. W. Dijkstra 提出了程序要实现结构化的主张, 并将这一类程序设计称为 结构化程序设计(structured programming)

6.1.1. 结构程序设计的主要原则

  1. 使用语言中的 顺序选择重复 等有限的 基本控制结构 表示程序逻辑。
  2. 选用的控制结构只准许有 一个入口一个出口
  3. 程序语句组成容易识别的块(Block),每块只有 一个入口一个出口
  4. 复杂结构应该用 基本控制结构 进行 组合嵌套 来实现。
  5. 语言中 没有的控制结构 ,可用一段 等价的程序段模拟 , 但要求该程序段在整个系统中应前后一致。
  6. 严格控制GOTO语句,仅在下列情形才可使用:
    • 非结构化的程序设计语言 去实现 结构化的构造
    • 不使用GOTO语句 就会使程序 功能模糊
    • 在某种可以 改善不是损害 程序 可读性 的情况下。 例如,在 查找结束 时, 文件访问结束 时, 出现错误情况要从循环中转出 时, 使用 布尔变量条件结构 来实现就 不如用GOTO语句来得简洁易懂
  7. 在程序设计过程中, 尽量采用 自顶向下(Top-Down)逐步细化(stepwise refinement) 的原则, 由粗到细,一步步展开。

6.2. 程序流程图

程序流程图(program flowchart) 也称为 程序框图 , 是软件开发者最熟悉的 算法表达工具 。 早期的流程图也存在一些缺点。特别是 表示程序控制流程的箭头 , 使用的 灵活性极大 ,程序员可以不受任何约束,随意 转移控制不符合结构化程序设计的思想

  • 为使用流程图描述结构化程序,必须限制流程图只能使用以下5种基本控制结构:
    • 顺序型 :几个连续的加工步骤依次排列构成。
    • 选择型 :由某个逻辑判断式的取值决定选择两个加工中的一个。
    • 先判定(while)型循环 :在循环控制条件成立时,重复执行特定的加工。
    • 后判定(until)型循环 :重复执行某些特定的加工,直至控制条件成立。
    • 多情况(case)型选择 :列举多种加工情况,根据控制变量的取值,选择执行其一。

6.2.1. 流程图的基本控制结构

process-flow-diagram-control-structures.png

Figure 41: 流程图的基本控制结构

6.2.2. 嵌套构成的流程图实例

process-flow-diagram-example.png

Figure 42: 嵌套构成的流程图实例

6.2.3. 标准程序流程图的规定符号

process-flow-diagram-symbols.png

Figure 43: 标准程序流程图的规定符号

6.2.4. 循环的标准符号

process-flow-diagram-loop-symbols.png

Figure 44: 循环的标准符号

6.2.5. 注解符的使用

process-flow-diagram-annotation-symbols.png

Figure 45: 注解符的使用

6.2.6. 多选择判断

process-flow-diagram-switch-structure.png

Figure 46: 多选择判断

6.3. N-S图

NassiShneiderman 提出了一种符合结构化程序设计原则的 图形描述工具 , 叫做 盒图(box-diagram) ,也叫做 N-S图 。 在N-S图中,为了表示5种基本控制结构,规定了5种 图形构件

6.3.1. N-S图的5种基本控制结构

ns-diagram-control-structures.png

Figure 47: N-S图的5种基本控制结构

6.3.2. N-S图的实例

ns-diagram-example.png

Figure 48: N-S图的实例

6.3.3. N-S图的特点

  • 图中每个矩形框(除case构造中表示条件取值的矩形框外) 都是明确定义了的 功能域(即一个特定控制结构的作用域) ,以图形表示,清晰可见。
  • 它的 控制转移不能任意规定 ,必须遵守结构化程序设计的要求。
  • 很容易确定 局部数据 和(或) 全局数据作用域
  • 很容易表现 嵌套关系 ,也可以表示模块的 层次结构

6.3.4. N-S图的扩展表示

ns-diagram-extended-representation.png

Figure 49: N-S图的扩展表示

6.4. PAD

问题分析图(Problem Analysis Diagram, PAD)日本日立公司 提出的、 由 程序流程图 演化来的用 结构化程序设计 思想表现程序逻辑结构的 图形工具

PAD也设置了5种基本控制结构的图式,并允许递归使用。

6.4.1. PAD的基本控制结构

pad-diagram-control-structures.png

Figure 50: PAD的基本控制结构

6.4.2. PAD实例

pad-diagram-example.png

Figure 51: PAD实例

6.4.3. PAD的扩充控制结构

pad-diagram-extended-control-structures.png

Figure 52: PAD的扩充控制结构

6.4.4. PAD的优点

  • 使用PAD符号所设计出来的程序必然是 结构化 程序。
  • PAD描绘程序 结构清晰 ,图中竖线的总条数就是程序的层次数。
  • 用PAD表现程序逻辑 易读易懂易记
  • 容易将PAD 自动转换 为高级语言程序。
  • PAD既可以表示 程序逻辑 ,也可用于描绘 数据结构
  • PAD的符号支持 自顶向下逐步求精 方法的使用。

6.5. PDL与伪代码

程序设计语言(Program Design Language, PDL) 是一种 设计描述 软件 方法过程 的手段。 它与伪代码有关,但与伪代码不同的是, 它采用 简单语言 编写,且不暗示使用任何编程语言或库的术语。 https://en.wikipedia.org/wiki/Program_Design_Language

伪代码(Pseudocode) 是一种介于 自然语言形式化语言 之间的 半形式化语言 , 是一种用于描述 功能模块的算法设计加工细节 的语言。 https://en.wikipedia.org/wiki/Pseudocode

  • 伪代码的语法规则分为 外语法(outer syntax)内语法(inter syntax)
    • 外语法 应当 符合 一般程序设计语言常用语句的 语法规则
    • 内语法 可以用英语中一些 简单的句子短语通用的数学符号描述程序应执行的功能

6.5.1. 伪代码的基本控制结构

  • 简单陈述句结构 :避免复合语句。
  • 判定结构if-then-elsecase-of 结构。
  • 重复结构while-dorepeat-util 结构。

6.5.2. 伪代码的实例

商店业务处理系统中 检查订货单 的例子:

IF 客户订货金额超过5000元 THEN
  IF 客户拖延未还赊欠钱款超过60天 THEN
    在偿还欠款前不予批准
  ELSE (拖延未还赊欠钱款不超过60天)
    发批准书、发货单
  ENDIF
ELSE (客户订货金额未超过5000元)
  IF 客户拖延未还赊欠钱款超过60天 THEN
    发批准书、发货单,并发催款通知书
  ELSE (拖延未还赊欠钱款不超过60天)
    发批准书、发货单
  ENDIF
ENDIF

6.5.3. 伪代码的特点

  1. 有固定的关键字外语法 ,提供全部结构化 控制结构数据说明模块特征 。 外语法的关键字是有限的词汇集,它们能对PDL的正文进行结构分割,使之变得易于理解。
  2. 内语法使用自然语言来描述处理特性 ,为开发者提供方便,提高 可读性
  3. 有数据说明机制 ,包括简单的(如标量和数组)与复杂的(如链表和层次结构)的 数据结构
  4. 有子程序定义与调用机制 ,用以表达各种方式的 接口说明

你更愿意或倾向使用哪种工具?为什么?

参与人数 0
程序流程图 0
N-S图 0
PAD 0
PDL或伪代码 0
, 程序流程图 , N-S图 , PAD , PDL或伪代码 , 0 , 0 , 0 , 0

6.6. 自顶向下、逐步细化的设计过程

自顶向下、逐步细化 的设计过程主要包括两个方面:

  • 一是将 复杂问题的解法 分解细化 成由若干个模块组成的 层次结构
  • 二是将 每个模块的功能 逐步 分解细化一系列的处理

在处理较大的 复杂任务 时,常采取 模块化 的方法, 即在程序设计时不是将全部内容都放在同一个模块中, 而是分成 若干个模块 ,每个模块实现一个功能。 模块分解完成后,下一步的任务就是将每个模块的功能逐步分解细化为 一系列的处理

  • 概要设计 阶段,我们已经采用 自顶向下、逐步细化 的方法, 把 复杂问题的解法 分解细化 成了由许多功能模块组成的 层次结构 的软件系统。
  • 详细设计编码 阶段,我们还应当采取 自顶向下、逐步求精 的方法, 把 模块的功能 逐步 分解细化一系列具体的步骤 , 进而翻译成一系列用某种程序设计语言写成的程序。

6.6.1. 自顶向下、逐步细化方法举例

要求用筛选法求100以内的素数。 所谓的筛选法,就是从2到100中去掉2、3、5、7的倍数, 剩下的就是100以内的素数。

为了解决这个问题,可以按程序功能写出以下框架:

void main() {
  // 建立2到100的数组A[],其中A[i]=i #1
  // 建立2到10的素数表B[],存放2到10以内的素数 #2
  // 若A[i]=i是B[]中任一数的倍数,则剔除A[i] #3
  // 输出A[]中所有没有被剔除的数 #4
}

写出的框架中每个加工语句都可进一步细化成循环语句:

void main() {
  // 建立2到100的数组A[],其中A[i]=i #1
  for (i = 2; i <= 100; i++) A[i] = i;
  // 建立2到10的素数表B[],存放2到10以内的素数 #2
  B[1] = 2; B[2] = 3; B[3] = 5; B[4] = 7;
  // 若A[i]=i是B[]中任一数的倍数,则剔除A[i] #3
  for (j = 1; j <= 4; j++)
    // 检查A[]所有的数能否被B[j]整除并将能被整除的数从A[]中剔除 #3.1
    // 输出A[]中所有没有被剔除的数 #4
    for (i = 2; i <= 100; i++)
      // 若A[i]没有被剔除,则输出之 #4.1
      ;
}

继续对语句 #3.1 和语句 #4.1 细化下去, 直到最后每个语句都能直接用程序设计语言来表示为止:

void main() {
  // 建立2到100的数组A[],其中A[i]=i #1
  for (i = 2; i <= 100; i++) A[i] = i;
  // 建立2到10的素数表B[],存放2到10以内的素数 #2
  B[1] = 2; B[2] = 3; B[3] = 5; B[4] = 7;
  // 若A[i]=i是B[]中任一数的倍数,则剔除A[i] #3
  for (j = 1; j <= 4; j++)
    // 检查A[]所有的数能否被B[j]整除并将能被整除的数从A[]中剔除 #3.1
    for (i = 2; i <= 100; i++)
      if (A[i] / B[j] * B[j] == A[i])
        A[i] = 0;
  // 输出A[]中所有没有被剔除的数 #4
  for (i = 2; i <= 100; i++)
    // 若A[i]没有被剔除,则输出之 #4.1
    if (A[i] != 0)
      printf("A[%d] = %d\n", i, A[i]);
}

在后续的编码阶段用C语言来实现:

void main() {
  // 建立2到100的数组A[],其中A[i]=i #1
  int A[101], B[5];
  for (int i = 2; i <= 100; i++) A[i] = i;
  // 建立2到10的素数表B[],存放2到10以内的素数 #2
  B[1] = 2; B[2] = 3; B[3] = 5; B[4] = 7;
  // 若A[i]=i是B[]中任一数的倍数,则剔除A[i] #3
  for (int j = 1; j <= 4; j++)
    // 检查A[]所有的数能否被B[j]整除并将能被整除的数从A[]中剔除 #3.1
    for (int i = 2; i <= 100; i++)
      if (A[i] / B[j] * B[j] == A[i])
        A[i] = 0;
  // 输出A[]中所有没有被剔除的数 #4
  // 向A[]中补充10以内的素数 #4.1
  A[2] = 2; A[3] = 3; A[5] = 5; A[7] = 7;
  for (int i = 2; i <= 100; i++)
    // 若A[i]没有被剔除,则输出之 #4.2
    if (A[i] != 0)
      printf("%d ", A[i]);
}

6.6.2. 自顶向下、逐步求精的方法的优点

  1. 自顶向下、逐步求精方法 符合 人们解决复杂问题的 普遍规律 。 可提高 软件开发的成功率生产率
  2. 先全局后局部先整体后细节先抽象后具体 的逐步求精的过程 开发出来的程序具有 清晰的层次结构 ,因此程序 容易阅读和理解
  3. 程序 自顶向下、逐步细化 ,分解成 树形结构 。 在同一层的结点上做细化工作,相互之间没有关系,因此它们之间的 细化工作相互独立 。 在任何一步发生错误,一般 只影响它下层的结点同一层其他结点不受影响
  4. 程序清晰和模块化 ,使得在修改和重新设计一个软件时, 可复用 的代码量最大。
  5. 程序的逻辑结构清晰 ,有利于 程序正确性 证明。
  6. 每一步工作仅在上层结点的基础上做不多的设计扩展, 便于检查
  7. 有利于 设计的 分工组织 工作。

7. 软件设计规格说明

GB/T 8567-2006 《计算机软件文档编制规范》中有关软件设计的文档有3种,即:

  • 《软件设计说明(SDD)》
  • 《数据库设计说明(DBDD)》
  • 《接口设计说明(IDD)》

这几个文档 互相补充 ,向用户提供了可视的 设计方案 , 并 为软件开发和维护提供了所需的信息

国家标准全文公开系统 - GB/T 8567-2006 计算机软件文档编制规范

7.1. 软件(结构)设计说明(SDD)

软件设计说明 描述了软件系统的设计方案,包括:

  • 系统级的设计决策
  • 体系结构设计(概要设计)
  • 实现该软件系统所需的详细设计

7.2. 数据库(顶层)设计说明(DBDD)

数据库设计说明 描述了:

  • 数据库设计
  • 存取与操纵数据库的软件系统

7.3. 接口设计说明(IDD)

接口设计说明 描述了:

  • 系统
  • 硬件
  • 软件
  • 人工操作
  • 其他系统部件的接口特性

接口设计说明(IDD)接口需求规格说明(IRS) 配合,用于沟通和控制接口的设计决策。

8. 软件设计评审

所有模块的设计文档完成以后 ,就可以对 软件设计 进行 评审

  • 软件需求 是否得到满足
    • 确认该设计 是否覆盖 了所有已确定的软件需求
    • 软件设计成果的每一成分是否可追踪到某一项需求,即满足需求的 可追踪性
  • 软件结构 是否达到要求
    • 质量
    • 接口 说明
    • 数据结构 说明
    • 实现测试可行性
    • 可维护性
    • ……

8.1. 概要设计评审的检查内容

概要设计评审的检查内容如下:

Table 3: 概要设计评审的检查内容
1. 系统概述 6. 属性设计 11. 清晰性
2. 系统描述和可追踪性 7. 数据结构 12. 一致性
3. 是否对需求分析中不完整、易变动、潜在的需求进行了相应的设计分析 8. 运行设计 13. 可行性
4. 总体设计 9. 出错处理 14. 详细程度
5. 接口设计 10. 运行环境 15. 可维护性

8.2. 详细设计评审的检查内容

详细设计评审的检查内容如下:

Table 4: 详细设计评审的检查内容
1. 清晰性 6. 数据 11. 性能
2. 完整性 7. 功能性 12. 可靠性
3. 规范性 8. 接口 13. 可测试性
4. 一致性 9. 详细程度 14. 可追踪性
5. 正确性 10. 可维护性  

9. 课后作业

  1. (习题4.5) 使用数据流图和处理叙述, 描述一个具有明显事物流特性的计算机系统 。 使用本章所介绍的技术定义数据流的边界,并将DFD映射成软件结构。
  2. (习题4.7) 用面向数据流的方法设计第3章习题3.6所描述的图书管理系统的软件结构 , 并尽量使用改进方法对模块结构进行精化。
  3. (习题4.9) 将大的软件划分成模块有什么好处?是不是模块化分得越小越好? 划分模块的依据是什么?
  4. (习题4.10) 什么叫 自顶向下、逐步细化