(第二部分:结构化分析与设计方法,第4章)
Created: 2022-06-07 Tue 19:55
第二部分:结构化分析与设计方法
第4章:结构化设计方法
设计 是一项核心的工程活动。
在20世纪90年代早期, Lotus 1-2-3
的发明人 Mitch Kapor
在 Dr. Dobbs 杂志上发表了 软件设计宣言 ,其中指出:
什么是 设计 ?设计是你站在两个世界 — 技术世界 和人类的 目标世界 , 而你尝试将这两个世界 结合 在一起……
罗马建筑批评家 Vitruvius 提出了这样一个观念:
设计良好的建筑应该展示出 坚固 、 适用 和令人 赏心悦目 。
尽管模块分解可以简化要解决的问题,但模块分解 并不是越小越好 。
Figure 1: 模块大小、模块数目与成本的关系
模块的独立性 是指软件系统中 每个模块 只 涉及软件要求的具体的 子功能 , 而与软件系统中 其他模块 的 接口 是 简单 的。
例如,若一个模块只具有 单一的功能 且 与其他模块没有太多的联系 , 那么,我们则称此模块具有 模块独立性 。
模块独立性比较强的模块应是怎样的模块?
参与人数 | 0 |
---|---|
高度内聚、松散耦合的模块 | 0 |
低度内聚、紧密耦合的模块 | 0 |
抽象 是指 忽视 一个主题中与 当前目标 无关的方面 , 以便更充分地 注意 与 当前目标 有关的方面 。 当我们进行软件设计时,设计开始时应尽量提高软件的抽象层次, 按抽象级别从高到低进行软件设计。
将软件的体系结构按自顶向下方式, 对各个层次的 过程细节 和 数据细节 逐层细化, 直到用程序设计语言的语句能够实现为止, 从而最后确立整个系统的体系结构。 这也是我们常说的 自顶向下、逐步细化 的设计过程。
过程抽象 和 数据抽象 是两种常用的抽象手段。
复用 是指同一事物 不做修改 或 稍加修改 就可以 多次重复使用 。 将复用的思想用于软件开发,称为 软件复用 。 我们将 软件的重用部分 称为 软构件 。
也就是说,在构造新的软件系统时不必从零做起, 可以直接使用 已有的软构件 即可 组装 (或加以合理 修改 )成新的系统。
保证软件 灵活性设计 的关键是 抽象 。
面向对象系统中的 类结构 类似一座金字塔,越接近金字塔的 顶端 , 抽象程度 就 越高 。
抽象 的反义词是 具体 。 理想情况下,一个系统的任何 代码 、 逻缉 、 概念 在这个系统中都应该是 唯一 的, 也就是说 不存在重复的代码 。
结构化软件设计 的主要任务是要解决 如何做 的问题, 要在 需求分析 的基础上建立各种 设计模型 , 并通过对设计模型的 分析 和 评估 ,来确定这些模型 是否能够满足需求 。
软件设计 是将 用户需求 准确地转化成为最终的 软件产品 的 唯一 途径, 在 需求 到 构造 之间起到了 桥梁 作用。
在软件设计阶段,往往存在 多种设计方案 , 通常需要在多种设计方案之中进行 决策 和 折中 , 并使用选定的方案进行后续的开发活动。
从 管理 和 技术 两个不同的角度对 设计阶段 及 设计内容 的认识,可以用下图表示。
Figure 2: 设计阶段及设计内容
结构化分析 的 结果 为 结构化设计 提供了最基本的输入信息, 两者的关系如图所示。
Figure 3: 结构化设计与结构化分析的关系
一般通过 功能划分 过程来完成 软件结构设计 。
功能划分过程从 需求分析 确立的 目标系统的模型 出发,对整个问题进行分割, 每一部分用 一个 或 几个软件模块 加以解决,这样整个问题就解决了。
这个过程可以形象地用右图来表示, 该图表明了从 软件需求分析 到 软件设计 的过渡。
Figure 4: 软件结构的形成
一个软件系统通常由很多模块组成, 结构化程序设计中的 函数 和 子程序 都可称为 模块 , 它是 程序语句 按 逻辑关系 建立起来的 组合体 。
Figure 5: 模块的表示
对于 大的模块 ,一般还可以 继续分解 或划分为 功能独立 的 较小的模块 。 我们称 不能再分解的模块 为 原子模块 。
这样的系统就是 完全因子分解 的系统,完全因子分解的系统被认为是 最好的系统 。 但实际上,这只是我们力图达到的目标,大多数系统做不到完全因子分解。
一般地,模块可以按照 在软件系统中的功能 划分为4种类型。
Figure 6: 模块的分类
模块结构最普通的形式就是 树状结构 和 网状结构 ,如图所示。
Figure 7: 模块的树状结构和网状结构
你认为哪种模块结构更优?
参与人数 | 0 |
---|---|
树状结构 | 0 |
网状结构 | 0 |
两种模块结构比较
在软件模式设计时,建议采用 树状结构 , 但往往可能在最底层存在一些 公共模块 (大多数为 数据操作模块 ), 使得实际软件的模块结构不是严格意义上的树状结构,这属于正常情况。
不加限制的 网状结构 ,由于模块间相互关系的任意性, 使得整个结构 十分复杂 ,处理起来势必引起许多麻烦。 这与原来 划分模块以便于处理 的意图相矛盾。
所以在软件开发的实践中,人们 通常 采用树状结构,而不采用网状结构 。
结构图(structure chart, SC) 是精确表达 模块结构 的图形表示工具。 它不仅严格地定义了各个 模块的名字 、 功能 和 接口 , 而且还集中地反映了 设计思想 。
(1)模块的 调用关系和接口
在结构图中,两个模块之间用 单向箭头 连接。 箭头从 调用模块 指向 被调用模块 ,表示:
有些结构图中模块间的调用关系将 箭头 简单地画为 连线 , 这时只要调用与被调用模块的 上下位置 保持就是允许的。
(2)模块间的 信息传递
当一个模块调用另一个模块时, 调用模块 把 数据 或 控制信息 传送给 被调用模块 , 以使被调用模块能够运行。 而 被调用模块 在执行过程中又把它产生的 数据 或 控制信息 回送给 调用模块 。
为了表示在模块之间传递的 数据 或 控制信息 , 在联结模块的箭头旁边给出 短箭头 , 并且用尾端带有 空心圆 的短箭头表示 数据信息 , 用尾端带有 实心圆 的短箭头表示 控制信息 。 通常在短箭头附近应注有 信息的名字 。
Figure 8: 模块间的调用关系和接口表示
(3)两个辅助符号
当模块A 有条件地 调用另一个模块B时, 在模块A的箭头尾部标以一个 菱形 符号; 当一个模块A 反复地 调用模块C和模块D时, 在调用箭头尾部则标以一个 弧形 符号。
在结构图中,这种 条件调用 所依赖的 条件 和 循环调用 所依赖的 循环控制条件 通常都 无需注明 。
Figure 9: 条件调用和循环调用的表示
(4)结构图的形态特征
在结构图中, 上级模块 调用 下级模块 ,它们之间存在 主从关系 , 即 自上而下 是 主宰 关系, 自下而上 是 从属 关系。 而同一层的模块之间并没有这种主从关系。
Figure 10: 结构图示例
Figure 11: 典型的数据结构
基于数据流的设计方法 可以很方便地将 数据流图 中表示的 数据流 映射成 软件结构 , 其主要过程包括:
Figure 12: 基于数据流方法的设计过程
变换型数据处理 问题的 工作过程 大致分为3步
Figure 13: 变换型数据流
变换型系统结构图 由以下3部分组成
Figure 14: 变换型系统结构图
事务型数据处理 问题的 工作过程 如下:
完成选择分派任务的部分叫做 事务处理中心 ,或 分派部件 。
Figure 15: 事务型数据流
事务型数据流图所对应的系统结构图就是 事务型系统结构图 。
Figure 16: 事务型系统结构图
事务型系统结构图可以有多种不同的形式。 例如,有 多层操作层 或 没有操作层 。 事务型系统结构图的简化形式 是把 分析作业 和 调度 都归入 事务中心 模块。
Figure 17: 简化的事务型系统结构图
事务型系统结构图在 数据处理 中经常遇到, 但是更多的是 变换型 与 事务型 系统结构图的 结合 。 例如,变换型系统结构中的某个变换模块本身又具有事务型的特点。
通常,系统数据处理问题的 处理流程 总能表示为 变换型数据流图 , 进一步可采用 变换型映射方法 建立系统的 结构图 。 但也可能遇到明显的事务数据处理问题,这时可采用 事务型映射方法 。
设计人员应当根据 数据流图 的 主要问题类型 , 选择一个 面向全局的,即涉及整个软件范围的 问题处理类型。 此外,在 局部范围 内是 变换型 还是 事务型 ,可 具体研究 ,区别对待。
在 需求分析阶段 得到的数据流图侧重于描述 系统如何加工数据 , 而 重画数据流图 的出发点是描述 系统中的数据是如何流动的 。
在这一步,可以暂时不考虑数据流图的一些支流,例如错误处理等。 根据经验, 几股数据流汇集的地方 往往是系统的 中心变换 部分。
Figure 18: 数据流图中的输入、中心变换与输出部分
自顶向下 设计的关键是找出系统 树形结构图的根 或 顶层模块 。
这一步工作是 自顶向下 、 逐步细化 , 为每一个 输入模块 、 输出模块 、 变换模块 设计它们的从属模块。
设计下层模块的顺序是 任意 的。但 一般 是先设计 输入模块 的下层模块。
Figure 19: 变换型数据流导出的结构图
与 变换分析 一样, 事务分析 也是从分析 数据流图 开始, 自顶向下,逐步分解,建立系统的结构图。
Figure 20: 事务型数据流导出的系统结构图
一般,一个大型的软件系统是 变换型结构 和 事务型结构 的 混合结构 。 所以,我们通常利用以 变换分析 为主, 事务分析 为辅的方式进行 软件结构设计 。
Figure 21: 一个典型的变换-事务混合型问题的结构图
耦合 是程序结构中 各个模块之间 相互 关联 的度量, 它取决于各个模块之间
从耦合的 机制 上进行分类,按照相对的 耦合松紧程度 的排列, 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.
低 | 耦合性 | 高 | ||||
非直接耦合 | 数据耦合 | 标记耦合 | 控制耦合 | 外部耦合 | 公共耦合 | 内容耦合 |
强 | 模块独立性 | 弱 |
开始时两个模块之间的耦合 不只是一种类型,而是多种类型的混合 。 这就要求设计人员按照Myers提出的方法进行 分析 , 逐步加以 改进 ,以 提高 模块的 独立性 。
(1)内容耦合(Content coupling)
如果发生下列情形,两个模块之间就发生了 内容耦合 :
在内容耦合的情形下,被访问模块的 任何变更 , 或者用 不同的编译器 对它再编译,都会造成 程序出错 。
它一般出现在 汇编语言 程序中, 目前大多数高级程序设计语言已经设计成 不允许 出现 内容耦合 。 这种耦合是 模块独立性最弱 的。
(2)公共耦合(Common coupling)
若一组模块都访问同一个公共数据环境,则它们之间的耦合就称为 公共耦合 。 公共的数据环境可以是
Figure 22: 公共耦合示意图
(3)外部耦合(External Coupling)
若一组模块都
则称之为 外部耦合 。
(4)控制耦合(Control coupling)
如果一个模块 传递 给另一个模块的 参数 中包含了 控制信息 , 该控制信息用于控制接收模块中的 执行逻辑 , 则它们之间的耦合称为 控制耦合 。
这种耦合的实质是在 单一接口 上选择 多功能模块 中的 某项功能 。 因此,对被控制模块的 任何修改 ,都会 影响 控制模块。 另外,控制耦合也意味着控制模块 必须知道 被控制模块 内部 的一些 逻辑关系 , 这些都会 降低 模块的 独立性 。
Figure 23: 控制耦合举例
(5)标记耦合(Stamp Coupling)
如果一组模块通过 参数表 传递 记录信息 , 则称它们之间的耦合为 标记耦合 。 事实上,这组模块共享了这个记录, 它是某一数据结构的子结构,而不是简单变量。
这要求这些模块都 必须清楚 该记录的 结构 , 并 按结构要求 对此记录进行 操作 。 在设计中应尽量 避免 这种耦合,它使在数据结构上的操作 复杂化 了。
如果我们采取 信息隐蔽 的方法, 把在数据结构上的操作全部 集中 在一个模块中, 就可以 消除 这种耦合。
Figure 24: 标记耦合举例
(6)数据耦合(Data coupling)
两个模块之间仅通过参数表传递简单数据,则称这种耦合为 数据耦合 。 由于限制了 只通过参数表 传递简单数据, 所以按数据耦合开发的程序 界面简单 、 安全可靠 。 数据耦合是 松散的耦合 ,模块之间的 独立性 比较 强 。 在软件程序结构中至少 必须有这类耦合 。
Figure 25: 改控制耦合为数据耦合举例
Figure 26: 改标记耦合为数据耦合举例
(7)非直接耦合
如果两个模块之间 没有直接关系 , 即它们之间的联系完全是通过 主模块 的 控制 和 调用 来实现的, 这就是 非直接耦合 。 这种耦合的模块 独立性 最强 。
耦合是不好的,设计模块时不应该让模块间产生耦合。 这种说法是否正确?
参与人数 | 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.
Figure 27: Coupling Vs. Cohesion
使用耦合的原则
对耦合的应用原则:
耦合是影响软件复杂程度的一个重要因素。 应该采取下述设计原则:
内聚 是一个 模块内部各个元素 彼此结合的 紧密程度 的度量。 在理想情况下,一个内聚性高的模块应当 只做一件事情 。 一般模块的内聚性分为7种类型。
高 | 内聚性 | 低 | ||||
功能内聚 | 信息内聚 | 通信内聚 | 过程内聚 | 时间内聚 | 逻辑内聚 | 巧合内聚 |
强 | 模块独立性 | 弱 |
在上面的关系中可以看到,
因此,人们总是希望一个模块的内聚类型向 内聚程度高 的方向靠。 模块的内聚 在系统的 模块化设计 中是一个关键的因素。
(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)
一个模块中各个部分 都是完成某一具体功能必不可少的组成部分 , 或者说该模块中所有部分 都是为了完成一项具体功能 而 协同工作 、 紧密联系 、 不可分割 的, 则称该模块为 功能内聚 模块。
功能内聚模块的 优点 是它们 容易修改和维护 , 因为它们的 功能是明确的 ,模块间的 耦合是简单的 。
功能内聚模块的 内聚程度最高 。 在把一个系统分解成模块的过程中, 应当 尽可能 使模块 达到功能内聚 这一级。
一个完整的功能模块,不仅能够完成 指定的功能 , 而且还应当能够告诉使用者完成 任务的状态 ,以及 不能完成的原因 。 也就是说,一个完整的模块应当有以下几部分:
所有上述部分,都应当看作是一个模块的 有机组成部分 , 不应分离到其他模块中去 ,否则将会增大模块间的耦合程度。
在得出系统的初始结构图之后,应当 审查分析 这个结构图。 如果 发现几个模块的功能有相似之处 ,可以加以 改进 。
Figure 28: 相似模块的各种合并方案
如果一个判定的 作用范围 包含在 这个判定所在模块的 控制范围 之内, 则这种结构是 简单 的。
Figure 29: 模块的作用范围与控制范围的关系
Figure 30: 高扇入和高扇出的分解
应限制使用如下3种病态连接:
Figure 31: 限制使用的病态连接
模块的大小 ,可以用模块中所含 语句的数量的多少 来衡量。
通常规定其语句行数在 50~100 左右, 保持在 一页纸 之内,最多不超过 500行 。
针对第3章的 银行储蓄系统 ,开发软件的 结构图 。
第一步
对银行储蓄系统的 数据流图 进行 复查 并 精化 , 得到如图所示的数据流图。
Figure 32: 银行储蓄系统的数据流图
第二步
确定数据流图具有 变换特性 还是 事务特性 。 通过对第一步得到的 数据流图 进行 分析 , 可以看到整个系统是对 存款 及 取款 两种 不同的事务 进行处理, 因此具有 事务特性 。
第三步
确定 输入流 和 输出流 的 边界 ,如图所示。
Figure 33: 数据流的边界
第四步
完成 第一级分解 。 分解后的结构图如图所示。
Figure 34: 第一级分解后的结构图
第五步
Figure 35: 第二级分解过程
Figure 36: 初始的软件结构
第六步
对软件结构进行精化。
Figure 37: 将调度模块合并到上级模块后的软件结构
Figure 38: 对输入密码模块进行调整后的软件结构
Figure 39: 对模块独立性进行调整后的软件结构
接口设计 的依据是 数据流图 中的 自动化系统边界 。
人机交互(用户)界面 是 人机交互 的主要方式。
为了设计好人机交互界面,设计者需要
Figure 40: 界面设计的类型
在选用 界面形式 的时候,应当考虑每种类型的 优点 和 限制 。 可以从以下几个方面来考察抉择:
数据 是 软件系统 中的重要组成部分, 在 设计阶段 必须对要存储的 数据 及其 结构 进行设计。
目前,大多数设计者都会采用成熟的 关系数据库管理系统(DBMS) 来 存储 和 管理 数据,由于关系数据库已经相当成熟, 如果应用开发中选择关系数据库, 在数据存储和管理方面可以省去很大的开发工作量。 虽然如此, 在某些情况下,选择文件保存方式仍有其优越性 。
以下几种情况适合于选择文件存储:
文件设计的主要工作就是根据
来
一般要根据文件的特性,来确定文件的组织方式:
关系数据库 最成熟,应用也最广泛,一般情况下,大多数设计者都会选择关系数据库。 在结构化设计方法中,很容易将结构化分析阶段建立的 实体—关系 模型 映射 到关系数据库中。
概要设计 的任务完成后,就进入 详细设计 阶段,也就是 过程设计 阶段。 在这个阶段,要 决定各个模块的实现算法 , 并使用 过程描述工具 精确地 描述这些算法 。
表达 过程规格说明 的工具称为 过程描述工具 ,可以将过程描述工具分为以下3类。
由于 软件开发 和 维护 中存在的一系列严重问题, 导致 20世纪60年代 爆发了 软件危机 。 很多人将软件危机的 一个原因 归咎于 GOTO语句的滥用 , 由此引发了关于GOTO语句的争论。
1965年, E. W. Dijkstra 在一次会议上提出,应当将GOTO语句从高级语言中取消。
1966年, Bohm 和 Jacopini 证明了 任何 单入口 单出口 的没有 死循环 的程序 都能由3种 最基本的控制结构 构造出来:
- 顺序结构
- 选择结构
- 循环结构
20世纪70年代, E. W. Dijkstra 提出了程序要实现结构化的主张, 并将这一类程序设计称为 结构化程序设计(structured programming) 。
程序流程图(program flowchart) 也称为 程序框图 , 是软件开发者最熟悉的 算法表达工具 。 早期的流程图也存在一些缺点。特别是 表示程序控制流程的箭头 , 使用的 灵活性极大 ,程序员可以不受任何约束,随意 转移控制 , 不符合结构化程序设计的思想 。
Figure 41: 流程图的基本控制结构
Figure 42: 嵌套构成的流程图实例
Figure 43: 标准程序流程图的规定符号
Figure 44: 循环的标准符号
Figure 45: 注解符的使用
Figure 46: 多选择判断
Nassi 和 Shneiderman 提出了一种符合结构化程序设计原则的 图形描述工具 , 叫做 盒图(box-diagram) ,也叫做 N-S图 。 在N-S图中,为了表示5种基本控制结构,规定了5种 图形构件 。
Figure 47: N-S图的5种基本控制结构
Figure 48: N-S图的实例
Figure 49: N-S图的扩展表示
问题分析图(Problem Analysis Diagram, PAD) 是 日本日立公司 提出的、 由 程序流程图 演化来的用 结构化程序设计 思想表现程序逻辑结构的 图形工具 。
PAD也设置了5种基本控制结构的图式,并允许递归使用。
Figure 50: PAD的基本控制结构
Figure 51: PAD实例
Figure 52: PAD的扩充控制结构
程序设计语言(Program Design Language, PDL) 是一种 设计 和 描述 软件 方法 与 过程 的手段。 它与伪代码有关,但与伪代码不同的是, 它采用 简单语言 编写,且不暗示使用任何编程语言或库的术语。 https://en.wikipedia.org/wiki/Program_Design_Language
伪代码(Pseudocode) 是一种介于 自然语言 和 形式化语言 之间的 半形式化语言 , 是一种用于描述 功能模块的算法设计 和 加工细节 的语言。 https://en.wikipedia.org/wiki/Pseudocode
if-then-else
或 case-of
结构。while-do
或 repeat-util
结构。商店业务处理系统中 检查订货单 的例子:
IF 客户订货金额超过5000元 THEN
IF 客户拖延未还赊欠钱款超过60天 THEN
在偿还欠款前不予批准
ELSE (拖延未还赊欠钱款不超过60天)
发批准书、发货单
ENDIF
ELSE (客户订货金额未超过5000元)
IF 客户拖延未还赊欠钱款超过60天 THEN
发批准书、发货单,并发催款通知书
ELSE (拖延未还赊欠钱款不超过60天)
发批准书、发货单
ENDIF
ENDIF
你更愿意或倾向使用哪种工具?为什么?
参与人数 | 0 |
---|---|
程序流程图 | 0 |
N-S图 | 0 |
PAD | 0 |
PDL或伪代码 | 0 |
自顶向下、逐步细化 的设计过程主要包括两个方面:
在处理较大的 复杂任务 时,常采取 模块化 的方法, 即在程序设计时不是将全部内容都放在同一个模块中, 而是分成 若干个模块 ,每个模块实现一个功能。 模块分解完成后,下一步的任务就是将每个模块的功能逐步分解细化为 一系列的处理 。
要求用筛选法求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]);
}
GB/T 8567-2006 《计算机软件文档编制规范》中有关软件设计的文档有3种,即:
这几个文档 互相补充 ,向用户提供了可视的 设计方案 , 并 为软件开发和维护提供了所需的信息 。
软件设计说明 描述了软件系统的设计方案,包括:
数据库设计说明 描述了:
接口设计说明 描述了:
接口设计说明(IDD) 与 接口需求规格说明(IRS) 配合,用于沟通和控制接口的设计决策。
所有模块的设计文档完成以后 ,就可以对 软件设计 进行 评审 :
概要设计评审的检查内容如下:
1. 系统概述 | 6. 属性设计 | 11. 清晰性 |
2. 系统描述和可追踪性 | 7. 数据结构 | 12. 一致性 |
3. 是否对需求分析中不完整、易变动、潜在的需求进行了相应的设计分析 | 8. 运行设计 | 13. 可行性 |
4. 总体设计 | 9. 出错处理 | 14. 详细程度 |
5. 接口设计 | 10. 运行环境 | 15. 可维护性 |
详细设计评审的检查内容如下:
1. 清晰性 | 6. 数据 | 11. 性能 |
2. 完整性 | 7. 功能性 | 12. 可靠性 |
3. 规范性 | 8. 接口 | 13. 可测试性 |
4. 一致性 | 9. 详细程度 | 14. 可追踪性 |
5. 正确性 | 10. 可维护性 |