5.总体设计

5.1设计过程

设计阶段

  • 从工程管理的角度,可以将软件设计分为概要设计阶段和详细设计阶段。
  • 从技术的角度,传统的结构化方法将软件设计划分为体系结构设计、数据设计、接口设计和过程设计4部分。
  • 面向对象方法则将软件设计划分为体系结构设计、类设计/数据设计、接口设计和构件级设计4部分。

基本任务

  • (1)将软件系统划分成模块
  • (2)决定每个模块的功能
  • (3)决定模块的调用关系
  • (4)决定模块的界面,即模块间传递的数据

总体设计的过程

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

    • 设想供选择的方案
    • 选取合理的方案
    • 推荐最佳方案
  • 结构设计阶段,确定软件结构

    • 功能分解
    • 设计软件结构
    • 设计数据库

流程

  • 1.设想供选择的方案

    • 根据需求分析阶段得出的数据流图考虑各种可能的实现方案,力求从中选出最佳方案。即由逻辑模型——>物理模型
  • 2.选取合理的方案

    • 从前一步得到的一系列供选择的方案中选取若干个合理的方案。通常,选取的这些方案中至少应包括低成本、中成本和高成本的三种方案类型。对每个合理的方案分析员都应该准备下列4份资料:对每个合理方案要提供以下几方面资料:

      • (1)系统流程图;
      • (2)组成系统的物理元素清单 ;
      • (3)成本/效益分析;
      • (4)实现这个系统的进度计划。
  • 3.推荐最佳方案

    • 分析员从合理方案中选择一个最佳方案向用户推荐,并为推荐的方案制定详细的实现计划。对于分析员推荐的最佳方案,用户和有关专家应该认真审查。如果确认该方案确实符合用户的需要,并且在现有条件下完全能够实现,则应该提请使用部门负责人进一步审批。在使用部门负责人也接受了分析员所推荐的方案之后,方可进入总体设计过程的下一步工作,即结构设计阶段。
  • 4.功能分解

    • (1)结构设计 —— 总体设计阶段的任务

      • 结构设计确定程序由哪些模块组成,以及这些模块之间的关系;
    • (2)过程设计 —— 详细设计阶段的任务

      • 过程设计确定每个模块的处理过程。主要是按照用户的要求把复杂的功能从实现的角度进行分解,以方便后面软件结构的确定
  • 5.设计软件结构

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

      • 1)设计数据库的表,表的结构就是数据的存储结构
      • 2)对这些表中的数据进行操作。
    • 数据库设计的主要挑战是“高速度处理大容量的数据”

    • 数据库设计的步骤:

      • 逻辑设计
      • 物理设计
      • 安全性设计
      • 性能优化
  • 7.制定测试计划

    • 在软件开发的早期阶段提前考虑软件的测试计划是很有必要的。这样能促使软件设计人员在设计时注意到软件的测试问题,从而有利于提高软件的可测试性。
  • 8.书写文档

    • (1)总体设计说明书(包括系统实现方案和软件模块结构);
    • (2)测试计划(包括测试策略、测试方案、预测的测试结果、测试进度计划等);
    • (3)用户手册(根据总体设计阶段的结果,编写的初步的用户操作手册);
    • (4)详细的实现计划;
    • (5)数据库设计结果。
    1. 审查和复审
    • 最后应该对总体设计的结果进行严格的技术审查,在技术审查通过之后再由使用部门的负责人从管理角度进行复审。

5.2设计原理

模块

  • 定义:

    • 模块是数据说明、可执行语句等程序对象的集合,它是单独命名的,可通过名字来进行访问。
  • 特点:

    • 一个模块具有输入和输出、功能、内部数据和程序代码等四个特性。
    • 输入输出和功能构成一个模块的外貌,即模块的外部特性。
    • 内部数据和程序代码是模块的内部特性。
  • 模块化

    • 模块化就是把程序划分成若干个模块,每个模块完成一个子功能,把这些模块集成起来构成一个整体,可以完成指定的功能,满足用户的需求
    • 特点:先分解,然后再集成
    • 软件模块化设计的指导思想是分解、抽象、逐步求精、信息隐蔽和模块独立性。
  • 为什么要模块化?

    • 模块化是为了使一个复杂的大型程序能被人的智力所管理,软件应该具备的惟一属性。
    • 如果一个大型程序仅由一个模块组成,它将很难被人所理解。
  • 评价一种设计方法定义模块能力的五条标准:

    • 模块可分解性
    • 模块可组装性
    • 模块可理解性
    • 模块连续性
    • 模块保护性
  • 模块化的作用:

    • 采用模块化原理可以使软件结构清晰,不仅容易设计也容易阅读和理解。
    • 模块化使软件容易测试和调试,因而有助于提高软件的可靠性。
    • 模块化能够提高软件的可修改性。
    • 模块化也有助于软件开发工程的组织管理。

抽象

  • 定义

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

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

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

逐步求精

  • 介绍:

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

    • 一个人在任何时候都只能把注意力集中在(7±2)个知识块上。
  • 抽象与求精的关系

    • 抽象与求精是一对互补的概念。抽象使得设计者能够说明过程和数据,同时却忽略低层细节。求精则帮助设计者在设计过程中逐步揭示出低层细节。
  • 逐步求精的作用:

    • 它能帮助软件工程师把精力集中在与当前开发阶段最相关的那些方面上,而忽略那些对整体解决方案来说虽然是必要的,然而目前还不需要考虑的细节。
    • 逐步求精方法确保每个问题都将被解决,而且每个问题都将在适当的时候被解决,但是,在任何时候一个人都不需要同时处理7个以上知识块。

信息隐藏和局部化

  • 信息隐藏原理:

    • 应该这样设计和确定模块,使得一个模块内包含的信息(过程和数据)对于不需要这些信息的模块来说,是不能访问的。
  • 局部化:

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

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

模块独立

  • 定义:

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

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

    • 耦合衡量不同模块彼此间互相依赖(连接)的紧密程度。耦合要低,即每个模块和其他模块之间的关系要简单;
    • 内聚衡量一个模块内部各个元素彼此结合的紧密程度。内聚要高,每个模块完成一个相对独立的特定子功能。
    • 模块独立性愈高,则块内联系越强(内聚性强/高),块间联系越弱(耦合度弱/低)。
  • 耦合——块间联系

    • 耦合:是对一个软件结构内不同模块之间互连程度的度量。
    • 要求:在软件设计中应该追求尽可能松散耦合的系统。
    • 可以研究、测试或维护任何一个模块,而不需要对系统的其他模块有很多了解;
    • 模块间联系简单,发生在一处的错误传播到整个系统的可能性就很小;
    • 模块间的耦合程度强烈影响系统的可理解性、可测试性、可靠性和可维护性。
  • 耦合程度的度量:

    • (1) 非直接耦合/完全独立

      • 如果两个模块中的每一个都能独立地工作而不需要另一个模块的存在,那么它们完全独立。
      • 在一个软件系统中不可能所有模块之间都没有任何连接。
    • (2) 数据耦合

      • 如果两个模块彼此间通过参数交换信息,而且交换的信息仅仅是数据,那么这种耦合称为数据耦合。

      • 评价:

        • 系统中至少必须存在这种耦合。一般说来,一个系统内可以只包含数据耦合。
        • 数据耦合是理想的目标。
        • 维护更容易,对一个模块的修改不会是另一个模块产生退化错误。
    • (3) 控制耦合

      • 如果两个模块彼此间传递的信息中有控制信息,这种耦合称为控制耦合。

      • 评价:

        • 控制耦合往往是多余的,把模块适当分解之后通常可以用数据耦合代替它。
        • 被调用的模块需知道调用模块的内部结构和逻辑,降低了重用的可能性 。
    • (4) 特征耦合

      • 当把整个数据结构作为参数传递而被调用的模块只需要使用其中一部分数据元素时,就出现了特征耦合。

      • 评价:

        • 被调用的模块可使用的数据多于它确实需要的数据,这将导致对数据的访问失去控制,从而给计算机犯罪提供了机会。
        • 无论何时把指针作为参数进行传递,都应该仔细检查该耦合。
    • (5) 公共环境耦合

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

      • 公共环境耦合的类型:

        • 一个模块往公共环境送数据,另一个模块从公共环境取数据。数据耦合的一种形式,是比较松散的耦合。

        • 两个模块都既往公共环境送数据又从里面取数据,这种耦合比较紧密,介于数据耦合和控制耦合之间。

        • 评价:

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

      • 最高程度的耦合是内容耦合。如果出现下列情况之一,两个模块间就发生了内容耦合:

        • 一个模块访问另一个模块的内部数据;
        • 一个模块不通过正常入口转到另一个模块的内部;
        • 两个模块有一部分程序代码重叠;
        • 一个模块有多个入口。
    • 设计原则:

      • 尽量使用数据耦合,
      • 少用控制耦合和特征耦合,
      • 限制公共环境耦合的范围。
      • 完全不用内容耦合
  • 内聚——块内联系

    • 内聚:标志一个模块内各个元素彼此结合的紧密程度,它是信息隐藏和局部化概念的自然扩展。简单地说,理想内聚的模块只做一件事情。
    • 要求:设计时应该力求做到高内聚,通常中等程度的内聚也是可以采用的,而且效果和高内聚相差不多;但是,低内聚不要使用。
    • 内聚和耦合是密切相关的,模块内的高内聚往往意味着模块间的松耦合。实践表明内聚更重要,应该把更多注意力集中到提高模块的内聚程度上。
  • 内聚程度的度量:

    • (1) 偶然内聚

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

      • 评价:

        • 模块内各元素之间没有实质性联系,很可能在一种应用场合需要修改这个模块,在另一种应用场合又不允许这种修改,从而陷入困境;
        • 可理解性差,可维护性产生退化;
        • 模块是不可重用的。
      • 解决方案:

        • 将模块分成更小的模块,每个小模块执行一个操作。
    • (2) 逻辑内聚

      • 如果一个模块完成的任务在逻辑上属于相同或相似的一类,则称为逻辑内聚。

      • 评价:

        • 接口难以理解,造成整体上不易理解;
        • 完成多个操作的代码互相纠缠在一起,即使局部功能的修改有时也会影响全局,导致严重的维护问题;
        • 难以重用。
      • 解决方案:

        • 模块分解。
    • (3) 时间内聚

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

      • 评价:

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

      • 如果一个模块内的处理元素是相关的,而且必须以特定次序执行,则称为过程内聚。

      • 使用程序流程图作为工具设计软件时,常常通过研究流程图确定模块的划分,这样得到的往往是过程内聚的模块。

      • 评价:

        • 比时间内聚好,至少操作之间是过程关联的。
        • 仍是弱连接,不太可能重用模块。
      • 解决方案:

        • 分割为单独的模块,每个模块执行一个操作。
    • (5) 通信内聚

      • 如果模块中所有元素都使用同一个输入数据和(或)产生同一个输出数据,则称为通信内聚。即在同一个数据结构上操作。

      • 评价:

        • 模块中各操作紧密相连,比过程内聚更好。
        • 不能重用。
      • 解决方案:

        • 分成多个模块,每个模块执行一个操作。
    • (6) 顺序内聚

      • 如果一个模块内的处理元素和同一个功能密切相关,而且这些处理必须顺序执行,则称为顺序内聚。

      • 评价:

        • 根据数据流图划分模块时,通常得到顺序内聚的模块,这种模块彼此间的连接往往比较简单。
    • (7) 功能内聚

      • 如果模块内所有处理元素属于一个整体,完成一个单一的功能,则称为功能内聚。功能内聚是最高程度的内聚。

      • 评价:

        • 模块可重用,应尽可能重用;
        • 可隔离错误,维护更容易;
        • 扩充产品功能时更容易。
    • 七种内聚的优劣评分结果:

      • 高内聚:

        • 功能内聚 10分
        • 顺序内聚 9分
      • 中内聚:

        • 通信内聚 7分
        • 过程内聚 5分
      • 低内聚:

        • 时间内聚 3分
        • 逻辑内聚 1分
        • 偶然内聚 0分
    • 总结:

      • 设计时力争做到高内聚,并且能够辨认出低内聚的模块。对于一个模块而言,模块自身的内聚越强,模块间的耦合就越小,模块所具有的独立性就越好,可以说高内聚低耦合是我们进行软件设计的一贯原则。

5.3启发规则

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

  • 通过模块分解或合并,降低耦合提高内聚。

    • 例如,多个模块公有的一个子功能可以独立成一个模块,由这些模块调用;有时可以通过分解或合并模块以减少控制信息的传递及对全程数据的引用,并且降低接口的复杂程度。
  • 两个方面:

    • 模块功能完善化。一个完整的模块包含:

      • 执行规定的功能的部分
      • 出错处理的部分
      • 返回一个“结束标志”
    • 消除重复功能,改善软件结构。

      • 完全相似

      • 局部相似

2. 模块规模应该适中

  • 经验表明,一个模块的规模不应过大,最好能写在一页纸内。通常规定50~100行语句,最多不超过500行。数字只能作为参考,根本问题是要保证模块的独立性。
  • 过大的模块往往是由于分解不充分,但是进一步分解必须符合问题结构,一般说来,分解后不应该降低模块独立性。
  • 过小的模块开销大于有效操作,而且模块数目过多将使系统接口复杂。

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

  • 深度: 表示软件结构中控制的层数,它往往能粗略地标志一个系统的大小和复杂程度。深度和程序长度之间应该有粗略的对应关系。
  • 宽度: 表示软件结构中控制的总跨度。即同一个层次上的模块总数的最大值,宽度越大系统越复杂。
  • 扇出:表示一个模块直接控制(调用)的模块数目。扇出为3-4,上限扇出为5-9。
  • 扇入:表示有多个上级模块直接调用该模块,扇入越大则共享该模块的上级模块数目越多。
  • 软件结构一般要求顶层扇出较大,中层扇出较少,底层扇入较大为好。

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

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

  • 模块的控制域是包括自己以及所有下属模块的集合。

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

    • 第一,这种结构使得软件难于理解
    • 第二,为了能影响到G,需要在A中设置标记,并把标记传递给M(A和G的公共上级),再由M传递给G。这个标记是控制信息不是数据,将使得模块间出现控制耦合。
  • 解决方案:

    • 使判定上移。把模块A中的判定移到模块M中;

    • 受判定影响的模块下移。把模块G移到模块A下面,作为他的下级模块。

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

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

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

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

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

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

8.组装软件要根据设计的约束和移植的需要

5.4描绘软件结构的图形工具

1.层次图

  • 层次图用来描绘软件的层次结构。很适于在自顶向下设计软件的过程中使用。

  • 层次图中的每个方框代表一个模块,方框间的连线表示调用关系,不同于层次方框图那样表示组成关系。

  • 层次图和层次方框图的区别:

  • 例:

2.HIPO图

  • HIPO图是美国IBM公司发明的“层次图+输入/处理/输出图”的英文缩写。
  • 为了能使HIPO图具有可追踪性,在H图(层次图)里除了最顶层的方框之外,每个方框都加了编号。

3.结构图

  • 结构图描述了程序的模块结构,表示了一个系统的层次分解关系,反映了块间联系和块内联系等特征及控制信息的传递关系。

  • 基本图形符号:

  • 选择调用:判定为真时调用A,为假时调用B。

  • 循环调用:模块M循环调用模块A、B、C。

  • 例子

  • 注意:

    • 层次图和结构图并不严格表示模块的调用次序,多数人习惯按调用次序从左到右画模块;
    • 层次图和结构图并不指明何时调用下层模块;
    • 层次图和结构图只表明一个模块调用那些模块,没有表示模块内还有没有其他成分;
    • 通常用层次图作为描绘软件结构的文档;
    • 由层次图导出结构图的过程,可以作为检查设计正确性和评价模块独立性的好方法。

5.5面向数据流的设计方法(SD方法)

概念

  • 面向数据流的设计方法把信息流映射成软件结构,信息流的类型决定了映射的方法。

  • 变换流

    • 信息沿输入通路进入系统,同时由外部形式变换成内部形式,进入系统的信息通过变换中心,经加工处理以后再沿输出通路变换成外部形式离开软件系统。
    • 变换型结构的数据流图基本上呈线性形状
    • 明显地分为输入、变换(或加工)和输出三部分
  • 事务流

    • 系统的数据流图呈辐射状

    • 数据沿输入通路到达一个处理T,T根据输入数据的类型,在若干个动作序列中选出一个来执行。处理T称为事务中心,它完成下述任务:

      • 接收输入数据;
      • 分析每个事务以确定它的类型;
      • 根据事务类型选取一条活动通路。

变换分析

  • 第1步 复查基本系统模型
  • 第2步 复查并精化数据流图
  • 第3步 确定数据流的类型
  • 第4步 确定输入流和输出流边界,从而孤立出变换中心
  • 第5步 把数据流图映射成系统模块结构,即设计系统的上层模块结构
  • 第6步 基于数据流图逐步分解高层模块结构,设计出下层模块
  • 第7步 根据模块独立性原理,精化模块结构
  • 第8步 描述模块接口信息,给出进出模块的数据信息

事务分析

  • 虽然在任何情况下都可以使用变换分析方法设计软件结构,但是在数据流具有明显的事务特点时,也就是有一个明显的“发射中心”(事务中心)时,还是以采用事务分析方法为宜。

  • 事务分析的设计步骤和变换分析的设计步骤大部分相同或类似,主要差别仅在于由数据流图到软件结构的映射方法不同:

    • 由事务流映射成的软件结构包括一个接收分支和一个发送分支;
    • 映射出接收分支结构的方法和变换分析映射出输入结构的方法很相像,即从事务中心的边界开始,把沿着接收流通路的处理映射成模块;
    • 发送分支的结构包含一个调度模块,它控制下层的所有活动模块;然后把数据流图中的每个活动流通路映射成与它的流特征相对应的结构。

设计优化

  • (1) 在不考虑时间因素的前提下开发并精化软件结构
  • (2) 在详细设计阶段选出最耗费时间的那些模块,仔细地设计它们的处理过程,以求提高效率
  • (3) 使用高级程序设计语言编写程序
  • (4) 在软件中孤立出那些大量占用处理机资源的模块
  • (5) 必要时重新设计或用依赖于机器的语言重写上述大量占用资源的模块的代码,以求提高效率
  • 一句格言:“先使它能工作,然后再使它快起来。”