软件工程笔记
目录
- 第一章 软件工程概述
- 第二章 可行性研究
- 第三章 需求分析
- 第五章 总体设计
- 第六章 详细设计
- 第七章 实现
- 第八章 软件维护
- 第九章 面向对象方法学
- 第十章 软件项目管理
第一章 软件工程概述
1.1 软件危机
1.1.1软件危机介绍
📌软件危机:
就是计算机软件开发和维护过程中遇到的一系列问题。
主要包括:
- 如何开发
- 如何维护
📌软件危机的典型表现:
- 软件开发的成本和进度估计往往不正确
- 用户对已完成的软件产品不满意
- 软件的质量不可靠
- 软件不可维护
- 没有合适的文档
- 软件的成本占整个计算机系统的成本的比例逐年增加
- 软件开发的效率赶不上计算机应用普及的速度
1.1.2产生软件危机的原因
📌软件本身的特点
- 软件是逻辑产品不是物理部件。
- 软件规模庞大,程序复杂性随着规模的增长成指数上升。
📌开发和维护软件的方法不正确
- 对软件开发和维护有很多错误观念
- 对用户需求没有完整准确的认识就着手编写代码
- 轻视软件维护
- 只重视程序而忽视软件配置其余成分
- 软件开发在不同的阶段进行修改需要付出的代价不同
- 软件从定义,开发,使用和维护,直到最终被废弃,要经历漫长的时期
1.1.3消除软件危机的途径
📌彻底消除软件就是程序的错误观念。
📌组织管理各类人员协同配合,共同完成。
📌使用在实践中总结出来的开发软件的成功的技术和方法。
📌开发和使用更好的软件工具。
1.2 软件工程
1.2.1软件工程概念
📌软件工程概念:
软件工程是使用工程的原理,概念,方法和技术开发和维护软件,把经过时间证明的正确的管理方法以及目前能够得到的最先进的技术,经济的开发和维护软件产品的工程学科
软件工程本质特征:
- 软件工程关注大型程序的构造。
- 软件工程的中心课题是控制复杂性。
- 软件经常变化。
- 开发软件的效率非常重要。
- 和谐的合作是开发软件的关键。
- 软件必须有效的支持他的用户。
- 在软件工程中通常由一种文化背景的人为另一种文化背景的人创造产品。
1.2.2软件工程基本原理
📌软件工程基本原理:
- 用分阶段的生命周期计划严格管理。
- 坚持阶段评审。
- 实行严格的产品控制。
- 采用现代程序设计技术。
- 结果应能清楚的审查。
- 开发小组人员应该少而精。
- 承认不断改进软件工程实践的必要性
1.2.3软件工程方法学
📌软件工程方法学概念:
软件工程方法学就是软件生命周期过程中使用到的一系列方法和技术的集合
三要素:方法,过程和工具
分为:传统方法学(结构化泛型)和面向对象方法学
📌软件过程:是为了获得高质量产品进行的一系列任务的框架
{软件过程是软件工程方法学的一个重要的要素}
📌1.传统方法学
定义:
采用结构化技术(结构化分析,结构化设计,结构化实现)来完成软件开发的各项任务,并使用适当的软件工具和软件过程环境支持结构化技术的运用。
优点:
- 把软件生命周期划分成若干个阶段,每个阶段的任务相互独立,而且简单,有利于不同人员协作开发
- 在软件生命周期的每个阶段都采用科学的管理技术和良好的技术方法,且在每个阶段结束之前都进行严格的审查,保证了软件的质量,提高了软件的可维护性
- 采用生命周期方法学可以大大提高软件开发的成功率,软件开发的生产率也能得到提高
📌2.面向对象方法学
把数据和行为看成同等重要,以数据为主线,把数据和操作紧密结合起来的方法。
要点:
- 把对象作为融合了数据及在数据上的操作行为的统一软件构件。
- 把所有对象划分成类。
- 继承性,多态性,封装性。
- 对象间仅能通过消息互相通信。
优点:
- 降低了软件产品的复杂性。
- 提高了软件的可理解性。
- 简化了软件的开发和维护工作
- 提高了软件的可重用性。
1.3软件生命周期
1.3.1软件生命周期定义
📌 定义:
软件生命周期是指一个软件从提出,定义,开发和维护到最后淘汰经历的漫长时期
1.3.2软件生命周期划分
软件生命周期划分为:
- 软件定义
- 问题定义 (确定系统要解决的问题)
- 可行性研究 (对于定义的问题确定是否有行得通的解或值得解)
- 需求分析 (确定系统应该具备哪些功能,编写需求规格说明书SRS)
- 软件开发
- 总体设计 (应该怎样实现系统,设计系统的体系结构,确定程序由哪些模块组成,以及模块间的关系
- 详细设计 (详细设计每个模块,确定模块功能和数据结构)
- 编码和单元测试
- 综合测试
- 软件维护 (通过各种必要的维护活动使系统持久的满足用户的需要)
📌改正性维护 (软件交付使用后,修改和改正软件运行过程中发现的错误的过程)
📌完善性维护(为了满足用户对系统提出的新的功能和性能的要求,扩充和完善软件功能的过程)
📌适应性维护 (为了适应软件运行环境变化修改软件的过程)
📌预防性维护(为了提高软件将来的可维护性,可靠性或者为了给未来的改进奠定良好的基础而修改软件的过程)
1.3.3软件生命周期模型
-
瀑布模型
瀑布模型是应用得最早,也是最广泛的生命周期模型。
定义:
传统软件工程方法学的软件过程,基本上可以用瀑布模型来描述。
特点:
-
阶段间具有顺序性和依赖性
① 必须等前一阶段的工作完成之后,才能开始后一阶段的工作;
② 前一阶段的输出文档就是后一阶段的输入文档,因此,只有前一阶段的输出文档正确,后一阶段的工作才能获得正确的结果。
-
推迟实现的观点
瀑布模型在编码之前设置了系统分析与系统设计的各个阶段,分析与设计阶段的基本任务规定,在这两个阶段主要考虑目标系统的逻辑模型,不涉及软件的物理实现。
-
质量保证的观点
(1) 每个阶段都必须完成规定的文档。
(2) 每个阶段结束前都要对所完成的文档进行评审。
实际的瀑布模型是带“反馈环”的,如图1.3所示。当在后面阶段发现前面阶段的错误时,需要沿图中左侧的反馈线返回前面的阶段,修正前面阶段的产品之后再回来继续完成后面阶段的任务
📌优点:
- 迫使开发人员采用规范的方法;
- 严格的规定每个阶段都要提交文档;
- 要求每个阶段交出的所有产品都必须要经过质量小组的仔细验证;
- 对文档的约束,使软件维护变得容易一些,且能降低软件预算
📌缺点: - 难以适应需求不确定或者经常变化的项目,缺乏灵活性。
- 开发过程末期见到成果,风险大;
- 开发早期存在的问题往往要到交付使用时才发现,维护代价大。
-
-
快速原型模型
- 快速建立起来的可以在计算机上运行的程序,完成的功能少于最终产品的。
- 不带反馈环,软件开发基本是线性顺序进行的。(原因有二:原型系统已经通过与用户交互而得到验证,不会进行较大的返工;建立原型系统已经学到了许多东西,发生错误的可能性小。)
- 适合于一些需求可变、模糊不定的软件系统开发。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zxKboPVH-1691070364387)(image/image_fSUgQefpvK.png)]
📌优点:
不带反馈环,软件开发基本上顺序执行
📌缺点:产品的先天性不足,因为开发者常常需要做实现上的折中,可能采用不合适的操作系统或程序设计语言,以使原型能够尽快工作
-
增量模型
概念及特点
- 把软件产品作为一系列的增量构件来设计、编码、集成和测试。
- 第一个增量构件往往实现软件的基本需求。
- 适合于软件要求不明确,设计方案有一定风险的软件项目。
📌优点:
能在短时间内提交一个可以完成部分功能的产品,使用户具有充裕的时间学习和适应新系统
📌缺点:- 软件体系结构必须是开放的。
- 多个构件并行开发,具有无法集成的风险。
-
螺旋模型
概念及特点
- 使用原型及其他方法来尽量降低风险(每个阶段都增加了风险分析过程的快速原型模型,即瀑布与快速的结合)
- 适用于内部开发的复杂大型软件项目;需要开发人员具有丰富的风险评估和知识经验。
📌优点:
- 有利于已有软件的重用;专注于软件质量;减少测试带来的风险;软件开发与维护没有本质区别。
📌缺点:
需要开发人员具有丰富的风险评估和知识经验
-
喷泉模型 (面向对象方法学模型)
-
Rational统一过程(RUP)
九个核心工作流:
业务建模,需求,分析与设计,实现,测试,部署,配置与变更管理,项目管理,环境
四个阶段:
初始阶段,精化阶段,构建阶段,移交阶段
-
敏捷过程与极限编程
敏捷过程四个价值观:
- 个体和交互胜过过程和工具
- 可以工作的软件胜过面面俱到的文档
- 客户谈判胜过合同谈判
- 响应变化胜过遵循计划
极限编程有效实践: - 客户作为开发团队成员
- 使用用户素材
- 短周期交付
- 验收测试
- 结对编程
- 测试驱动开发(测试先行)
- 集体所有
- 持续集成
- 可持续的开发速度
- 开放的工作空间
- 及时调整计划
- 重构
- 使用隐喻
-
微软过程
第二章 可行性研究
2.1 可行性研究介绍
2.1.1 可行性研究目的
📌目的:
用最小的代价在最短的时间内确定问题是否有解是否值得解。
2.1.2 可行性研究任务
- 技术可行性:使用现有的技术是否能够实现该系统;
- 经济可行性:这个系统的经济效益是否能够超过他的开发成本;
- 操作可行性:操作方式在客户组织内部是否行得通
2.1.3可行性研究过程
- 复查系统的规模和目标
- 研究正在使用的系统
- 导出新系统逻辑模型
- 进一步定义问题
- 导出和评价供选择的解法
- 推荐行动方针
- 草拟开发计划
- 书写文档提交审查
2.3系统流程图
📌定义:描绘物理系统的传统工具
表示数据在系统各部件之间流动的情况,而不是对数据进行加工处理的控制过程
2.3.1 符号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NpARs8Hh-1691070364388)(image/image_C8qycpNrdj.png)]
2.3.2 例子
某装配厂有一座存放零件的仓库,仓库中现有的各种零件的数量以及每种零件的库存量临界值等数据记录在库存清单主文件中。当仓库中零件数量有变化时,应该及时修改库存清单主文件,如果哪种零件的库存量少于它的库存量临界值,则应该报告给采购部门以便定货,规定每天向采购部门送一次定货报告。
该装配厂使用一台小型计算机处理更新库存清单主文件和产生定货报告的任务。零件库存量的每一次变化称为一个事务,由放在仓库中的CRT终端输入到计算机中;系统中的库存清单程序对事务进行处理,更新存储在磁盘上的库存清单主文件,并且把必要的定货信息写在磁带上。最后,每天由报告生成程序读一次磁带,并且打印出定货报告。图2.3 的系统流程图描绘了上述系统的概貌。图中每个符号用黑盒子形式定义了组成系统的一个部件,箭头确定了信息通过系统的逻辑路径。系统流程图的习惯画法是使信息在图中从顶向下或从左向右流动。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tQjR3t4A-1691070364389)(image/image_WOpkbRrOB5.png)]
2.3.3 分层
面对复杂的系统时,一个比较好的方法是分层次地描绘这个系统。首先用一张高层次的系统流程图描绘系统总体概貌,表明系统的关键功能;然后分别把每个关键功能扩展到适当的详细程度。
2.4数据流图
2.4.1 概念
📌数据流图(DFD)是一种图形化技术,它描绘信息流和数据从输入移动到输出的过程中所经受的变换
2.4.2 符号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mGQkzFca-1691070364390)(image/image_99SCcxUleA.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nvqOQleA-1691070364390)(image/image_xSuwFJyTUL.png)]
2.4.3例子
假设一家工厂的采购部每天需要一张定货报表,报表按零件编号排序,表中列出所有需要再次定货的件。对于每个需要再次定货的零件应该列出下述数据:零件编号,零件名称,定货数量,目前价格,主要供应者,次要供应者。零件入库或出库称为事务,通过放在仓库中的CRT终端把事务报告给定货系统。当某种零件的库存数量少于库存量临界值时就应该再次定货。数据流图有4种成分:源点或终点,处理,数据存储和数据流。因此,第一步可以从问题描述中提取数据流图的4种成分: 首先考虑数据的源点和终点,从上面对系统的描述可以知道“采购部每天需要一张定货报表”,“通过放在仓库中的CRT终端把事务报告给定货系统”,所以采购员是数据终点,而仓库管理员是数据源点。接下来考虑处理,再一次阅读问题描述,“采部需要报表”,显然他们还没有这种报表,因此必须有一个用于产生报表的处理。事务的后果是改变零件库存量,然而任何改变数据的操作都是处理,因此对事务进行的加工是另一个处理。
接下来应该对功能级数据流图中描绘的系统主要功能进一步细化。考虑通过系统的逻辑数据流:当发
生一个事务时必须首先接收它;随后按照事务的内容修改库存清单;最后如果更新后的库存量少于库存量临界值时,则应该再次定货,也就是需要处理定货信息。因此,把“处理事务”这个功能分解为下述3个步骤,这在逻辑上是合理的:“接收事务”、“更新库存清单”和“处理定货”(图2.7)。当对数据流图分层细化时必须保持信息连续性,也就是说,当把一个处理分解为一系列处理时,分解前和分解后的输入输出数据流必须相同。
2.4.4 命名
-
为数据流(或数据存储)命名
(1) 名字应代表整个数据流(或数据存储)的内容,而不是仅仅反映它的某些成分。
(2) 不要使用空洞的、缺乏具体含义的名字(如“数据”、“信息”、“输入”之类)。
(3) 如果在为某个数据流(或数据存储)起名字时遇到
了困难,则很可能是因为对数据流图分解不恰当造
成的,应该试试重新分解,看是否能克服这个困难。
-
为处理命名
(1) 通常先为数据流命名,然后再为与之相关联的处理命名。
(2) 名字应该反映整个处理的功能,而不是它的一部分功能。
(3) 名字最好由一个具体的及物动词加上一个具体的宾语组成。应该尽量避免使用“加工”、“处理”等空洞笼统的动词作名字。
(4) 通常名字中仅包括一个动词,如果必须用两个动词才能描述整个处理的功能,则把这个处理再分解成两个处理可能更恰当些。
(5) 如果在为某个处理命名时遇到困难,则很可能是发现了分解不当的迹象,应考虑重新分解。
2.4.5 用途
📌目的:
- 作为交流信息的工具
- 作为系统分析和设计的工具
2.5 数据字典
2.5.1 概念
📌数据字典是关于数据的信息集合,也就是对数据流图中所有元素的定义的集合
优点:全面而准确地定义数据
缺点:不够形象直观
意义:
数据流图和数据字典共同构成系统的逻辑模型,没有数据字典,数据流图就不严格,没有数据流图,数据字典也难以发挥作用。只有数据流图和对数据流图中的每个元素的精确定义放在一起,才能共同构成系统的规格说明。
2.5.2 内容
📌由四类元素的定义组成:
数据流
数据元素
数据处理
数据存储
2.5.3 定义数据的方法
-
数据定义方式:由数据元素组成数据的方式只有以下三种基本类型:
- 顺序:即以确定次序连接两个或多个分量。
- 选择:即从两个或多个可能的元素中选取一个。
- 重复:即把指定的分量重复零次或多次。
- 可选 即一个分量是可有可无的(重复零次或一次)。
为了更加清晰简洁,建议采用下列符号:
=
意思是等价于(或定义为);
+
意思是和(连接两个分量);[ ]
意思是或(即,从方括弧内列出的若干个分量中选择一个),通常用“|”号隔开供选择的分量;{ }
意思是重复(即,重复花括弧内的分量);( )
意思是可选(即,圆括弧里的分量可有可无)。下面举例说明上述定义数据的符号的使用方法:某程序设计语言规定,用户说明的标识符是长度不超过8个字符的字符串,其中第一个字符必须是字母字符,随后的字符既可以是字母字符也可以是数字字符。使用上面讲过的符号,我们可以像下面那样定义标识符:
标识符 = 字母字符 + 字母数字串
字母数字串 = 0{字母或数字}7
字母或数字 = [字母字符 | 数字字符]
字母字符 = a … z
数字字符 = 0 … 9
2.5.4 用途
📌作用:
- 作为分析阶段的工具
- 数据字典中包含的每个数据元素的控制信息是很有价值的
- 数据字典是开发数据库的第一步
2.5.5 实现
目前,数据字典几乎总是作为CASE“结构化分析与设计工具”的一部分实现的。在开发大型软件系统的过程中,数据字典的规模和复杂程度迅速增加,人工维护数据字典几乎是不可能的。如果在开发小型软件系统时暂时没有数据字典处理程序,可采用卡片形式书写数据字典,每张卡片上保存描述一个数据的信息。这样做更新和修改起来比较方便,而且能单独处理描述每个数据的信息。每张卡片上主要应该包含下述这样一些信息:名字、别名、描述、定义、位置。
2.6成本/效益分析
目的:
从经济角度分析开发一个特定的新系统是否划算
2.6.1成本估计
- 代码行技术
- 任务分解技术
- 自动估计成本技术
2.6.2 成本/效益分析方法
📌四个衡量指标:
- 货币时间价值
- 投资回收期
- 纯收入
- 投资回收率
第三章 需求分析
3.1 需求分析相关概念
3.1.1 定义
📌需求分析是软件定义的最后一个阶段,基本任务是准确的回答“系统必须做什么”,即对目标系统提出完整,准确,清晰,具体的要求。并编写需求规格说明书
3.1.2 必要性
为了开发出真正满足用户需求的软件产品,必须知道用户的需求。对软件需求深入的理解是软件开发工作中获得成功的前提条件,不论人们把编码工作做得如何出色,不能真正满足用户需求的程序只会令用户失望,给开发者带来烦恼。
3.1.3 准则
- 必须理解并描述问题的信息域,根据这条准则应该建立数据模型
- 必须定义软件应完成的功能,建立功能模型
- 必须描述作为外部事件结果的软件行为,这条准则要求建立行为模型
- 必须对描述信息,功能和行为的模型进行分解,用层次的方式展示细节。
3.2需求分析任务
3.2.1确定系统的综合要求
-
功能需求
-
性能需求
-
可靠和可用性需求
-
出错处理需求
-
接口需求
-
约束
设计约束或实现约束描述在设计或实现应用系统时应遵守的限制条件
-
逆向需求
-
将来可能提出的需求
3.2.2分析系统的数据要求
意义:
任何一个软件系统本质上都是信息处理系统,系统必须处理的信息和系统必须产生的信息很大程度上决定了系统的面貌。因此,必须分析系统的数据要求
工具:
建立数据模型,常使用层次方框图,warnier图
3.2.3导出系统的逻辑模型
- 常用数据流图,E-R图,状态转换图,数据字典
3.2.4修正系统的开发计划
3.3获取需求的方法
3.3.1 访谈
正式访谈 :
系统分析员将提出一些事先准备好的具体问题
非正式访谈:
分析员将提出一些用户可以自由回答的开放性问题, 以鼓励被访问人员说出自己的想法。
情景分析技术:
情景分析就是对用户将来使用目标系统解决某个具体问题的方法和结果进行分析。
当需要调查大量人员的意见时,向被调查人分发调查表是一个十分有效的做法
3.3.2 面向数据流自顶向下求精
📌结构化分析(SA)方法是面向数据流自顶向下逐步求精分析需求的方法。
3.3.3 简易的应用规格说明技术
简易的应用规格说明技术是一种面向团队的需求收集法
用户和开发者不分彼此,紧密合作
3.3.4快速建立软件模型
快速的建立起具有目标系统主要功能的可运行的程序。
- 使用工具和技术:
-
第四代技术
-
可重用的软件构件
数据结构(数据库),软件体系结构构件(程序),过程构件(模块)
-
形式化规格说明和原型环境
-
3.4分析建模与规格说明
3.4.1分析建模
所谓模型,就是为了理解事物而对事物做出的一种抽象,是对事物的一种无歧义的书面描述。
需求分析过程应该建立3种模型,它们分别是数据模型、功能模型和行为模型。
3.4.2 软件需求规格说明
需求分析阶段还应该写出软件需求规格说明书(SRS),它是需求分析得出的最主要的文档。
3.5实体-联系图
为了把用户的数据要求清楚、准确地描述出来,系统分析员通常建立一个概念性的数据模型(也称为信息模型)。概念性数据模型是一种面向问题的数据模型,是按照用户的观点对数据建立的模型。它描述了从用户角度看到的数据,它反映了用户的现实环境,而且与在软件系统中的实现方法无关。数据模型中包含3种相互关联的信息:数据对象、数据对象的属性及数据对象彼此间相互连接的关系。
📌描述系统所有数据对象的组成和属性及数据对象关系的图形语言
3.5.1数据对象
数据对象是软件必须理解的复合信息的抽象。复合信息指具有一系列不同性质或属性的事物,仅有单个值的事物不是数据对象
特点:
- 可以由一组属性来定义的实体都可以被认为是数据对象。
- 数据对象之间彼此是有关联的
- 数据对象只封装了数据而没有施加于数据上的操作
3.5.2属性
属性定义了数据对象的性质
3.5.3联系
数据对象彼此之间相互连接的方式称为联系
1:1, 1:N, N:N
3.6 数据规范化
第一范式:属性必须是原子值
第二范式:每一个非主属性都由整个关键字决定
第三范式:每一个非关键字仅由关键字决定(不存在传递依赖)
3.7 状态转换图
📌定义:
状态转换图通过描绘系统的状态及引起系统状态转换的事件,来表示系统的行为。
3.7.1 状态
📌定义:状态是任何可以被观察到的系统行为模式,一个状态代表系统的一种行为模式。
分类:初始状态,终态,一个状态转换图只能有一个初始状态,可以有多个或0个终态
3.7.2事件
📌事件是某个时刻发生的事情,它是对引起系统从一个状态转化成另一个状态的外界事件的抽象。
3.7.3状态图符号
在状态图中,初态用实心圆表示,终态用一对同心圆(内圆为实心圆)表示。中间状态用圆角矩形表示,可以用两条水平横线把它分成上、中、下3个部分。上面部分为状态的名称,这部分是必须有的;中间部分为状态变量的名字和值,这部分是可选的;下面部分是活动表,这部分也是可选的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V6v9yGgu-1691070364391)(image/image_Y4aUJT9u3X.png)]
3.7.4例子
图中表明,没有人打电话时电话处于闲置状态;有人拿起听筒则进入拨号音状态,到达这个状态后,电话的行为是响起拨号音并计时;这时如果拿起听筒的人改变主意不想打了,他把听筒放下(挂断),电话重又回到闲置状态;如果拿起听筒很长时间不拨号(超时),则进入超时状态;……。
3.8 其他图形工具(要知道什么阶段会用到哪些图)
3.8.1 层次方框图
层次方框图用树形结构的一系列多层次的矩形框描绘数据的层次结构。
3.8.2 Warnier图
法国计算机科学家Warnier提出了表示信息层次结构的另外一种图形工具。Warnier图也用树形结构描绘信息。
3.8.3 IPO图
IPO图是输入、处理、输出图的简称,它是IBM公司发展完善起来的一种图形工具,能够方便地描绘输入数据、对数据的处理和输出数据之间的关系
IPO图的基本形式是在左边的框中列出有关的输入数据,在中间的框内列出主要的处理,在右边的框内列出产生的输出数据。处理框中列出处理的次序暗示了执行的顺序,在IPO图中用箭头清楚地指出数据通信的情况。图3.7是一个主文件更新的例子
一种改进的IPO图(也称为IPO表),这种图中包含某些附加的信息,在软件设计过程中将比原始的IPO图更有用。在需求分析阶段可以使用IPO图简略地描述系统的主要算法(即数据流图中各个处理的基本算法)
3.9验证软件需求
3.9.1验证软件需求的正确性
验证软件需求正确性的目的:
需求分析阶段的工作是开发软件系统的重要基础。为了提高软件质量,确保软件成功开发,降低软件开发成本,一旦对目标系统提出一组需求之后,必须严格验证这些需求的正确性。
从四个方面验证:
(1) 一致性:所有需求必须是一致的,任何一条需求不能和其他需求互相矛盾。
(2) 完整性:需求必须是完整的,规格说明书应该包括用户需要的每一个功能或性能。
(3) 现实性:指定的需求应该是用现有的硬件技术和软件技术基本上可以实现的。
(4) 有效性:必须证明需求是正确有效的,确实能解决用户面对的问题。
3.9.2验证软件需求的方法
-
验证需求的一致性
人工审查系统规格说明书
软件工具验证
-
验证需求的现实性
为了验证需求的现实性,分析员应该参照以往开发.类似系统的经验,分析用现有的软、硬件技术实现目标系统的可能性。必要的时候应该采用仿真或性能模拟技术,辅助分析软件需求规格说明书的现实性。
-
验证需求的完整性和有效性
只有目标系统的用户才真正知道软件需求规格说明书是否完整、准确地描述了他们的需求。因此,检验需求的完整性,特别是证明系统确实满足用户的实际需要,只有在用户的密切合作下才能完成。
第五章 总体设计
5.1总体设计概要
5.1.1定义
总体设计主要回答“系统应该怎么实现”的问题,又称为概要设计或初步设计
5.1.2 主要任务
- 划分出组成系统的物理元素程序, 文件,数据库,人工过程和文档等,但是每个物理元素仍然处于黑盒子级,这些黑盒子里的具体内容将在以后仔细设计。
- 设计软件的结构,确定系统由哪些模块组成,以及模块间的关系
5.1.3 步骤
- 寻找实现目标系统的各种不同的方案,需求分析阶段得到的数据流图是设想各种方案的基础
- 分析员从若干个方案中选出合理的方案,并为每个合理的方案都准备一份系统流程图,列出组成系统的物理元素,进行成本/效益分析,并制定实现这个方案的进度计划。
- 进行必要的数据库设计,确定测试要求并制定测试计划
5.1.4 必要性
📌可以站在全局高度上,花较少的成本,从比较抽象的层次上分析对比多种可能的系统实现方案和软件结构,从中选出最佳方案和最合理的软件结构,从而用较低的成本开发高质量的软件
5.2 设计过程
总体设计过程通常由两个主要阶段组成:系统设计阶段,确定系统的具体实现方案;结构设计阶段,确定软件结构。典型的总体设计过程包括下述9个步骤:
5.2.1设想供选择的方案
5.2.2选取合理的方案
5.2.3推荐最佳方案
5.2.4功能分解
通常分为两个阶段完成:首先进行结构设计,然后进行过程设计。结构设计确定程序由哪些模块组成,以及这些模块之间的关系;过程设计确定每个模块的处理过程。结构设计是总体设计阶段的任务,过程设计是详细设计阶段的任务。
为确定软件结构,首先需要从实现角度把复杂的功能进一步分解。分析员结合算法描述仔细分析数据流图中的每个处理,如果一个处理的功能过于复杂,必须把它的功能适当地分解成一系列比较简单的功能。一般说来,经过分解之后应该使每个功能对大多数程序员而言都是明显易懂的。功能分解导致数据流图的进一步细化,同时还应该用IPO图或其他适当的工具简要描述细化后每个处理的算法。
5.2.5设计软件结构
通常程序中的一个模块完成一个适当的子功能。应该把模块组织成良好的层次系统,顶层模块调用它的下层模块以实现程序的完整功能,每个下层模块再调用更下层的模块,从而完成程序的一个子功能,最下层的模块完成最具体的功能。软件的逻辑结构可以用层次图或结构图来描绘。
5.2.6设计数据库
对于需要使用数据库的那些应用系统,软件工程师应该在需求分析阶段所确定的系统数据需求的基础上,进一步设计数据库。
5.2.7制定测试计划
在软件开发的早期阶段考虑测试问题,能促使软件设计人员在设计时注意提高软件的可测试性。
5.2.8书写文档
应该用正式的文档记录总体设计的结果 ,在这个阶段应该完成的文档通常有下述几种:
(1) 系统说明: 主要内容包括用系统流程图描绘的系统构成方案,组成系统的物理元素清单,成本/效益分析;对最佳方案的概括描述,精化的数据流图,用层次图或结构图描绘的软件结构,用IPO图或其他工具简要描述的各个模块的算法,模块间的接口关系,以及需求、功能和模块三者之间的交叉参照关系等等。
(2) 用户手册: 根据总体设计阶段的结果,修改更
正在需求分析阶段产生的初步的用户手册。
(3) 测试计划:包括测试策略,测试方案,预期的
测试结果,测试进度计划等等。
(4) **详细的实现计划 **
(5)** 数据库设计结果**
5.2.9审查和复审
最后应该对总体设计的结果进行严格的技术审查,在技术审查通过之后再由使用部门的负责人从管理角度进行复审
5.3设计原理
5.3.1 模块化
📌模块:
模块是由边界元素限定的相邻程序元素的序列, 而且总有一个标识符代表它,模块是程序的基本构件
📌模块化:
为了解决问题将软件系统从上至下逐层分解成若干模块的过程。
📌优点:
使软件结构清晰,提高了可阅读性和可理解性
使软件容易测试和调试,有助于提高软件的可靠性
提高软件的可修改性
有助于软件开发工程的组织管理
5.3.2抽象
📌定义:
抽出事物的本质而暂时忽略他们的细节。
📌优点:
简化软件的设计和实现
提高了软件的可理解性和可维护性
使得软件更容易维护
5.3.3 逐步求精
📌定义:
逐步求精是一种自顶向下的设计策略,是人类解决复杂问题常用的一种技术。为了能够集中精力解决主要问题而尽量推辞考虑问题的细节。
5.3.4 信息隐藏和局部化
📌信息隐藏定义:
一个模块内的信息对于不需要这些信息的其他模块来说是不能访问的。
📌局部化定义:
把一些关系密切的软件元素放得彼此靠近。
📌优点:
绝大多数数据和过程对于软件的其他部分而言是隐蔽的,在修改期间由于疏忽引入的错误就很少可能传播到软件的其他部分。
5.3.5模块独立
📌定义:
每个模块都能完成一个相对独立的特定子功能,并且与其他模块的关系很简单。
📌重要性:
具有独立模块的软件比较容易开发
每个独立的模块容易测试和维护
📌衡量模块独立程度的标准:内聚和耦合。
📌内聚:
衡量模块内部各元素之间的彼此结合紧密程度的度量。
内聚分类:
- 低内聚
- 偶然内聚
- 各元素之间没什么实质联系
- 逻辑内聚
- 完成的任务在逻辑上属于相似或相同一类
- 时间内聚
- 一个模块内包含的任务必须在同一时间内执行
- 中内聚
- 过程内聚
- 一个模块内的处理元素相关,必须以特定的次序执行
- 通信内聚
- 模块中的所有元素都使用同一个输入数据或产生同一个输出数据
- 高内聚
- 顺序内聚
- 一个模块内的处理元素和同一个功能密切相关,切必须顺序执行
- 功能内聚
- 模块内的处理元素完成一个单一的功能
📌耦合:
衡量各模块之间的相互联系紧密程度的度量。
耦合分类:
完全独立
数据耦合 通参数传递信息,信息只有数据
控制耦合 通过参数传递信息,信息包含控制信息
特征耦合 传递了整个数据结构而只用到其中部分信息
公共环境耦合 模块有共享内存等 通过公共环境相互作用
📌内容耦合 :
- 一个模块访问另一个模块的内部数据
- 一个模块通过不正常入口进入另一个模块的内部
- 一个模块具有多个入口
- 两个模块有一部分程序代码重叠
尽量使用数据耦合,少用控制耦合和特征耦合,限制公共环境耦合的范围,完全不用内容耦合。
5.4启发式原则
启发原则
1.改进软件结构提高模块独立性
- 2.模块数量适中
- 3.深度,宽度,扇入,扇出都应适当
- 4.模块的作用域应该在控制域之内
- 5.力争降低模块接口的复杂度
- 6.设计单入口单出口的模块
- 7.模块功能应该可以预测
📌模块控制域:包含模块本身及其从属模块的集合。
📌模块的作用域:指受该模块内部的一个判断所影响的模块的集合。
📌扇入:表明有多少上级模块直接调用它。
📌扇出:表明直接控制的模块的数目。
📌宽度:是软件结构中同一层次的模块数量的最大值。
📌深度:表示软件结构控制的层数。
5.5描绘软件结构的图形工具
5.4.1 层次图和HIPO图
层次图用来描绘软件的层次结构。层次图中的一个矩形框代表一个模块,方框间的连线表示调用关系。图5.3是层次图的一个例子。
层次图很适于在自顶向下设计软件的过程中使用。HIPO图是IBM公司发明的“层次图加输入/处理/输出图”的缩写。为了能使HIPO图具有可追踪性,在层次图里除了最顶层的方框之外,每个方框都加了编号。图5.3加了编号后得到图5.4。
5.4.2 结构图
Yourdon提出的结构图是进行软件结构设计的另一个工具。结构图和层次图类似,也是描绘软件结构的图形工具,图中一个方框代表一个模块,框内注明模块的名字或主要功能;方框之间的箭头(或直线)表示模块的调用关系。因为按照惯例总是图中位于上方的方框代表的模块调用下方的模块,即使不用箭头也不会产生二义性,为了简单起见,可以只用直线而不用箭头表示模块间的调用关系。在结构图中通常还用带注释的箭头表示模块调用过程中来回传递的信息。如果希望进一步标明传递的信息是数据还是控制信息,则可以利用注释箭头尾部的形状来区分:尾部是空心圆表示传递的是数据,实心圆表示传递的是控制信息。图5.5是结构图的一个例子。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-etfcGzcx-1691070364392)(image/image_qxTMjnHPAx.png)]
以上介绍的是结构图的基本符号,也就是最经常使用的符号。此外还有一些附加的符号,可以表示模的选择调用或循环调用。图5.6表示当模块M中某个判定为真时调用模块A,为假时调用模块B。图5.7表示模块M循环调用模块A、B和C。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DKJdKxhK-1691070364393)(image/image_FvZ3Ws74AW.png)]
5.6 面向数据流的设计方法
面向数据流的设计方法的目标是给出设计软件结构的一个系统化的途径。在软件工程的需求分析阶段,信息流是一个关键考虑,通常用数据流图描绘信息在系统中加工和流动的情况。面向数据流的设计方法定义了一些不同的 **“映射”,利用这些映射可以把数据流图变换成软件结构 **。因为任何软件系统都可以用数据流图表示,所以面向数据流的设计方法理论上可以设计任何软件的结构。通常所说的结构化设计方法(简称SD方法),也就是基于数据流的设计方法。
5.6.1 概念
面向数据流的设计方法把信息流映射成软件结构,信息流的类型决定了映射的方法。信息流有下述两种类型。
-
变换流
进入系统的信息通过变换中心,经加工处理以后再沿输出通路变换成外部形式离开软件系统。当数据
流图具有这些特征时,这种信息流就叫作变换流。
-
事务流
数据沿输入通路到达一个处理T,这个处理根据输入数据的类型在若干个动作序列中选出一个来执行。
当数据流图具有类似的形状时,这种数据流是“以事务为中心的”,称为事务流。图5.9中的处理T称为事务中心,它完成下述任务:
(1) 接收输入数据(输入数据又称为事务);
(2) 分析每个事务以确定它的类型;
(3) 根据事务类型选取一条活动通路。
5.6.2设计过程
图5.10说明了使用面向数据流方法逐步设计的过程。注意,任何设计过程都不是机械地一成不变的。
5.6.3 变换分析
变换分析是一系列设计步骤的总称,经过这些步骤把具有变换流特点的数据流图按预先确定的模式映射成软件结构。
-
例子
考虑汽车数字仪表板的设计。假设的仪表板将完成下述功能:
(1) 通过模数转换实现传感器和微处理机接口;
(2) 在发光二极管面板上显示数据;
(3) 指示每小时英里数(mph),行驶的里程,每加仑
油行驶的英里数(mpg)等等;
(4) 指示加速或减速;
(5) 超速警告:如果车速超过55英里/小时,则发出超速警告铃声。
在软件需求分析阶段应该对上述每条要求以及系统的其他特点进行全面的分析评价,建立起必要的文档资料,特别是数据流图。
-
设计步骤
第1步 复查基本系统模型。复查的目的是确保系统的输入数据和输出数据符合实际。
第2步 复查并精化数据流图。应该对需求分析阶段得出的数据流图认真复查,并且在必要时进行精化。不仅要确保数据流图给出了目标系统的正确的逻辑模型,而且应该使数据流图中每个处理都代表一个规模适中相对独立的子功能。假设在需求分析阶段产生的数字仪表板系统的数据
流图如图5.11所示。
这个数据流图对于软件结构设计的“第一次分割”而言已经足够详细了,因此不需要精化就可以进行下一个设计步骤。第3步 确定数据流图具有变换特性还是事务特性。 一般地说,一个系统中的所有信息流都可以认为是变换流,但是,当遇到有明显事务特性的信息流时,建议采用事务分析方法进行设计。在这一步,设计人员应该根据数据流图中占优势的属性,确定数据流的全局特性。此外还应该把具有和全局特性不同的特点的局部区域孤立出来,以后可以按照这些子数据流的特点精化根据全局特性得出的软件结构。从图5.11看出,数据沿着两条输入通路进入系统,然后沿着5条通路离开,没有明显的事务中心。因此可以认为这个信息流具有变换流的总特征。第4步确定输入流和输出流的边界,从而孤立出变换中心。输入流和输出流的边界和对它们的解释有关,也就是说,不同设计人员可能会在流内选取稍微不同的点作为边界的位置。当然在确定边界时应该仔细认真,但是把边界沿着数据流通路移动一个处理框的距离,通常对最后的软件结构只有很小的影响。对于汽车数字仪表板的例子,设计人员确定的流的边界如图5.12所示
对第一次分割得到的软件结构,总可以根据模块独立原理进行精化。为了产生合理的分解,得到尽可能高的内聚、尽可能松散的耦合,最重要的是,为了得到一个易于实现、易于测试和易于维护的软件结构,应该对初步分割得到的模块进行再分解或合并。具体到数字仪表板的例子,对于从前面的设计步骤得到的软件结构,还可以做许多修改。下面是某些可能的修改:输入结构中的模块“转换成rpm”和“收集sps”可以合并;模块“确定加速/减速”可以放在模块“计算mph”下面,以减少耦合;模块“加速/减速显示”可以相应地放在模块“显示mph”的下面。经过上述修改后的软件结构画在图5.19中。上述7个设计步骤的目的是,开发出软件的整体表示。也就是说,一旦确定了软件结构就可以把它作为一个整体来复查,从而能够评价和精化软件结构。在这个时期进行修改只需要很少的附加工作,但是却能够对软件的质量特别是软件的可维护性产生深远的影响
5.6.3 设计优化
软件设计人员应该致力于开发能够满足所有功能和性能要求,而且按照设计原理和启发式设计规则衡量是值得接收的软件。应该在设计的早期阶段尽量对软件结构进行精化。可以导出不同的软件结构,然后对它们进行评价和比较,力求得到“最好”的结果。
结构简单通常既表示设计风格优雅,又表明效率高。设计优化应该力求做到在有效的模块化的前提下使用最少量的模块,以及在能够满足信息要求的前提下使用最简单的数据结构。
(1) 在不考虑时间因素的前提下开发并精化软件结构;
(2) 在详细设计阶段选出最耗费时间的那些模块,仔细地设计它们的处理过程(算法),以求提高效率;
(3) 使用高级程序设计语言编写程序;
(4) 在软件中孤立出那些大量占用处理机资源的模块;
(5) 必要时重新设计或用依赖于机器的语言重写上述大量占用资源的模块的代码,以求提高效率。上述优化方法遵守了一句格言:“先使它能工作,然后再使它快起来。
第六章 详细设计
6.1详细设计概述
6.1.1目标
- 确定怎样具体的实现所要求的系统
- 逻辑上正确的实现每个模块的功能,设计出的处理过程应该尽可能简明易懂。
6.1.2工作任务
不是具体的编写程序,而是设计程序蓝图
6.1.3关键技术
📌结构程序设计技术 是详细设计的关键技术
6.2结构程序设计
6.2.1控制结构
1.基本控制结构
📌三种基本控制结构是:顺序,选择,循环(重复)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-68uvViut-1691070364394)(image/image_wS9-XejlNZ.png)]
2.扩展的控制结构
do until和do case
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rVD2MVbn-1691070364394)(image/image_2paxl6ZYJv.png)]
3.修正的控制结构
leave和break
6.2.2定义
-
经典定义
📌如何一个程序的代码块仅仅通过顺序,选择,循环这三种基本控制结构进行连接,并且每个代码块只有一个入口和一个出口,则称这个程序是结构化的
-
改进的定义
结构程序设计是尽可能少用GO TO语句的程序设计方法。最好仅在检测出错误时才使用GO TO语句,而且应该总是使用前向GO TO语句。
6.3人机界面设计
6.3.1重要性
人机界面设计是接口设计的一个重要组成部分。对于交互式系统来说,人机界面设计和数据设计,体系结构设计及过程设计一样重要
一个人机交互的计算机系统,通常必须考虑三个元素:交互设备,交互软件,以及人的因素。
6.3.2设计问题
在设计人机界面的过程中,几乎总会遇到下述4个问题:系统响应时间、用户帮助设施、出错信息处理和命令交互。
-
系统响应时间
系统响应时间指从用户完成某个控制动作(例如,按回车键或点击鼠标),到软件给出预期的响应(输出信息或做动作)之间的这段时间。
系统响应时间有两个重要属性,分别是长度和易变性。
如果系统响应时间过长,用户就会感到紧张和沮丧。但是,当用户工作速度是由人机界面决定的时候,系统响应时间过短也不好,这会迫使用户加快操作节奏,从而可能会犯错误。易变性指系统响应时间相对于平均响应时间的偏差,在许多情况下,这是系统响应时间的更重要的属性。 即使系统响应时间较长,响应时间易变性低也有助于用户建立起稳定的工作节奏。例如,稳定在1秒的响应时间比从0.1秒到2.5秒变化的响应时间要好。
-
用户帮助设施
几乎交互式系统的每个用户都需要帮助,当遇到复杂问题时甚至需要查看用户手册以寻找答案。大多数现代软件都提供联机帮助设施,这使得用户无须离开用户界面就能解决自己的问题。
常见的帮助设施可分为集成的和附加的两类
集成的帮助设施从一开始就设计在软件里面,通常对用户工作内容是敏感的,因此用户可以从与刚刚完成的操作有关的主题中选择一个请求帮助。可以缩短用户获得帮助的时间,增加界面的友好性。
附加的帮助设施是在系统建成后再添加到软件中的,在多数情况下它实际上是一种查询能力有限的联机用户手册。集成的帮助设施优于附加的帮助设施。
具体设计帮助设施时,必须解决的问题:
(1) 在用户与系统交互期间,是否在任何时候都能获得关于系统任何功能的帮助信息?有两种选择:提供部分功能的帮助信息和提供全部功能的帮助信息。
(2) 用户怎样请求帮助?有3种选择:帮助菜单,特殊功能键和HELP命令。
(3) 怎样显示帮助信息?有3种选择:在独立的窗口中,指出参考某个文档和在屏幕固定位置显示简短提示。
(4) 用户怎样返回到正常的交互方式中?有两种选择:屏幕上的返回按钮和功能键。
(5) 怎样组织帮助信息?有3种选择:平面结构,信息的层次结构和超文本结构。 -
出错信息处理
定义:
出错信息和警告信息,是出现问题时交互式系统给出的“坏消息”。出错信息设计得不好,将向用户提供无用的甚至误导的信息,反而会加重用户的挫折感
属性:
(1) 信息应该用用户可以理解的术语描述问题。
(2) 信息应该提供有助于从错误中恢复的建设性意见。
(3) 信息应该指出错误可能导致哪些负面后果(例如,破坏数据文件),以便用户检查是否出现了这些问题,并在确实出现问题时及时解决。
(4) 信息应该伴随着听觉上或视觉上的提示,例如,在显示信息时同时发出警告铃声,或者信息用闪烁方式显示,或者信息用明显表示出错的颜色显示。
(5) 信息不能带有指责色彩,也就是说,不能责怪用户。当确实出现了问题的时候,有效的出错信息能提高
交互式系统的质量,减轻用户的挫折感。 -
命令交互
现在,面向窗口的、点击和拾取方式的界面已经减少了用户对命令行的依赖,但是,许多高级用户仍然偏爱面向命令行的交互方式。在多数情况下,用户既可以从菜单中选择软件功能,也可以通过键盘命令序列调用软件功能。在提供命令交互方式时,必须考虑下列设计问题。
(1) 是否每个菜单选项都有对应的命令?
(2) 采用何种命令形式?有3种选择:控制序列(例如,Ctrl+P),功能键和键入命令。
(3) 学习和记忆命令的难度有多大?忘记了命令怎么办?
(4) 用户是否可以定制或缩写命令?
在越来越多的应用软件中,人机界面设计者都提供了“命令宏机制”,利用这种机制用户可以用自己定义的名字代表一个常用的命令序列。需要使用这个命令序列时,用户无须依次键入每个命令,只需输入命令宏的名字就可以顺序执行它所代表的全部命令。在理想的情况下,所有应用软件都有一致的命令使用方法。如果在一个应用软件中命令Ctrl+D表示复制一个图形对象,而在另一个应用软件中Ctrl+D命令的含义是删除一个图形对象,显然会使用户感到困惑,并且往往会导致用错命令。
6.3.3设计过程
用户界面设计是一个迭代的过程,也就是说,通常先创建设计模型,再用原型实现这个设计模型,并由用户试用和评估,然后根据用户意见进行修改。
6.3.4人机界面设计指南
用户界面设计主要依靠设计者的经验。总结众多设计者的经验得出的设计指南,有助于设计者设计出友好、高效的人机界面。下面介绍3类人机界面设计指南。
-
一般交互指南
一般交互指南涉及信息显示、数据输入和系统整体控制,因此,这类指南是全局性的。
(1) 保持一致性。应该为人机界面中的菜单选择、命令输入、数据显示以及众多的其他功能,使用一致的格式。
(2) 提供有意义的反馈。应向用户提供视觉的和听觉的反馈,以保证在用户和系统之间建立双向通信。
(3) 在执行有较大破坏性的动作之前要求用户确认。如果用户要删除一个文件,或覆盖一些重要信息,或终止一个程序的运行,应该给出“您是否确实要……”的信息,以请求用户确认他的命令。
(4) 允许取消绝大多数操作。UNDO或REVERSE功能曾经使众多用户避免了大量时间浪费。每个交互式系统都应该能方便地取消已完成的操作。
(5) 减少在两次操作之间必须记忆的信息量。不应该期望用户能记住在下一步操作中需使用的一大串数字或标识符。应该尽量减少记忆量。
(6) 提高对话、移动和思考的效率。应该尽量减少用户击键的次数,设计屏幕布局时应该考虑尽量减少鼠标移动的距离。
(7) 允许犯错误。系统应该能保护自己不受严重错误的破坏。
(8) 按功能对动作分类,并据此设计屏幕布局。下拉菜单的一个主要优点就是能按动作类型组织命令。
(9) 提供对用户工作内容敏感的帮助设施。
(10) 用简单动词或动词短语作为命令名。过长的命令名难于识别和记忆,也会占用过多的菜单空间。
-
信息显示指南
如果人机界面显示的信息是不完整的、含糊的或难于理解的,则该应用系统显然不能满足用户的需求。下面是关于信息显示的设计指南。
(1) 只显示与当前工作内容有关的信息。用户在获得有关系统的特定功能的信息时,不必看到与之无 关的数据、菜单和图形。
(2) 不要用数据淹没用户,应该用便于用户迅速吸取信息的方式来表示数据。例如,可以用图形或图表来取代庞大的表格。
(3) 使用一致的标记、标准的缩写和可预知的颜色。显示的含义应该非常明确,用户无须参照其他信息源就能理解。
(4) 允许用户保持可视化的语境。如果对所显示的图形进行缩放,原始的图像应该一直显示着(以缩小的形式放在显示屏的一角),以使用户知道当前看到的图像部分在原图中所处的相对位置。
(5) 产生有意义的出错信息。
(6) 使用大小写、缩进和文本分组以帮助理解。人机界面显示的信息大部分是文字,文字的布局和形式对用户从中提取信息的难易程度有很大影响。
(7) 使用窗口分隔不同类型的信息。利用窗口用户能够方便地“保存”多种不同类型的信息。
(8) 使用“模拟”显示方式表示信息,以使信息更容易被用户提取。
(9) 高效率地使用显示屏。当使用多窗口时,应该有足够的空间使得每个窗口至少都能显示出一部分。此外,屏幕大小应该选得和应用系统的类型相配套。
-
数据输入指南
用户的大部分时间用在选择命令、键入数据和向系统提供输入。下面是关于数据输入的设计指南。
(1) 尽量减少用户的输入动作。最重要的是减少击键次数,这可以用下列方法实现:用鼠标从预定义的一组输入中选一个;用“滑动标尺”在给定的值域中指定输入值;利用宏把一次击键转变成更复杂的输入数据合。
(2) 保持信息显示和数据输入之间的一致性。显示的视觉特征应该与输入域一致。
(3) 允许用户自定义输入。
(4) 交互应该是灵活的,并且可调整成用户最喜欢的输入方式。用户类型与喜好的输入方式有关。
(5) 使在当前动作语境中不适用的命令不起作用。这可使得用户不去做那些肯定会导致错误的动作。
(6) 让用户控制交互流。用户应该能够跳过不必要的动作,改变所需做的动作的顺序(在应用环境允许的前提下),以及在不退出程序的情况下从错误状态中恢复正常。
(7) 尽量对所有输入动作都提供帮助。
(8) 消除冗余的输入。尽可能提供默认值;不要要求用户提供程序可以自动获得或计算出来的信息。
6.4过程设计工具
6.4.1定义
描述程序处理过程的工具
6.4.2分类
-
程序流程图
程序流程图又称为程序框图,它是历史最悠久、使用最广泛的描述过程设计的方法,然而它也是用得最混乱的一种方法。程序流程图一直是软件设计的主要工具。它的主要优点是对控制流程的描绘很直观,便于初学者掌握。至今仍在广泛使用着。
程序流程图的主要缺点如下:
(1) 程序流程图本质上不是逐步求精的好工具,它诱使程序员过早地考虑程序的控制流程,而不去考虑程序的全局结构。
(2) 程序流程图中用箭头代表控制流,因此程序员不受任何约束,可以完全不顾结构程序设计的精神,随意转移控制。
(3) 程序流程图不易表示数据结构[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4EOE0pdS-1691070364394)(image/image_DkeDq6-Qsh.png)]
-
盒图(N-S图)
Nassi和Shneiderman提出了盒图,又称为N-S图。它有下述特点:
(1) 功能域(即,一个特定控制结构的作用域)明确,可以从盒图上一眼就看出来。
(2)** 不可能任意转移控制。**
(3) 很容易确定局部和全程数据的作用域。
(4) 很容易表现嵌套关系,也可以表示模块的层次结构。图6.4给出了结构化控制结构的盒图表示,也给出了调用子程序的盒图表示方法。
盒图没有箭头,因此不允许随意转移控制。 坚持使用盒图作为详细设计的工具,可以使程序员逐步养成用结构化的方式思考问题和解决问题的习惯 -
PAD图
PAD是问题分析图(problem analysis diagram)的英文缩写,1973年由日立公司发明。它用二维树形结构的图来表示程序的控制流,将这种图翻译成程序代码比较容易。图6.5给出PAD图的基本符号。
PAD图的主要优点如下:
(1) 使用表示结构化控制结构的PAD符号所设计出来的程序必然是结构化程序。
(2) PAD图所描绘的程序结构十分清晰。图中最左面的竖线是程序的主线,即第一层结构。随着程序层次的增加,PAD图逐渐向右延伸,每增加一个层次,图形向右扩展一条竖线。PAD图中竖线的总条数就是程序的层次数。
(3) 用PAD图表现程序逻辑,易读、易懂、易记。
(4) 容易将PAD图转换成高级语言源程序。(5 ) 既可用于表示程序逻辑,也可用于描绘数据结构。
(6)** PAD图的符号支持自顶向下、逐步求精方法的使用。** PAD图是面向高级程序设计语言的,FORTRAN,COBOL和PASCAL等每种常用的高级程序设计语言都提供了一整套相应的图形符号。 -
判定表(判定树)
当算法中包含多重嵌套的条件选择时,用判定表却能够清晰地表示复杂的条件组合与应做的动作之间的对应关系。
一张判定表由4部分组成,左上部列出所有条件,左下部是所有可能做的动作,右上部是表示各种条件组合的一个矩阵,右下部是和每种条件组合相对应的动作。判定表右半部的每一列实质上是一条规则,规定了与特定的条件组合相对应的动作。
下面以行李托运费的算法为例说明判定表的组织方法。假设某航空公司规定,乘客可以免费托运重量不超过30kg的行李。当行李重量超过30kg时,对头等舱的国内乘客超重部分每公斤收费4元,对其他舱的国内乘客超重部分每公斤收费6元,对外国乘客超重部分每公斤收费比国内乘客多一倍,对残疾乘客超重部分每公斤收费比正常乘客少一半。用判定表可以清楚地表示与上述每种条件组合相对应的计算行李费的算法,如表6.1所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xXTHeHly-1691070364395)(image/image_GSPnNmRF4V.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b5Rodrad-1691070364395)(image/image__LGlf9x8Xx.png)]
-
过程设计语言
定义:
过程设计语言(PDL) 也称为伪码,是用正文形式表示数据和处理过程的设计工具。PDL具有严格的关键字外部语法,用于定义控制结构和数据结构;另一方面,PDL表示实际操作和条件的内部语法通常又是灵活自由的,可以适应各种工程项目的需要。
特点:
(1) 关键字的固定语法,它提供了结构化控制结构、数据说明和模块化的特点。为了使结构清晰和可读性好,通常在所有可能嵌套使用的控制结构的头和尾都有关键字,例如,if…endif等等。
(2) 自然语言的自由语法,它描述处理特点。
(3) 数据说明的手段。应该既包括简单的数据结构(例如纯量和数组),又包括复杂的数据结构(例如,链表或层次的数据结构)。
(4) 模块定义和调用的技术,应该提供各种接口描述模式。优点:
(1) 可以作为注释直接插在源程序中间。这样做能促使维护人员在修改程序代码的同时也相应地修改PDL注释,因此有助于保持文档和程序的一致性,提高了文档的质量。
(2) 可以使用普通的正文编辑程序或文字处理系统,很方便地完成PDL的书写和编辑工作。
(3) 已经有自动处理程序存在,而且可以自动由PDL生成程序代码。缺点: 不如图形工具形象直观,描述复杂的条件组合与动作间的对应关系时,不如判定表清晰简单。
6.5面向数据结构的设计方法
6.5.1 相关概念
目的:面向数据结构的设计方法的最终目标是得出对程序处理过程的描述。
适用性:适合于在详细设计阶段使用,也就是说,在完成了软件结构设计之后,可以使用面向数据结构的方法来设计每个模块的处理过程。
Jackson方法和Warnier方法是最著名的两个面向数据结构的设计方法。使用面向数据结构的设计方法,当然首先需要分析确定数据结构,并且用适当的工具清晰地描绘数据结构。
6.5.2 jackson图
虽然程序中实际使用的数据结构种类繁多,但是它们的数据元素彼此间的逻辑关系却只有顺序、选择和重复3类,因此,逻辑数据结构也只有这3类。
-
顺序结构
顺序结构的数据由一个或多个数据元素组成,每个元素按确定次序出现一次。图6.8是表示顺序结构的Jackson图的一个例子。
-
选择结构
选择结构的数据包含两个或多个数据元素,每次使用这个数据时按一定条件从这些数据元素中选择一个。图6.9是表示3个中选1个结构的Jackson图
-
重复结构
重复结构的数据,根据使用时的条件由一个数据元素出现零次或多次构成。图6.10是表示重复结构的Jackson图。
Jackson图有下述优点:
便于表示层次结构,而且是对结构进行自顶向下分解的有力工具;形象直观可读性好;既能表示数据结构也能表示程序结构(因为结构程序设计也只使用上述3种基本控制结构)。
Jackson图实质上是对第3.7节中介绍的层次方框图的一种精化。虽然Jackson图和描绘软件结构的层次图形式相当类似,但是含义却很不相同,即,层次图中的一个方框通常代表一个模块;而Jackson图即使在描绘程序结构时,一个方框也并不代表一个模块,通常一个方框只代表几个语句。层次图表现的是调用关系,通常一个模块除了调用下级模块外,还完成其他操作;而Jackson图表现的是组成关系,也就是说,一个方框中包括的操作仅仅由它下层框中的那些操作组成
6.5.3 Jackson方法
Jackson结构程序设计方法基本上由下述5个步骤组成:
(1) 分析并确定输入数据和输出数据的逻辑结构,并用Jackson图描绘这些数据结构。
(2)** 找出输入数据结构和输出数据结构中有对应关系的数据单元**。所谓有对应关系是指有直接的因果关系,在程序中可以同时处理的数据单元(对于重复出现的数据单元必须重复的次序和次数都相同才可能有对应关系)。
(3) 用下述3条规则从描绘数据结构的Jackson图导出描绘程序结构的Jackson图:
1为每对有对应关系的数据单元,按照它们在数据结构图中的层次在程序结构图的相应层次画一个处理框(注意,如果这对数据单元在输入数据结构和输出数据结构中所处的层次不同,则和它们对应的处理框在程序结构图中所处的层次与它们之中在数据结构图中层次低的那个对应);
根据输入数据结构中剩余的每个数据单元所处的层次,在程序结构图的相应层次分别为它们画上对应的处理框;
根据输出数据结构中剩余的每个数据单元所处的层次,在程序结构图的相应层次分别为它们画上对应的处理框。总之,描绘程序结构的Jackson图应该综合输入数据结构和输出数据结构的层次关系而导出来。在导出程序结构图的过程中,由于改进的Jackson图规定在构成顺序结构的元素中不能有重复出现或选择出现的元素,因此可能需要增加中间层次的处理框。
(4) 列出所有操作和条件(包括分支条件和循环结束条件),并且把它们分配到程序结构图的适当位置。
(5) 用伪码表示程序。Jackson方法中使用的伪码和Jackson图是完全对应的,下面是和3种基本结构对应的伪码。
6.6程序复杂程度的定量度量
6.6.1价值
6.6.2 McCabe方法
-
流图
McCabe方法根据程序控制流的复杂程度定量度量程序的复杂程度,这样度量出的结果称为程序的环形复杂度。为了突出表示程序的控制流,人们通常使用流图(也称为程序图)。所谓流图实质上是“退化了的”程序流程图,它仅仅描绘程序的控制流程,完全不表现对数据的具体操作以及分支或循环的具体条件。在流图中用圆表示结点,一个圆代表一条或多条语句。程序流程图中的一个顺序的处理框序列和一个菱形判定框,可以映射成流图中的一个结点。流图中的箭头线称为边,它和程序流程图中的箭头线类似,代表控制流。在流图中一条边必须终止于一个结点,即使这个结点并不代表任何语句(实际上相当于一个空语句)。由边和结点围成的面积称为区域,当计算区域数时应该包括图外部未被围起来的那个区域。图6.15举例说明把程序流程图映射成流图的方法。用任何方法表示的过程设计结果,都可以翻译成流图。图6.16是用PDL表示的处理过程及与之对应的流图。当过程设计中包含复合条件时,生成流图的方法稍微复杂一些。所谓复合条件,就是在条件中包含了一个或多个布尔运算符(逻辑OR,AND,NAND,NOR)。在这种情况下,应该把复合条件分解为若干个简单条件,每个简单条件对应流图中一个结点。包含条件的结点称为判定节点,从每个判定结点引出两条或多条边。图6.17是由包含复合条件的PDL片断翻译成的流图。
-
计算环形复杂度的方法
环形复杂度定量度量程序的逻辑复杂度。有了描绘程序控制流的流图之后,可以用下述3种方法中的任何一种来计算环形复杂度。
(1) 流图中的区域数等于环形复杂度。
(2) 流图G的环形复杂度V(G)=E-N+2,其中,E是流图中边的条数,N是结点数。
(3) 流图G的环形复杂度V(G)=P+1,其中,P是流图中判定结点的数目。 -
环形复杂度的用途
程序的环形复杂度取决于程序控制流的复杂程度,也即是取决于程序结构的复杂程度。当程序内分支数或循环个数增加时,环形复杂度也随之增加,因此它是对测试难度的一种定量度量,也能对软件最终的可靠性给出某种预测。McCabe研究大量程序后发现,环形复杂度高的程序往往是最困难、最容易出问题的程序。实践表明,模块规模以V(G)≤10为宜,也就是说,V(G)=10是模块规模的一个更科学更精确的上限
6.6.3 Halstead方法
Halstead方法是另一个著名的方法,它根据程序中运算符和操作数的总数来度量程序的复杂程度。令N1为程序中运算符出现的总次数,N2为操作数出现的总次数,程序长度N定义为:N=N1+N2详细设计完成之后,可以知道程序中使用的不同运算符(包括关键字)的个数n1,以及不同操作数(变量和常数)的个数n2。Halstead给出预测程序长度的公式如下:H=n1 log2 n1+n2 log2 n2 多次验证都表明,预测的长度H与实际长度N非常接近。
Halstead还给出了预测程序中包含错误的个数的公式如下:E=N log2(n1+n2)/3000
有人曾对从300条到12000条语句范围内的程序核实了上述公式,发现预测的错误数与实际错误数相比误差在8%之内。
第七章 实现
7.1编码
7.1.1 定义
编码是把软件设计的结果翻译成程序设计语言书写的程序,是对软件设计进一步具体化
7.1.2选择程序设计语言
-
重要性:
会影响人的思维和解题方式,
会影响人和计算机通信的方式和质量,
会影响其他人阅读和理解程序的难易程度
-
适宜的程序设计语言优点:
能使根据设计去完成编码时困难最少。
可以减少需要的程序测试量。
出更容易阅读和更容易维护的程序。
-
选择程序语言的理想标准
选用高级语言
选用的高级语言应该有理想的模块化机制,以及可读性好的控制结构和数据结构
选用语言特点应该使编译程序能够尽可能多的发现程序中的错误
选用的高级语言应该有良好的独立编译机制
-
选择程序语言的实用标准
(1) 系统用户的要求。如果所开发的系统由用户负责维护,用户通常要求用他们熟悉的语言书写程序。
(2) 可以使用的编译程序。运行目标系统的环境中可以提供的编译程序往往限制了可以选用的语言的范围。
(3) 可以得到的软件工具。如果某种语言有支持程序开发的软件工具可以利用,则目标系统的实现和验证都变得比较容易。
(4) 工程规模。如果工程规模很庞大,现有的语言又不完全适用,那么设计并实现一种供这个工程项目专用的程序设计语言,可能是一个正确的选择。
(5) 程序员的知识。如果和其他标准不矛盾,那么应该选择一种已经为程序员所熟悉的语言。
(6) 软件可移植性要求。如果目标系统将在几台不同的计算机上运行,或者预期的使用寿命很长,那么选择一种标准化程度高、程序可移植性好的语言就是很重要的。
(7) 软件的应用领域。所谓的通用程序设计语言实际上并不是对所有应用领域都同样适用。因此,选择语言时应该充分考虑目标系统的应用范围。
7.1.3编码风格
源程序代码的逻辑简明清晰、易读易懂是好程序的一个重要标准,为了做到这一点,应该遵循下述规则。
-
程序内部的文档
所谓程序内部的文档包括恰当的标识符、适当的注解和程序的视觉组织等等。
选取含义鲜明的名字,使它能正确地提示程序对象所代表的实体,这对于帮助阅读者理解程序是很重要的。如果使用缩写,那么缩写规则应该一致,并且应该给每个名字加注解。
注解是程序员和程序读者通信的重要手段,正确的注解非常有助于对程序的理解。通常在每个模块开始处有一段序言性的注解,简要描述模块的功能、主要算法、接口特点、重要数据及开发简史。
插在程序中间与一段程序代码有关的注解,主要解释包含这段代码的必要性。用空格或空行清楚地区分注解和程序。注解的内容一定要正确。程序清单的布局对于程序的可读性也有很大影响,应该利用适当的形式使程序的层次结构清晰明显。
-
数据说明
数据说明的次序应该标准化。有次序就容易查阅,因此能够加速测试、调试和维护的过程。当多个变量名在一个语句中说明时,按字母顺序排列这些变量。
如果设计时使用了一个复杂的数据结构,则应该用注解说明用程序设计语言实现这个数据结构的方法和特点。
-
语句构造
构造语句应该遵循的原则是,每个语句都应该简单而直接, 不能为了提高效率而使程序变得过分复杂。
下述规则有助于使语句简单明了:
不要为了节省空间而把多个语句写在同一行;
尽量避免复杂的条件测试;
尽量减少对“非”条件的测试;
避免大量使用循环嵌套和条件嵌套;
利用括号使逻辑表达式或算术表达式的运算次序清晰直观。
-
输入输出
在设计和编写程序时应该考虑下述有关输入输出风格的规则:
对所有输入数据都进行检验;检查输入项重要组合的合法性;保持输入格式简单;明确提示交互式输入的请求,详细说明可用的选择或边界数值;当程序设计语言对格式有严格要求时,应保持输入格式一致;设计良好的输出报表;给所有输出数据加标志。
-
效率
效率主要指处理机时间和存储器容量两个方面。程序的效率和程序的简单程度是一致的,不要牺牲程序的清晰性和可读性来不必要地提高效率。下面从三个方面进一步讨论效率问题。
(1) 程序运行时间
源程序的效率直接由详细设计阶段确定的算法的效率决定,但是,写程序的风格也能对程序的执行速
度和存储器要求产生影响。在把详细设计结果翻译成程序时,总可以应用下述规则:写程序之前先简化算术的和逻辑的表达式;研究嵌套的循环,以确定是否有语句可以从内层往外移;尽量避免使用多维数组;尽量避免使用指针和复杂的表;使用执行时间短的算术运算;不要混合使用不同的数据类型;尽量使用整数运算和布尔表达式。
(2) 存储器效率
在大型计算机中必须考虑操作系统页式调度的特点。如果要求使用最少的存储单元,则应选用有紧缩存
储器特性的编译程序,必要时可以使用汇编语言。提高执行效率的技术通常也能提高存储器效率。
(3) 输入输出的效率
简单清晰是提高人机通信效率的关键。所有输入输出都应该有缓冲,以减少用于通信的额外开销;对二级存储器(如磁盘)应选用最简单的访问方法;二级存储器的输入输出应该以信息组为单位进行;如果“超高效的”输入输出很难被人理解,则不应采用这种方法。这些简单原则对于软件工程的设计和编码两个阶段都适用
7.2软件测试基础
7.2.1软件测试目标
📌软件测试是为了发现程序中的错误而运行程序的过程。
📌一个好的测试方案是很可能发现迄今为止还没发现的错误的测试方案。
📌一个成功的测试是发现了迄今为止还没发现的错误的测试。
7.2.2软件测试准则
(1) 所有测试都应该能追溯到用户需求。正如上一小节讲过的,软件测试的目标是发现错误。从用户的角度看,最严重的错误是导致程序不能满足用户需求的那些错误。
(2) 应该远在测试开始之前就制定出测试计划。实际上,一旦完成了需求模型就可以着手制定测试计划,在建立了设计模型之后就可以立即开始设计详细的测试方案。因此,在编码之前就可以对所有测试工作进行计划和设计。
(3) 把二八原则应用到软件测试中。pareto原理:测试中发现的错误中的80%很可能是由程序中20%的模块造成的。
(4) 应该从“小规模”测试开始,并逐步进行“大规模”测试。通常,首先重点测试单个程序模块,然后把测试重点转向在集成的模块簇中寻找错误,最后在整个系统中寻找错误。
(5) 穷举测试是不可能的。所谓穷举测试就是把程序所有可能的执行路径都检查一遍的测试。但是,精心地设计测试方案,有可能充分覆盖程序逻辑并使程序达到所要求的可靠性。
(6) 为了达到最佳的测试效果,应该由独立的第三方从事测试工作。
7.2.3测试方法
📌黑盒测试:完全忽略程序的内部处理过程,只检查程序的功能是否按照规格说明书正常运行
📌白盒测试:知道程序的内部结构和处理过程,检查程序的内部处理过程是否按照规格说明书的规定正常运行
7.2.4测试步骤
-
模块测试
模块测试通常又称为单元测试。在设计得好的软件系统中,每个模块完成一个清晰定义的子功能。因此,把每个模块作为一个单独的实体来测试,容易设计检验模块正确性的测试方案。模块测试的目的是保证每个模块作为一个单元能正确运行,在这个测试步骤中所发现的往往是编码和详细设计的错误。
-
子系统测试
子系统测试是把经过单元测试的模块放在一起形成一个子系统来测试。模块相互间的协调和通信是这个测试过程中的主要问题,因此,这个步骤着重测试模块的接口。
-
系统测试
系统测试是把经过测试的子系统装配成一个完整的系统来测试。在这个过程中不仅应该发现设计和编码的错误,还验证系统确实能提供需求说明书中指定的功能。在这个测试步骤中发现的往往是软件设计中的错误,也可能发现需求说明中的错误。不论是子系统测试还是系统测试,都兼有检测和组装两重含义,通常称为集成测试。
-
验收测试
验收测试也称为确认测试。
验收测试把软件系统作为单一的实体进行测试,测试内容与系统测试基本类似,但是它是在用户参与下进行的,而且可能主要使用实际数据进行测试。验收测试的目的是验证系统确实能够满足用户的需要,在这个测试步骤中发现的往往是系统需求说明书中的错误。
-
平行运行
关系重大的软件产品在验收之后往往并不立即投入生产性运行,而是要再经过一段运行时间的考验。所谓平行运行就是同时运行新开发出来的系统和旧系统,以便比较新旧两个系统的处理结果。这样做的具体目的有如下几点:
(1) 可以在准生产环境中运行新系统而又不冒风险;
(2) 用户能有一段熟悉新系统的时间;
(3) 可以验证用户指南和使用手册之类的文档;
(4) 能够以准生产模式对新系统进行全负荷测试,可以用测试结果验证性能指标。
7.2.5测试阶段信息流
图7.1描绘了测试阶段的信息流,这个阶段的输入信息有两类(1)软件配置,包括需求说明书、设计说明书和源程序清单等;(2)测试配置,包括测试计划和测试方案。所谓测试方案不仅仅是测试时使用的输入数据(称为测试用例),还应该包括每组输入数据预定要检验的功能,以及每组输入数据预期应该得到的正确输出。实际上测试配置是软件配置的一个子集,最终交出的软件配置应该包括上述测试配置以及测试的实际结果和调试的记录。
比较测试得出的实际结果和预期的结果,如果两者不一致则很可能是程序中有错误。设法确定错误的准确位置并且改正它,这就是调试的任务。与测试不同,通常由程序的编写者负责调试。
7.3单元测试
单元测试集中检测软件设计的最小单元——模块。在编写出源程序代码并通过语法检查之后,就可以用详细设计作指南,对重要的执行通路进行测试,以便发现模块内部的错误。单元测试主要使用白盒测试技术,而且对多个模块的测试可以并行地进行。
7.3.1测试重点
-
模块接口
首先应该对通过模块接口的数据流进行测试。在对模块接口进行测试时主要检查下述几个方面:参数的数目、次序、属性或单位系统与变元是否一致;是否修改了只作输入用的变元;全局变量的定义和用法在各个模块中是否一致。
-
局部数据结构
对于模块来说,局部数据结构是常见的错误来源。应该仔细设计测试方案,以便发现局部数据说明、初始化、默认值等方面的错误。
-
重要的执行通路
在单元测试期间选择最有代表性、最可能发现错误的执行通路进行测试是十分关键的。应该设计测试方案用来发现由于错误的计算、不正确的比较或不适当的控制流而造成的错误。
-
出错处理通路
好的设计应该能预见出现错误的条件,并且设置适当的处理错误的通路,以便在真的出现错误时执行相应的出错处理通路或干净地结束处理。当评价出错处理通路时,应该着重测试下述一些可能发生的错误:
(1) 对错误的描述是难以理解的;
(2) 记下的错误与实际遇到的错误不同;
(3) 在对错误进行处理之前,错误条件已经引起系统干预;
(4) 对错误的处理不正确;
(5) 描述错误的信息不足以帮助确定造成错误的位置。 -
边界条件
边界测试是单元测试中最后的也可能是最重要的任务。软件常常在它的边界上失效,例如,处理n元数组的第n个元素时,或做到i次循环中的第i次重复时,往往会发生错误。使用刚好小于、刚好等于和刚好大于最大值或最小值的数据结构、控制量和数据值的测试方案,非常可能发现软件中的错误。
7.3.2测试方法
-
代码审查
人工测试源程序可以由编写者本人非正式地进行,也可以由审查小组正式进行。后者称为代码审查,它是一种非常有效的程序验证技术,对于典型的程序来说,可以查出30%~70%的逻辑设计错误和编码错误。审查小组最好由下述4人组成:(1) 组长,应该是一个很有能力的程序员,而且没有直接参与这项工程;(2) 程序的设计者;(3) 程序的编写者;(4) 程序的测试者。
如果一个人既是程序的设计者又是编写者,或既是编写者又是测试者,则审查小组中应该再增加一个程序员。审查之前,小组成员应该先研究设计说明书,力求理解这个设计。可以先由设计者扼要地介绍他的设计,其他成员倾听他的讲解,并力图发现其中的错误。审查会上进行的另外一项工作,是对照类似于上一小节中介绍的程序设计常见错误清单,分析审查这个程序。当发现错误时由组长记录下来,审查会继续进行(审查小组的任务是发现错误而不是改正错误)。审查会还有另外一种常见的进行方法,称为预排:由一个人扮演“测试者”,其他人扮演“计算机”。会前测试者准备好测试方案,会上由扮演计算机的成员模拟计算机执行被测试的程序。在大多数情况下,通过向程序员提出关于他的程序的逻辑和他编写程序时所做的假设的疑问,可以发现的错误比由测试方案直接发现的错误还多。代码审查比计算机测试优越的是:一次审查会上可以发现许多错误;用计算机测试的方法发现错误之后,通常需要先改正这个错误才能继续测试,因此错误是一个一个地发现并改正的。也就是说,采用代码审查的方法可以减少系统验证的总工作量。实践表明,对于查找某些类型的错误来说,人工测试比计算机测试更有效;对于其他类型的错误来说则刚好相反。因此,人工测试和计算机测试是互相补充,相辅相成的,缺少其中任何一种方法都会使查找错误的效率降低
-
计算机测试
方法:
模块并不是一个独立的程序,因此必须为每个单元测试开发驱动软件和(或)存根软件。
📌驱动程序:
接收测试数据,把这些数据传送给被测试的模块,并且印出有关的结果。
📌存根程序:代替被测试的模块所调用的模块,它使用被它代替的模块的接口,可能做最少量的数据操作,印出对入口的检验或操作结果,并且把控制归还给调用它的模块。
7.4集成测试
集成测试是测试和组装软件的系统化技术,由模块组装成程序时有两种方法。
7.4.1非渐增测试方法
📌分别测试每个模块,再把所有模块按设计要求放在一起结合成所要的程序
缺点:
- 所有模块放在一起,测试者面对的情况比较复杂.
- 在庞大的程序中诊断定位一个错误非常困难
- 一旦改正一个错误之后,又会遇到新的错误
7.4.2渐增测试方法
📌把下一个要测试的模块同已经测试好的那些模块结合起来进行测试,测试完以后再把下一个应该测试的模块结合进来测试。
优点:
- 把程序划分成小段来构造和测试,比较容易定位和改正错误。
- 对接口可以进行更彻底的测试
- 可以使用系统化的测试方法
分为自顶向下集成和自底向上集成
自顶向下:
从主控制模块开始,沿着程序的控制层次向下移动,逐渐把各个模块结合起来。在把附属于主控制模块的那些模块组装到程序结构中去时,或者使用深度优先的策略,或者使用宽度优先的策略。
步骤:
- 对主控制模块进行测试,测试时用存根程序代替直接附属于主控制模块的模块
- 根据选定的策略(深度优先,广度优先),每次用一个实际模块替换存根程序。
- 在结合进一个新模块的同时进行测试
- 为了保证加入的模块没有引进新的错误,通常要进行回归测试
自底向上:
从“原子”模块(即在软件结构最低层的模块)开始组装和测试。因为是从底部向上结合模块,总能得到所需的下层模块处理功能,所以不需要存根程序。
步骤:
- 把底层模块组合成能实现某个特定的软件子功能的族
- 写一个驱动程序,协调测试数据的输入和输出
- 对由模块组成的子功能族进行测试
- 去掉驱动程序。沿软件结构自下而上移动,把子功能族组合成更大的子功能族
7.4.3两种集成策略比较
📌自顶向下集成:
优点:
不需要测试驱动程序
能够在测试阶段的早期实现并验证主要功能
能在早期发现上层模块接口的错误
缺点:
需要存根程序,可能遇到与此相关的测试难点
低层模块中的错误发现较晚
在早期不能充分展开人力
📌自底上集成:
优点:
不需要存根程序
能较早的发现低层模块中的错误
能在早期充分展开人力
缺点:
需要驱动程序,可能遇到与此相关的测试困难
高层模块接口的错误发现较晚
不能在测试阶段早期实现并验证主要功能
7.4.4其他集成策略
一般说来,纯粹自顶向下或纯粹自底向上的策略可能都不实用,人们在实践中创造出许多混合策略:
(1) 改进的自顶向下测试方法。 基本上使用自顶向下的测试方法,但是在早期使用自底向上的方法测试软件中的少数关键模块。一般的自顶向下方法所具有的优点在这种方法中也都有,而且能在测试的早期发现关键模块中的错误;但是,它的缺点也比自顶向下方法多一条,即测试关键模块时需要驱动程序。
(2)混合法。对软件结构中较上层使用的自顶向下方法与对软件结构中较下层使用的自底向上方法相结合。这种方法兼有两种方法的优点和缺点,当被测试的软件中关键模块比较多时,这种混合法可能是最好的折衷方法。
7.4.5回归测试
📌回归测试是指重新执行已经做过的测试的某个子集,用于保证由于调试或其他原因引起的变化,不会导致非预期的软件行为或额外错误的测试活动。
方法:
通过重新执行测试用例的一个子集人工地进行
使用自动化的捕获回放工具
回归测试集(已执行过的测试用例的子集)包括下述3类不同的测试用例:
(1) 检测软件全部功能的代表性测试用例;
(2) 专门针对可能受修改影响的软件功能的附加测试;
(3) 针对被修改过的软件成分的测试。
7.5确认测试
7.5.1概念
📌确认测试也称为验收测试,他的目标是验证软件的有效性。
📌验证:验证指的是保证软件正确地实现了某个特定要求的一系列活动。
📌确认:确认指的是为了保证软件确实满足了用户需求而进行的一系列活动
📌软件有效性:指软件的功能和性能都满足用户的需求,软件就是有效的
7.5.2确认测试的范围
-
要求
确认测试必须有用户积极参与,或者以用户为主进行。用户应该参与设计测试方案,使用用户界面输入测试数据并且分析评价测试的输出结果,在验收之前由开发单位对用户进行培训。
确认测试通常使用黑盒测试法。应该仔细设计测试计划和测试过程,测试计划包括要进行的测试的种类及进度安排,测试过程规定了用来检测软件是否与需求一致的测试方案。通过测试和调试要保证软件能满足所有功能要求,
-
结果
(1) 功能和性能与用户要求一致,软件是可以接受的;
(2) 功能和性能与用户要求有差距。
7.5.3软件配置复查
确认测试的一个重要内容是复查软件配置。复查的目的是保证软件配置的所有成分都齐全,质量符合要求,文档与程序完全一致,具有完成软件维护所必须的细节,而且已经编好目录。
确认测试过程中还应该严格遵循用户指南及其他操作程序,以便检验这些使用手册的完整性和正确性。
必须仔细记录发现的遗漏或错误,并且适当地补充和改正。
7.5.4 Alpha和Beta测试
📌Alpha测试由用户在开发者的场所进行,并且在开发者对用户的“指导”下进行测试。开发者负责记录发现的错误和使用中遇到的问题。总之,Alpha测试是在受控的环境中进行的。
📌Beta测试由软件的最终用户们在一个或多个客户场所进行。与Alpha测试不同,开发者通常不在Beta测试的现场,因此,Beta测试是软件在开发者不能控制的环境中的“真实”应用。
7.6白盒测试
白盒测试
- 逻辑覆盖
- 语句覆盖
- 判定覆盖
- 条件覆盖
- 条件/判定覆盖
- 条件组合覆盖
- 点覆盖
- 边覆盖
- 路径覆盖
- 控制结构测试
- 基本路径测试
- 条件测试
- 循环测试
- 简单循环
- 串接循环
- 嵌套循环
7.6.1逻辑覆盖
-
语句覆盖
选择足够多的测试数据,使被测程序中每个语句至少执行一次。
例如,图7.5所示的程序流程图描绘了一个被测模块的处理算法。为了使每个语句都执行一次,程序的执行路径应该是sacbed,为此只需要输入下面的测试数据(实际上X可以是任意实数):A=2,B=0,X=4
-
判定覆盖
判定覆盖又叫分支覆盖,它的含义是,不仅每个语句必须至少执行一次,而且每个判定的每种可能的结果都应该至少执行一次,也就是每个判定的每个分支都至少执行一次。判定覆盖比语句覆盖强,但是对程序逻辑的覆盖程度仍然不高,例如,上面的测试数据只覆盖了程序全部路径的一半。
判断覆盖路径:sacbed (走了True)和sabd(走了False)
-
条件覆盖
判定表达式中的每个条件都取到各种可能的结果(A>1 ,B=0,A=2,X>1)这四个条件的真和假都要取到
-
条件/判断覆盖
判定表达式中的每个条件都取到各种可能的值,而且每个判定表达式也都取到各种可能的结果
-
条件/组合覆盖
每个判定表达式中条件的各种可能组合都至少出现一次。
-
点覆盖
点覆盖标准和语句覆盖标准是相同的。
-
边覆盖
边覆盖和判定覆盖是一致
-
路径覆盖
路径覆盖的含义是,选取足够多测试数据,使程序的每条可能路径都至少执行一次(如果程序图中有环,则要求每个环至少经过一次)。
7.6.2控制结构测试
-
基本路径测试
基本路径测试是一种白盒测试技术。使用这种技术设计测试用例时,首先计算程序的环形复杂度,并用该复杂度为指南定义执行路径的基本集合,从该基本集合导出的测试用例可以保证程序中的每条语句至少执行一次,而且每个条件在执行时都将分别取真、假两种值。
使用基本路径测试技术设计测试用例的步骤如下:
第一步,根据过程设计结果画出相应的流图。
例如,为了用基本路径测试技术测试下列的用PDL描述的求平均值过程,首先画出图7.6所示的流图。注意,为了正确地画出流图,我们把被映射为流图结点的PDL语句编了序号。
第二步,计算流图的环形复杂度。
环形复杂度定量度量程序的逻辑复杂性。有了描绘程序控制流的流图之后,可以用第6.5.1小节讲述的3种方法之一计算环形复杂度。经计算,图7.6所示流图的环形复杂度为6。
**第三步,确定线性独立路径的基本集合。 **
所谓独立路径是指至少引入程序的一个新处理语句集合或一个新条件的路径,用流图术语描述,独立
路径至少包含一条在定义该路径之前不曾用过的边。使用基本路径测试法设计测试用例时,程序的环形
复杂度决定了程序中独立路径的数量,而且这个数是确保程序中所有语句至少被执行一次所需的测试数量的上界。
对于图7.6所描述的求平均值过程来说,由于环形复杂度为6,因此共有6条独立路径。
通常在设计测试用例时,识别出判定结点是很有必要的。本例中结点2、3、5、6和10是判定结点。
第四步,设计可强制执行基本集合中每条路径的测试用例。 应该选取测试数据使得在测试每条路径时都适当地设置好了各个判定结点的条件。在测试过程中,执行每个测试用例并把实际输出结果与预期结果相比较。一旦执行完所有测试用例,就可以确保程序中所有语句都至少被执行了一次,
而且每个条件都分别取过true值和false值。应该注意,某些独立路径不能以独立的方式测试,也就是说,程序的正常流程不能形成独立执行该路径所需要的数据组合。在这种情况下,这些路径必须作为另一个路径的一部分来测试。
-
条件测试
用条件测试技术设计出的测试用例,能够检查程序模块中包含的逻辑条件。一个简单条件是一个布尔变量或一个关系表达式,在布尔变量或关系表达式之前还可能有一个NOT算符。关系表达式的形式如下:
E1<关系算符>E2
其中,E1和E2是算术表达式,而<关系算符>是下
列算符之一:“<” ,“≤” ,“=”,“≠” “>”或“≥”
复合条件由两个或多个简单条件、布尔算符和括弧组成。布尔算符有OR(“|”),AND(“&”)和NOT。不包含关系表达式的条件称为布尔表达式。因此,条件成分的类型包括布尔算符、布尔变量、布尔括弧(括住简单条件或复合条件)、关系算符及算术表达式。如果条件不正确,则至少条件的一个成分不正确。
因此,条件错误的类型如下:
布尔算符错(布尔算符不正确,遗漏布尔算符或有
多余的布尔算符)
布尔变量错
布尔括弧错
关系算符错
算术表达式错
条件测试方法着重测试程序中的每个条件。
条件测试策略有两个优点: ①容易度量条件的测试覆盖率; ②程序内条件的测试覆盖率可指导附加测试的设计。
条件测试的目的不仅是检测程序条件中的错误,而且是检测程序中的其他错误。
条件测试策略:
分支测试
BRO测试
-
循环测试
循环是绝大多数软件算法的基础,但是,在测试软件时却往往未对循环结构进行足够的测试。循环测试是一种白盒测试技术,它专注于测试循环结构的有效性。在结构化的程序中通常只有3种循环,即简单循环、串接循环和嵌套循环,如图7.7所示。下面分别讨论这3种循环的测试方法。
(1) 简单循环。应该使用下列测试集来测试简单循环,其中n是允许通过循环的最大次数。跳过循环。只通过循环一次。
通过循环两次。通过循环m次,其中m<n-1。通过循环n-1,n,n+1次。
(2 嵌套循环。如果把简单循环的测试方法直接应用到嵌套循环,可能的测试数就会随嵌套层数的增加按几何级数增长。B.Beizer提出了一种能减少测试数的方法:从最内层循环开始测试,把所有其他循环都设置为最小值。对最内层循环使用简单循环测试方法,而使外层循环的迭代参数(例如,循环计数器)取最小值,并为越界值或非法值增加一些额外的测试。由内向外,对下一个循环进行测试,但保持所有其他外层循环为最小值,其他嵌套循环为“典型”值。继续进行下去,直到测试完所有循环。
(3)串接循环。如果串接循环的各个循环都彼此独立,则可以使用前述的测试简单循环的方法来测试串接循环。但是,如果两个循环串接,而且第一个循环的循环计数器值是第二个循环的初始值,则这两个循环并不是独立的。当循环不独立时,建议使用测试嵌套循环的方法来测试串接循环。
7.7黑盒测试
7.7.1概念
黑盒测试着重测试软件功能。黑盒测试并不能取代白盒测试,它是与白盒测试互补的测试方法,它很可能发现白盒测试不易发现的其他类型的错误。
📌黑盒测试力图发现下述类型的错误:
①功能不正确或遗漏了功能;
②界面错误;
③数据结构错误或外部数据库访问错误;
④性能错误;
⑤初始化和终止错误。
黑盒测试适用性:
白盒测试在测试过程的早期阶段进行,而黑盒测试主要用于测试过程的后期。
设计黑盒测试方案时,应该考虑下述问题:
(1) 怎样测试功能的有效性?
(2) 哪些类型的输入可构成好测试用例?
(3) 系统是否对特定的输入值特别敏感?
(4) 怎样划定数据类的边界?
(5) 系统能够承受什么样的数据率和数据量?
(6) 数据的特定组合将对系统运行产生什么影响?
应用黑盒测试技术,能够设计出满足下述标准的测试用例集:
(1) 所设计出的测试用例能够减少为达到合理测试所需要设计的测试用例的总数;
(2) 所设计出的测试用例能够告诉我们,是否存在某些类型的错误,而不是仅仅指出与特定测试相关的错误是否存在。
7.7.2技术方法
-
等价划分
定义:
等价划分是一种黑盒测试方法,这种技术把程序的输入域划分成若干个数据类,据此导出测试用例。一个理想的测试用例能独自发现一类错误
目的:
等价划分法力图设计出能发现若干类程序错误的测试用例,从而减少必须设计的测试用例的数目。
流程:
划分数据的等价类
为此需要研究程序的功能说明,从而确定输入数据的有效等价类和无效等价类。
在确定输入数据的等价类时常常还需要分析输出数据的等价类,以便根据输出数据的等价类导出对应的输入数据等价类。
根据等价类设计测试方案
(1) 设计一个新的测试方案以尽可能多地覆盖尚未被覆盖的有效等价类,重复这一步骤直到所有有效等价类都被覆盖为止;
(2) 设计一个新的测试方案,使它覆盖一个而且只覆盖一个尚未被覆盖的无效等价类,重复这一步骤直到所有无效等价类都被覆盖为止。注意,通常程序发现一类错误后就不再检查是否还有其他错误,因此,应该使每个测试方案只覆盖一个无效的等价类。等价类划分规则:
- 如果规定了输入值的范围,则可划分出一个有效的等价类(输入值在此范围内),两个无效的等价类(输入值小于最小值或大于最大值;
- 如果规定了输入数据的个数,则可以划分出一个有效的等价类和两个无效的等价类;
- 如果规定了输入数据的一组值,而且程序对不同输入值做不同处理,则每个允许的输入值是一个有效的等价类,此外还有一个无效的等价类(任一个不允许的输入值);
- 如果规定了输入数据必须遵循的规则,则可以划分出一个有效的等价类(符合规则)和若干个无效的等价类(从各种不同角度违反规则);
- 如果规定了输入数据为整型,则可以划分出正整数、零和负整数等3个有效类;
- 如果程序的处理对象是表格,则应该使用空表,以及含一项或多项的表。为
-
边界值分析
使用边界值分析方法设计测试方案首先应该确定边界情况,通常输入等价类和输出等价类的边界,就是应该着重测试的程序边界情况。选取的测试数据应该等于、刚刚小于和刚刚大于边界值。通常设计测试方案时总是联合使用等价划分和边界值分析两种技术。
-
错误推测
错误推测法在很大程度上靠直觉和经验进行。它的基本想法是列举出程序中可能有的错误和容易发生错误的特殊情况,并且根据它们选择测试方案。
7.8调试
📌调试(也称为纠错)作为成功测试的后果出现,也就是说,调试是在测试发现错误之后排除错误的过程
7.8.1 调试过程
调试不是测试,但是它总是发生在测试之后。调试过程从执行一个测试用例开始,评估测试结果,如果发现实际结果与预期结果不一致,则这种不一致就是一个症状,它表明在软件中存在着隐藏的问题。调试过程试图找出产生症状的原因,以便改正错误。调试过程总会有以下两种结果之一: ①找到了问题的原因并把问题改正和排除掉了; ②没找出问题的原因。在后一种情况下,调试人员可以猜想一个原因,并设计测试用例来验证这个假设,重复此过程直至找到原因并改正了错误。
7.8.2 调试途径
无论采用什么方法,调试的目标都是寻找软件错误的原因并改正错误。通常需要把系统地分析、直觉和运气组合起来,才能实现上述目标。一般说来,有下列3种调试途径可以采用:
-
原因排除法
对分查找法、归纳法和演绎法都属于原因排除法。对分查找法的基本思路是,如果已经知道每个变量在程序内若干个关键点的正确值,则可以用赋
值语句或输入语句在程序中点附近“注入”这些变量的正确值,然后运行程序并检查所得到的输出。如果输出结果是正确的,则错误原因在程序的
前半部分;反之,错误原因在程序的后半部分。对错误原因所在的那部分再重复使用这个方法,直到把出错范围缩小到容易诊断的程度为止。归纳
法是从个别现象推断出一般性结论的思维方法。使用这种方法调试程序时,首先把和错误有关的数据组织起来进行分析,以便发现可能的错误原
因。然后导出对错误原因的一个或多个假设,并利用已有的数据来证明或排除这些假设。演绎法从一般原理或前提出发,经过排除和精化的过程推
导出结论。采用这种方法调试程序时,首先设想出所有可能的出错原因,然后试图用测试来排除每一个假设的原因。如果测试表明某个假设的原因
可能是真的原因,则对数 据进行细化以准确定位错误。
-
回溯法
回溯是一种相当常用的调试方法,当调试小程序时这种方法是有效的。具体做法是,从发现症状的地方开始,人工沿程序的控制流往回追踪分析
程序代码,直到找出错误原因为止。但是,随着程序规模扩大,应该回溯的路径数目也变得越来越大,以至彻底回溯变成完全不可能了。
-
蛮干法
蛮干法可能是寻找软件错误原因的最低效的方法。仅当所有其他方法都失败了的情况下,才应该使用这种方法。这种方法印出内存的内容,激活对运行过程的跟踪,并在程序中到处都写上WRITE语句,希望能发现错误的线索。
上述3种调试途径都可以使用调试工具辅助完成,但是工具并不能代替对全部设计文档和源程序的仔细分析与评估。在使用任何一种调试方法之前,必须首先进行周密的思考,必须有明确的目的,应该尽量减少无关信息的数量。如果用遍了各种调试方法和调试工具却仍然找不出错误原因,则应该向同行求助。把遇到的问题向同行陈述并一起分析讨论,往往能开阔思路,较快找出错误原因。
一旦找到错误就必须改正它,但是,改正一个错误可能引入更多的其他错误,以至“得不偿失”。因此,在动手改正错误之前,软件工程师应该仔细考虑下述3个问题:
(1) 是否同样的错误也在程序其他地方存在?在许多情况下,一个程序错误是由错误的逻辑思维模式造成的,而这种逻辑思维模式也可能用在别的地方。仔细分析这种逻辑模式,有可能发现其他错误。
(2) 将要进行的修改可能会引入的“下一个错误”是什么?在改正错误之前应该仔细研究源程序(最好也研究设计文档),以评估逻辑和数据结构的耦合程度。如果所要做的修改位于程序的高耦合段中,则修改时必须特别小心谨慎。
(3) 为防止今后出现类似的错误,应该做什么?如果不仅修改了软件产品还改进了开发软件产品的软件过程,则不仅排除了现有程序中的错误,还避免了今后在程序中可能出现的错误。
软件错误的特征:
(1) 症状和产生症状的原因可能在程序中相距甚远,也就是说,症状可能出现在程序的一个部分,而实际的原因可能在与之相距很远的另一部分。
(2) 当改正了另一个错误之后,症状可能暂时消失了。
(3) 症状可能实际上并不是由错误引起的(例如,舍入误差)。
(4) 症状可能是由不易跟踪的人为错误引起的。
(5) 症状可能是由定时问题而不是由处理问题引起的。
(6) 可能很难重新产生完全一样的输入条件。
(7) 症状可能时有时无。
(8) 症状可能是由分布在许多任务中的原因引起的,这些任务运行在不同的处理机上。
7.9软件可靠性
7.9.1基本概念
📌软件可靠性:指在一定时间间隔内,软件按照规格说明书规定成功运行的概率。
📌软件可用性:指在给定的时间点,软件按照规格说明书规定成功运行的概率。
可靠性和可用性之间的主要差别是,可靠性意味着在0到t这段时间间隔内系统没有失效,而可用性只意味着在时刻t,系统是正常运行的。
MTTF和MTTR:
📌平均维修时间MTTR
表示修复一个故障平均需要用的时间,取决于维修人员的技术水平和对系统的熟悉程度,也和系统的可维护性有关
📌平均无故障时间MTTF
表示系统按规格说明书规定成功运行的平均时间
第八章 软件维护
8.1软件维护概念
8.1.1定义
📌所谓软件维护就是在软件已经交付使用之后,为了改正错误或满足新的需要而修改软件的过程。
8.1.2分类
📌改正性维护:为了修改和改正软件使用过程中出现的错误进行的维护
📌完善性维护:为了满足用户提出的对系统新的功能和性能的要求,而扩充和修改软件功能的过程。
📌适应性维护:为了适应变化的软件运行环境而修改软件的过程
📌预防性维护:为了提高软件的可靠性,可维护性,或者是为了将来的维护奠定良好的基础而修改软件的过程
8.2软件维护特点
8.2.1结构化维护和非结构化维护差别巨大
📌结构化维护:指软件开发过程中运用软件工程方法,软件的维护过程,有一整套完整的方案,技术和审定过程。
- 有一个完整的软件配置存在,维护工作从评价设计文档开始。
- 确定软件重要的结构特点,性能特点以及接口特点;
- 估量要求的改动将带来的影响,并且计划实施途径;
- 修改设计并且对所做的修改进行仔细复查;
- 编写相应的源程序代码
- 使用测试说明书中包含的信息进行回归测试
- 把修改后的软件再次交付使用
📌非结构化维护:缺乏必要的文档说明,难于确定数据结构,系统接口等特性。
流程:
- 软件配置的唯一成分是程序代码,维护活动从评价程序代码开始。
- 对于程序内部文档不足,而对软件结构,全程数据结构,系统结构,性能和设计约束等会产生误解
- 对程序代码所做的改动的后果是难于估量的
- 没有测试方面的文档,不可能进行回归测试。
8.2.2维护代价高昂
软件维护的无形的代价:
可用的资源必须供维护任务使用,以致耽误了开发的良机;
当看来合理的有关改错或修改的要求不能及时满足时将引起用户不满;
由于维护时的改动,在软件中引入了潜伏的错误,从而降低了软件的质量;
必须把软件工程师调去从事维护工作时,将在开发过程中造成混乱。
软件维护的最后一个代价是生产率的大幅度下降,这种情况在维护旧程序时常常遇到。
8.2.3维护的问题
(1) 理解别人写的程序通常非常困难,而且困难程度随着软件配置成分的减少而迅速增加。
(2) 需要维护的软件往往没有合格的文档,或者文档资料显著不足。
(3) 当需要对软件进行维护时,往往原来写程序的人已经不在该项目组中了。
(4) 绝大多数软件在设计时没有充分考虑将来的修改。
(5) 软件维护不是一项吸引人的工作。形成这种观念很大程度上是因为维护工作经常遭受挫折。软件工程至少部分地解决了与维护有关的每一个问题。
8.3软件维护过程
8.3.1定义
维护过程本质上是修改和压缩了的软件定义和开发过程。可以描述为:
建立一个维护组织;
确定报告和评价的过程;
为每一个维护要求确定一个标准化的事件序列;
建立一个适用于维护活动的记录保管过程
规定复审标准
8.3.2具体过程
-
维护组织
通常并不需要建立正式的维护组织。每个维护要求都通过维护管理员转交给相应的系统管理员去评价。系统管理员对维护任务做出评价之后,由变化授权人决定应该进行的活动。图8.1描绘了上述组织方式。在维护活动开始之前就明确维护责任是十分必要的,这样做可以大大减少维护过程中可能出现的混乱。
-
维护报告
应该用标准化的格式表达所有软件维护要求,称为软件问题报告表,这个表格由用户填写。如果遇到了一个错误,那么必须完整描述导致出现错误的环境。对于适应性或完善性的维护要求,应该提出一个简短的需求说明书。由维护管理员和系统管理员评价用户提交的维护要求表。
维护要求表是计划维护活动的基础。软件组织内部应该制定出一个软件修改报告,它给出下述信息:
(1) 满足维护要求表中提出的要求所需要的工作量;
(2) 维护要求的性质;
(3) 要求的优先次序;
(4) 与修改有关的事后数据。
在拟定进一步的维护计划之前,把软件修改报告提交给变化授权人审查批准。
-
维护的事件流
不管维护类型如何,都需要进行同样的技术工作。这些工作包括修改软件设计、复查、必要的代码修改、单元测试和集成测试(包括使用以前的测试方案的回归测试)、验收测试和复审。不同类型的维护强调的重点不同,但是基本途径是相同的。维护事件流中最后一个事件是复审,它再次检验软件配置的所有成分的有效性,并且保证事实上满足了维护要求表中的要求。
-
保存维护记录
保存维护记录需要保存下述内容:
①程序标识; ②源语句数; ③机器指令条数; ④使用的程序设计语言; ⑤程序安装的日期; ⑥自从安装以来程序运行的次数; ⑦自从安装以来程序失效的次数; ⑧程序变动的层次和标识; ⑨因程序变动而增加的源语句数; 因程序变动而删除的源语句数; 每个改动耗费的人时数; 程序改动的日期; 软件工程师的名字; 维护要求表的标识;维护类型; 维护开始和完成的日期; 累计用于维护的人时数; 与完成的维护相联系的纯效益。应该为每项维护工作都收集上述数据。可以利用这些数据构成一个维护数据库的基础,并且对它们进行评价。
-
评价维护活动
(1) 每次程序运行平均失效的次数;
(2) 用于每一类维护活动的总人时数;
(3) 平均每个程序、每种语言、每种维护类型所做的程序变动数;
(4) 维护过程中增加或删除一个源语句平均花费的人时数;
(5) 维护每种语言平均花费的人时数;
(6) 一张维护要求表的平均周转时间;
(7) 不同维护类型所占的百分比。
8.4软件的可维护性
📌软件的可维护性是指:维护人员理解,改正,改动或改进这个软件的难易程度
8.4.1决定软件可维护性的因素
📌可理解性
指理解软件的结构,功能,接口和内部处理过程的难易程度
影响因素:
模块化,详细的文档,结构化设计,程序内部文档和良好的高级程序设计语言。
📌可测试性
诊断和测试的难易程度取决于软件容易理解的程度。
影响因素:
详细的文档,软件结构,可用的测试工具和调试工具,以前的测试过程。
要求:
维护人员应该能够得到在开发阶段用过的测试方案,以便进行回归测试。
📌可修改性
耦合、内聚、信息隐藏、局部化、控制域与作用域的关系等等,都影响软件的可修改性。
📌可移植性
指把程序从一个计算环境移植到另一个计算环境的难易程度
📌可重用性
指同一事物不做修改或稍作修改就在不同环境中多次重复使用
8.4.2 文档
文档是影响软件可维护性的决定因素。软件在使用过程中必然会经受多次修改,所以文档比程序代码更重要。软件系统的文档可以分为用户文档和系统文档两类。用户文档主要描述系统功能和使用方法,并不关心这些功能是怎样实现的;系统文档描述系统设计、实现和测试等各方面的内容。总的说来,软件文档应该满足下述要求:
(1) 必须描述如何使用这个系统;
(2) 必须描述怎样安装和管理这个系统;
(3) 必须描述系统需求和设计;
(4) 必须描述系统的实现和测试,以便使系统成为可维护的。
-
用户文档
用户文档是用户了解系统的第一步,它应该能使用户获得对系统的准确的初步印象。文档的结构方式应该使用户能够方便地根据需要阅读有关的内容。用户文档至少应该包括下述5方面的内容:
(1) 功能描述,说明系统能做什么;
(2) 安装文档,说明怎样安装这个系统以及怎样使系统适应特定的硬件配置;
(3) 使用手册,简要说明如何着手使用这个系统;
(4) 参考手册,详尽描述用户可以使用的所有系统设施以及它们的使用方法,还应该解释系统可能产生的各种出错信息的含义;
(5) 操作员指南(如果需要有系统操作员的话),说明操作员应该如何处理使用中出现的各种情况。
上述内容可以分别作为独立的文档,也可以作为一个文档的不同分册,具体做法应该由系统规模决定。
-
系统文档
所谓系统文档指从问题定义、需求说明到验收测试计划这样一系列和系统实现有关的文档。描述系统设计、实现和测试的文档对于理解程序和维护程序来说是极端重要的。和用户文档类似,系统文档的结构也应该能把读者从对系统概貌的了解,引导到对系统每个方面每个特点的更形式化更具体的认识。
8.4.3 可维护性复审
可维护性是所有软件都应该具备的基本特点,必须在开发阶段保证软件具有可维护性。在软件工程过程的每一个阶段都应该考虑并努力提高软件的可维护性,在每个阶段结束前的技术审查和管理复审中,应该着重对可维护性进行复审。
复审的任务:
-
需求分析阶段的复审
应该对将来要改进的部分和可能会修改的部分加以注意并指明;
应该讨论软件的可移植性问题,并且考虑可能影响软件维护的系统界面。
-
正式的和非正式的设计复审
应该从容易修改、模块化和功能独立的目标出发,评价软件的结构和过程;
设计中应该对将来可能修改的部分预作准备。
-
代码复审
应该强调编码风格和内部说明文档这两个影响可维护性的因素。
在设计和编码过程中应该尽量使用可重用的软件构件。
-
测试阶段称为配置复审。
保证软件配置的所有成分是完整的、一致的和可理解的,
而且为了便于修改和管理已经编目归档了。
-
维护阶段
应该针对整个软件配置,不应该只修改源程序代码。
复审必要性:
在软件再次交付前,对软件配置进行严格审查,则可大大减少文档的问题
8.5 预防性维护
当初开发这些老程序时并没有使用软件工程方法学来指导,文档不全甚至完全没有文档,对曾经做过的修改也没有完整的记录。
怎样满足用户对上述这类老程序的维护要求呢?为了修改这类程序以适应用户新的或变更的需求,有以下几种做法可供选择:
(1) 反复多次地做修改程序的尝试,以实现所要求的修改;
(2) 通过分析程序尽可能多地掌握程序的内部工作细节,以便更有效地修改它;
(3) 在深入理解原有设计的基础上,用软件工程方法重新设计、重新编码和测试那些需要变更的软件部分;
(4) 以软件工程方法学为指导,对程序全部重新设计、重新编码和测试,为此可以使用CASE工具来帮助理解原有的设计。
通常人们采用后3种做法。其中第4种做法称为软件再工程。预防性维护方法是由Miller提出来的,他把这种方法定义为:“把今天的方法学应用到昨天的系统上,以支持明天的需求。”
预防性维护实质上是软件再工程。典型的软件再工程过程模型定义了库存目录分析、文档重构、逆向工程、代码重构、数据重构和正向工程等6类活动。上述模型是一个循环模型,这意味着每项活动都可能被重复,而且对于任意一个特定的循环来说,再工程过程可以在完成任意一个活动之后终止。
第九章 面向对象方法学
9.1面向对象方法学概述
9.1.1定义
📌面向对象方法学是一种以数据和信息为主线,把数据和操作结合起来的方法,即把对象作为融合数据及数据上的操作的统一体
9.1.2基本原则
尽可能模拟人类习惯的思维方式,使开发软件的方法和过程尽可能接近人类认识世界解决问题的方法和过程
9.1.3特点
📌对象
面向对象的软件系统是由对象组成的,软件中的任何元素都是对象,复制的软件对象由比较简单的对象组合而成。用对象分解取代了传统方法的功能分解
📌类
把所有对象都划分成各种对象类(简称为类),每个类都定义了一组数据和一组方法。数据用于表示对象的静态属性,是对象的状态信息。类中定义的方法是允许施加于该类对象上的操作,用于实现对象的动态行为。
📌继承性
按照子类(或称为派生类)与父类(或称为基类)的关系,把若干个类组成一个层次结构的系统(也称为类等级)。在类等级中,下层的派生类自动具有上层基类的特性(包括数据和方法),这种现象称为继承。
📌封装性
对象彼此之间仅能通过传递消息互相联系。对象与传统的数据有本质区别,它不是被动地等待外界对它施加操作,相反,它是进行处理的主体,必须发消息请求它执行它的某个操作,处理它的私有数据,而不能从外界直接对它的私有数据进行操作。也就是说,一切局部于该对象的私有信息都被封装在该对象内,就好像装在一个黑盒子中一样,在外界是看不见的,更不能直接使用,这就是封装性。
9.1.4面向对象方法学优点
-
与人类思维方式一致
以对象为核心,开发出的软件系统由对象组成
设计的主要思路是使用现实世界的概念抽象地思考问题从而自然地解决问题
基本原则是按照人类习惯的思维方式建立问题域和求解域模型
抽象机制使用户在利用计算机软件系统解决复杂问题时使用习惯的抽象思维工具
对象分类的过程,支持从特殊到一般的归纳思维过程
继承特性,支持从一般到特殊的演绎思维过程
提供了随着对系统认识的逐步深入和具体化,而逐步设计和实现该系统的可能性
-
面向对象软件稳定性好
-
面向对象软件可维护性好
-
面向对象软件可重用性好
-
易开发大型软件产品
9.2面向对象概念
9.2.1对象
📌面向对象程序设计角度:对象是具有相同状态的一组操作的集合。
📌按照结构化角度:对象是封装了数据结构及可以施加在这些数据结构上的操作的封装体。
对象的特点:
- 以数据为中心
- 对象是主动的
- 实现了数据封装
- 具有并行性
- 模块独立性好
9.2.2类
📌 类是具有相同数据和相同操作的一组相似对象的定义
9.2.3实例
📌实例就是由某个特定的类所描述的一个具体的对象。
9.2.4消息
📌消息就是要求某个对象执行在定义它的那个类中所定义的某个操作的规格说明。
9.2.5方法
📌方法就是对象所能执行的操作,也就是类中所定义的服务
9.2.6属性
📌属性就是类中定义的数据,是对客观世界实体所具有性质的抽象。
9.2.7封装
📌封装就是把数据和实现操作的代码集中起来放在对象内部
9.2.8继承
📌子类自动享有父类中定义的属性和方法的机制
9.2.9多态
📌相同的操作或函数,过程作用于不同的对象上获得不同的结构
9.2.10重载
📌在同一作用域,若干个参数特征不同的函数可以使用同一函数名
9.3面向对象建模
模型是为了理解事物而对事物做出的一种抽象,是对事物的一种无歧义的书面描述。
三类模型:
对象模型:描述系统的数据结构
动态模型:描述系统的控制结构
功能模型:描述系统功能
9.3.1对象模型
定义:
对象模型表示静态的、结构化的系统的数据性质。它是对模拟客观世界实体的对象以及对象彼此间的关系的映射,描述了系统的静态结构。对象模型为建立动态模型和功能模型,提供了实质性的框架。
工具:
使用UML(统一建模语言)提供的类图来建立对象模型。 在UML中,类的实际含义是一个类及属于该类的对象
具体来说,UML提供了以下13种图
用例图:从用户角度描述系统功能。
类图:描述系统中类的静态结构。
对象图:系统中的多个对象在某一时刻的状态。
状态图:是描述状态到状态控制流,常用于动态特性建模
活动图:描述了业务实现用例的工作流程
顺序图:对象之间的动态合作关系,强调对象发送消息的顺序,同时显示对象之间的交互
协作图:描述对象之间的协助关系
构件图:一种特殊的UML图来描述系统的静态实现视图
部署图:定义系统中软硬件的物理体系结构
包图:对构成系统的模型元素进行分组整理的图
组合结构图:表示类或者构建内部结构的图
交互概览图:用活动图来表示多个交互之间的控制关系的图
类图的基本符号
1)定义类
A:表示
UML中类的图形符号为长方形,用两条横线把长方形分上、中、下3个区域,3个区域分别放类的名字、属性和服务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X0IhRjK1-1691070364397)(image/image_WwZWWP2zOK.png)]
B:命名规则
类名应该是富于描述的、简洁的而且无二义性的
使用标准术语,不要随意创造名字
使用具有确切含义的名词,不要使用空洞或含义模糊的词作名字
必要时可用名词短语作名字,有时也可以加入形容词
(2)定义属性
具体格式为
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KG9TkFOs-1691070364397)(image/image_GvbFRk9-kq.png)]
可见性:有公有的(+)、私有的(-)和保护的(#)
类型名:表示该属性的数据类型
赋值:在创建类的实例时应给其他属性赋值,如果给某个属性定义了初值,则该初值可作为创建实例时这个属性的默认值
性质串:明确地列出该属性所有可能取值,用逗号隔开
(3)定义服务
具体格式为
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zUa5oXtD-1691070364398)(image/image_ngpJB9ifFU.png)]
可见性:有公有的(+)、私有的(-)和保护的(#)
参数表:用逗号隔开不同参数,每个参数语法为 “参数名:类型名=默认值”
三:表示关系的符号
类与类之间通常具有以下四种关系
-
关联
A:定义
关联表示两个类的对象之间存在某种语义上的联系B:关联的角色
在任何关联中都会涉及参与此关联的对象所扮演的角色,在某些情况下显式标明角色名有助于别人理解类[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-07rdzlhl-1691070364398)(image/image_Qb6xNm6SIC.png)]
如果没有显式标出角色名,则意味着用类名作为角色名
C:普通关联
①:定义
普通关联是最常见的关联关系,只要在类与类之间存在连接关系就可以用普通关联表示②:表示
第一,普通关联的图示符号是连接两个类之间的直线,如下图
第二,关联是双向的,可为关联起一个名字。在名字前面(或后面)加一个表示关联方向的黑三角
第三,在表示关联的直线两端可以写上重数,它表示该类有多少个对象与对方的一个对象连接。未明确标出关联的重数,则默认重数是1[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vo20QKW4-1691070364399)(image/image_PlFF7Z2U-Q.png)]
D:限定关联
①:定义
限定关联通常用在一对多或多对多的关联关系中,可以把模型中的重数从一对多变成一对一, 或从多对多简化成多对一②:表示
在类图中把限定词放在关联关系末端的一个小方框内。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tbxnWybg-1691070364399)(image/image_0tx-FgIhsf.png)]
利用限定词“文件名”表示了目录与文件之间的关系,利用限定词把一对多关系简化成了一对一关系
③:意义
限定提高了语义精确性,增强了查询能力E:关联类
①:定义
为了说明关联的性质,可能需要一些附加信息。关联类可以用来记录相关信息②:表示
关联类通过一条虚线与关联连接[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gI506rw8-1691070364399)(image/image_Hp8OnFv7IN.png)]
关联中的每个连接与关联类的一个对象相联系
-
聚集(它是关联的特例)
聚集(聚合)是关联的特例。**表示类与类之间的关系是整体与部分的关系。在陈述需求时使用的“包含”、“组成”、“分为…部分”**等字句,往往意味着存在聚集关系。除了一般聚集之外,还有两种特殊的聚集关系,分别是共享聚集和组合聚集A:共享聚集
如果在聚集关系中处于部分方的对象可同时参与多个处于整体方对象的构成, 则该聚集称为共享聚集[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vbx34GFf-1691070364400)(image/image_PxE7-I4mby.png)]
一般聚集和共享聚集的图示符号,都是在表示关联关系的直线末端紧挨着整体类的地方画一个空心菱形
B:组合聚集
如果部分类完全隶属于整体类,部分与整体共存,整体不存在了部分也会随之消失, 则该聚集称为组合聚集(组成)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1B1e20PT-1691070364400)(image/image_EAcAv70k_0.png)]
组成关系用实心菱形示例
-
泛化(本质就是继承)
UML中的泛化关系就是继承关系,它是通用元素和具体元素之间的一种分类关系。 具体元素完全拥有通用元素的信息,并且还可以附加一些其他信息。在UML中,用一端为空心三角形的连线表示泛化关系,三角形的顶角紧挨着通用元素A:普通泛化
①:抽象类****没有具体对象的类称为抽象类。抽象类通常都有抽象操作,来指定该类的所有子类应具有哪些行为[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I3hvSAFM-1691070364400)(image/image_1j1T-szWAh.png)]
表示抽象类是在类名下方附加一个标记值{abstract},表示抽象操作是在操作标记后面跟随一个性质串{abstract}
②:具体类****具体类有自己的对象,并且该类的操作都有具体的实现方法[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EJth57Hx-1691070364400)(image/image_SNlbvWznLe.png)]
B:受限泛化①:定义
可以给泛化关系附加约束条件,以进一步说明该泛化关系的使用方法或扩充方法, 这样的泛化关系称为受限泛化②:约束
预定义的约束有4种(都是语义约束)多重:一个子类可以同时多次继承同一个上层基类
不相交:一个子类不能多次继承同一个基类。-般的继承都是不相交继承
完全:父类的所有子类都已在类图中穷举出来了
不完全:父类的子类并没有都穷举出来,随着对问题理解的深入,可不断补充和维护。是默认的继承关系
-
依赖和细化****A:依赖关系依赖关系描述两个模型元素之间的语义连接关系 :其中一个模型元素是独立的,另一个模型元素不是独立的,它依赖于独立的模型元素,如果独立的模型元素改变了,将影响依赖于它的模型元素
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ShkPQyV2-1691070364401)(image/image_iVxNGHXRss.png)]
在UML类图中用带箭头的虚线连接有依赖关系的两个类,箭头指向独立的类。在虚线上可以带一个版类标签,具体说明依赖的种类
B:细化关系****对同一个事物在不同抽象层次上描述时,这些描述之间具有细化关系[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ECSBjgau-1691070364401)(image/image_F3eFYQHPvI.png)]
细化的图示符号为由元素B指向元素A的一端为空心三角形的虚线
9.3.3动态模型
- 概念
动态模型表示瞬时的、行为化的系统的控制性质,它规定了对象模型中的对象的合法变化序列 - 建模
用UML提供的状态图来描绘对象的状态、触发状态转换的事件以及对象的行为。每个类的动态行为用一张状态图来描绘,各个类的状态图通过共享事件合并起来,从而构成系统的动态模型,即动态模型是基于事件共享而互相关联的一组状态图的集合
9.3.4功能模型
-
概念
A:定义
功能模型表示变化的系统的功能性质,它指明了系统应该做什么,因此更直接地反映了用户对目标系统的需求B:组成
功能模型由一组数据流图组成 -
用例图
UML提供的用例图也是进行需求分析和建立功能模型的强有力工具。在UML中把用用例图建立起来的系统模型称为用例模型A:定义
用例模型描述的是外部行为者所理解的系统功能。用例模型的建立是系统开发者和用户反复讨论的结果,它描述了开发者和用户对需求规格所达成的共识。B:表示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IsMGu55N-1691070364402)(image/image_akb-HObUOf.png)]
①:系统
定义:系统被看作是一个提供用例的黑盒子,内部如何工作、用例如何实现,这些对于建立用例模型来说都是不重要的表示:系统用方框表示,其边线表示系统的边界,用于划定系统的功能范围,定义了系统所具有的功能。描述该系统功能的用例置于方框内,代表外部实体的行为者置于方框外
②:用例
定义 :一个用例是可以被行为者感受到的、系统的一个完整的功能。 在UML中把用例定义成系统完成的一系列动作表示:在UML中,椭圆代表用例。用例通过关联与行为者连接,关联指出一个用例与哪些行为者交互,这种交互是双向的
特征:
用例代表某些用户可见的功能,实现一个具体的用户目标
用例总是被行为者启动的,并向行为者提供可识别的值
用例必须是完整的
注意:用例是一个类,它代表一类功能而不是使用该功能的某个具体实例。用例的实例是系统的一种实际使用方法,通常把用例的实例称为脚本。脚本是系统的一次具体执行过程③:行为者
定义:行为者是指与系统交互的人或其他系统,它代表外部实体。使用用例并且与系统交互的任何人或物都是行为者。行为者代表一种角色,而不是某个具体的人或物表示:在UML中,线条人代表行为者。在用例图中用直线连接行为者和用例,表示两者之间交换信息,称为通信联系。行为者触发用例,并与用例交换信息。单个行为者可与多个用例联系,一个用例也可与多个行为者联系
④:用例间关系
扩展关系: 向一个用例中添加一些动作后构成了另一个用例,这两个用例之间的关系就是扩展关系,后者继承前者的一些行为,通常把后者称为扩展用例使用关系: 一个用例使用另一个用例时,这两个用例之间就构成了使用关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jMXhZoCc-1691070364402)(image/image_YayK7o7wib.png)]
两种关系的异同
都是从几个用例中抽取那些公共的行为并放入一个单独的用例中,而这个用例被其他用例使用或扩展
使用和扩展的目的是不同的。在描述一般行为的变化时采用扩展关系
在两个或多个用例中出现重复描述又想避免这种重复时,采用使用关系
三:三种模型比较(了解)
针对每个类建立的动态模型,描述了类实例的生命周期或运行周期
状态转换驱使行为发生,这些行为在数据流图中被映射成处理,在用例图中被映射成用例,它们同时与类图中的服务相对应
功能模型中的处理对应于对象模型中的类所提供的服务
数据流图中的数据存储,以及数据的源点/终点,通常是对象模型中的对象
数据流图中的数据流,往往是对象模型中对象的属性值,也可能是整个对象
用例图中的行为者,可能是对象模型中的对象
功能模型中的处理可能产生动态模型中的事件
对象模型描述了数据流图中的数据流、数据存储以及数据源点/终点的结构
9.4面向对象分析
9.4.1面向对象分析的基本过程
📌定义
面向对象分析:就是抽取和整理用户需求并建立问题域精确模型的过程
3个子模型与5个层次:
A:3个子模型
面向对象建模得到的模型包含系统的三个要素:
静态结构(对象模型):解决任一问题,都需要从客观世界实体及实体间相互关系抽象出极有价值的对象模型
交互次序(动态模型):当问题涉及交互作用和时序时,动态模型是重要的
数据变换(功能模型):解决运算量很大的问题,则涉及重要的功能模型
B:5个层次
复杂问题的对象模型由5个层次组成:主题层、类与对象层、结构层、属性层和服务层。 这5个层次一层比一层显现出对象模型的更多细节
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MWv0L2jQ-1691070364402)(image/image_17P4xoAi8N.png)]
①:主题
主题是指导读者理解大型、复杂模型的一种机制。即通过划分主题把一个大型、复杂的对象模型分解成几个不同的概念范畴
②:7 ± \pm± 2原则
心理研究表明,人类的短期记忆能力一般限于一次记忆5~9个对象,面向对象从两个方面体现这条原则
控制可见性:控制读者能见到的层次数目来控制可见性
指导读者注意力:增加了主题层,可从高层次描述总体模型,并指导读者的注意力。
③:面向对象分析顺序
- 寻找类与对象
- 识别结构
- 识别主题
- 定义属性
- 建立动态模型
- 建立功能模型
- 定义服务
④:建模要点
面向对象分析不可能严格按照顺序线性进行
必须在反复分析中对初始模型中不准确、不完整和错误的内容加以扩充和更正
仔细研究类似问题的分析结果,尽可能重用这些结果
9.4.2需求陈述
- 内容
- 阐明“做什么”而不是“怎样做”
- 描述用户的需求而不是提出解决问题的方法
- 指出哪些是系统必要的性质,哪些是任选的性质
- 避免对设计策略施加过多的约束,不描述系统的内部结构
- 描述系统性能及系统与外界环境交互协议
- 描述采用的软件工程标准、模块构造准则、将来的扩充以及可维护性要求等方面
- 书写规范
- 做到语法正确,而且应该慎重选用名词、动词、形容词和同义词
- 必须把需求与实现策略区分开,后者不是问题域的本质性质
- 需求陈述可简可繁
- 避免出现具有二义性的、不完整的、不一致的内容
9.4.3建立对象模型
-
基本概念
对象模型:面向对象分析的首要工作就是建立问题域的对象模型。对象模型表示**静态的,结构化的数据性质。它是对模拟客观世界实体的对象以及对象彼此间的关系的映射,**描述了系统的静态结构。
先建立对象模型的原因:
- 静态数据结构对应用细节依赖较少,比较容易确定。
- 当用户的需求变化时,静态数据结构相对来说比较稳定
信息来源:需求陈述,应用领域的专业知识,客观世界的常识,是建立对象模型时的主要信息来源
面向对象分析的顺序:确定类和对象,识别结构,识别主题,定义属性,建立动态模型,建立功能模型,定义服务)
典型的建模步骤:(确定类和对象,确定关联,划分主题,确定属性,识别继承关系,反复修改)
- 确定对象类和关联(对于大型复杂问题还要进一步划分出若干个主题);
- 给类和关联增添属性,以进一步描述它们;
- 使用适当的继承关系进一步合并和组织类
-
确定类与对象
- 找出候选的类和对象
-
客观事物分类
对象是对问题域中有意义的事物的抽象,它们既可能是物理实体,也可能是抽象概念。客观事物可分为下述5类:
- 可感知的物理实体;
- 人或组织的角色;
- 应该记忆的事件;
- 两个或多个对象的相互作用;
- 需要说明的概念
-
非正式分析
以用自然语言书写的需求陈述为依据,把陈述中的名词作为类与对象的候选者,用形容词作为确定属性的线索,把动词作为服务的候选者。
-
提取隐含的类和对象
-
- 确定正确的类和对象
- 如果两个类表达了同样的信息,则应该保留在此问题域中最富于描述力的名称
- 需要把与本问题密切相关的类与对象放进目标系统中
- 系统无须记忆笼统的、泛指的名词信息,把这些笼统的或模糊的类去掉
- 把描述的是其他对象属性的词从候选类与对象中去掉
- 慎重考虑既可作为名词,又可作为动词的词,以便正确地决定把它们作为类还是作为类中定义的操作。本身具有属性,需独立存在的操作,应该作为类与对象
- 应该去掉仅和实现有关的候选的类与对象
- 找出候选的类和对象
-
确定关联
-
关联A:定义两个或多个对象之间的相互依赖、相互作用的关系就是关联。在需求陈述中使用的描述性动词或动词词组,通常表示关联关系
B:确定关联的重要性分析确定关联,能促使分析员考虑问题域的边缘情况, 有助于发现尚未被发现的类与对象
-
步骤****A:初步确定关联
直接提取动词短语得出的关联
需求陈述中隐含的关联
根据问题域知识得出的关联B:筛选
筛选时主要根据下述标准删除候选的关联:已删去的类之间的关联:如果在分析确定类与对象的过程中已经删掉了某个候选类,则与这个类有关的关联也应该删去,或用其他类重新表达这个关联
与问题无关的或应在实现阶段考虑的关联:应该把处在本问题域之外的关联与实现密切相关的关联删去
瞬时事件: 关联应该描述问题域的静态结构,而不应该是一个瞬时事件
三元关联: 三个或三个以上对象间的关联,可分解为二元关联或用词组描述成限定的关联
派生关联: 去掉那些可以用其他关联定义的冗余关联
C:改进
可以从以下几个方面进一步完善经筛选后余下的关联正名: 仔细选择含义更明确的名字作为关联名
分解: 为了能够适用于不同的关联,必要时应该分解以前确定的类与对象
补充: 发现了遗漏的关联就应该及时补上
标明重数: 应该初步判定各个关联的类型,并粗略地确定关联的重数
-
-
划分主题
(1)定义
在开发大型、复杂系统的过程中,为了降低复杂程度,把系统再进一步划分成几个不同的主题, 即在概念上把系统包含的内容分解成若干个范畴(2)针对不同类型的方法
规模小的系统:可能无须引入主题层
含有较多对象的系统:首先识别出类与对象和关联,然后划分主题,并用它作为指导开发者和用户观察整个模型的一种机制
规模大的系统:首先由高级分析员粗略地识别对象和关联,然后初步划分主题,经进一步分析,对系统结构有更深入的了解之后,再进一步修改和精炼(3)原则
按问题领域而不是用功能分解方法来确定主题
按照使不同主题内的对象相互间依赖和交互最少的原则来确定主题 -
确定属性
(1)属性
属性是对象的性质,借助于属性人们能对类与对象和结构有更深入更具体的认识注意:在分析阶段不要用属性来表示对象间的关系,使用关联能够表示两个对象间的任何关系,而且把关系表示得更清晰、更醒目
(2)确定属性的步骤
A:分析
在需求陈述中用名词词组表示属性,用形容词表示可枚举的具体属性
借助于领域知识和常识分析需要的属性
仅考虑与具体应用直接相关的属性,不要考虑那些超出所要解决的问题范围的属性
首先找出最重要的属性,以后再逐渐把其余属性增添进去
不要考虑那些纯粹用于实现的属性
B:选择
从初步分析确定下来的属性中删掉不正确的或不必要的属性。有以下几种常见情况:误把对象当作属性:如果某个实体的独立存在比它的值更重要,则应把它作为一个对象而不是对象的属性
误把关联类的属性当作一般对象的属性:如果某个性质依赖于某个关联链的存在,则该性质是关联类的属性,在分析阶段不应把它作为一般对象的属性
把限定误当成属性:如果把某个属性值固定下来以后能减少关联的重数,则应该考虑把这个属性重新表达成一个限定词。
误把内部状态当成了属性:如果某个性质是对象的非公开的内部状态,则应该从对象模型中删除这个属性。
过于细化:在分析阶段应该忽略那些对大多数操作都没有影响的属性
存在不一致的属性:类应该是简单而且一致的。如果得出一些看起来与其他属性毫不相关的属性,则应该考虑把类分解成两个不同的类
-
识别继承关系
(1)建立继承关系的方式
确定了类中应该定义的属性之后,就可以利用继承机制共享公共性质,并对系统中众多的类加以组织。可以使用以下两种方式建立继承关系自底向上: 抽象出现有类的共同性质泛化出父类,这个过程实质上模拟了人类归纳思维的过程自顶向下:把现有类细化成更具体的子类,这模拟了人类的演绎思维过程。从应用域中常常能明显看出应该做的自顶向下的具体化工作
(2)多重继承
A:作用
利用多重继承可以提高共享程度,但增加了概念上以及实现时的复杂程度B:要点
指定一个主要父类,从它继承大部分属性和行为;
次要父类只补充一些属性和行为 -
反复修改
-
必要性
软件开发过程就是一个多次反复修改、逐步完善的过程。仅仅经过一次建模过程很难得到完全正确的对象模型
-
面向对象在修改时的优点
面向对象的概念和符号在整个开发过程中都是一致的,比使用结构分析、设计技术更容易实现反复修改、逐步完善的过程
-
9.4.4建立动态模型和建立功能模型
- 建立动态模型
-
概念****A:适用性
- 对于仅存储静态数据的系统来说,动态模型并没有什么意义
- 在开发交互式系统时,动态模型却起着很重要的作用
- 收集输入信息是系统的主要工作时,则在开发时建立正确的动态模型是至关重要的
B:步骤(编写脚本,设想用户界面,画事件跟踪图,画状态图,审查动态模型) - 编写典型交互行为的脚本
- 从脚本中提取出事件,确定触发每个事件的动作对象以及接受事件的目标对象
- 排列事件发生的次序,确定每个对象的状态及状态间的转换关系,用状态图描绘
- 比较各个对象的状态图,确保事件之间的匹配
-
编写脚本****A:定义
脚本是指系统在某一执行期间内出现的一系列事件。 脚本描述用户与目标系统之间的一个或多个典型的交互过程。编写脚本的过程,就是分析用户对系统交互行为的要求的过程
B:目的
保证不遗漏重要的交互步骤,有助于确保交互过程的正确性、清晰性C:内容
脚本描写的范围主要由编写脚本的具体目的决定,既可以包括系统中发生的全部事件,也可以只包括由某些特定对象触发的事件D:方法
编写正常情况的脚本
考虑特殊情况
考虑出错情况 -
设想用户界面
大多数交互行为都可以分为应用逻辑和用户界面两部分,通常,系统分析员首先集中精力考虑系统的信息流和控制流,而不是首先考虑用户界面A:重要性
用户界面的美观程度、方便程度、易学程度以及效率等,是用户使用系统时最先感受到的。用户界面的好坏往往对用户是否喜欢、是否接受一个系统起很重要的作用B:目的
这个阶段用户界面的细节并不太重要,重要的是在这种界面下的信息交换方式。目的是确保能够完成全部必要的信息交换,而不会丢失重要的信息C:方法
快速地建立起用户界面的原型,供用户试用与评价 -
画事件跟踪图
A:必要性
用自然语言书写的脚本往往不够简明,而且有时在阅读时会有二义性。为了有助于建立动态模型,需要画出事件跟踪图B:步骤
①:确定事件
1 .提取出所有外部事件·找出正常事件、异常事件和出错条件(传递信息的对象的动作也是事件)
把对控制流产生相同效果的事件组合为一类事件,并取一个唯一的名字2.画出事件跟踪图
一条竖线代表一个对象
每个事件用一条水平的箭头线表示
箭头方向从事件的发送对象指向接受对象
时间从上向下递增
用箭头线在垂直方向上的相对位置表示事件发生的先后,不表示事件间的时间差[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o69Ydkvh-1691070364403)(image/image_STj6ElWyxY.png)]
-
画状态图
A:定义
状态图描绘事件与对象状态的关系。当对象接受了一个事件以后,它的下个状态取决于当前状态及所接受的事件。由事件引起的改变称为“转换”。一张状态图描绘了一类对象的行为,它确定了由事件序列引出的状态序列[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YxAYGwDH-1691070364403)(image/image_cutxRSFI8R.png)]
B:适用性
对于仅响应与过去历史无关的那些输入事件,或者把历史作为不影响控制流的参数类的对象,状态图是不必要的C:方法
- 仅考虑事件跟踪图中指向某条竖线的那些箭头线。把这些事件作为状态图中的有向边,边上标以事件名
- 两个事件之间的间隔就是一个状态,每个状态取个有意义的名字。从事件跟踪图中当前考虑的竖线射出的箭头线,是这条竖线代表的对象达到某个状态时所做的行为。
- 根据一张事件跟踪图画出状态图后,再把其他脚本的事件跟踪图合并到该图中
考虑完正常事件后再考虑边界情况和特殊情况,包括在不适当时候发生的事件
-
审查动态模型
检查系统级的完整性和一致性
审查每个事件,跟踪它对系统中各对象所产生的效果,保证与每个脚本都匹配
-
- 建立功能模型
-
定义功能模型表明了系统中数据之间的依赖关系,以及有关的数据处理功能, 它由一组数据流图组成。在建立了对象模型和动态模型之后再建立功能模型
-
画出基本系统模型图
基本的系统模型有下述两部分组成:数据源点/终点:数据源点输入的数据和输出到数据终点的数据,是系统与外部世界间交互事件的参数
处理框:代表了系统加工、变换数据的整体功能 -
画出功能级数据流图
把基本系统模型中单一的处理框分解成若干个处理框,以描述系统加工、变换数据的基本功能,就得到功能级数据流图 -
描述处理框功能****A:要点
着重描述每个处理框所代表的功能,而不是实现功能的具体算法B:分类
说明性描述(更为重要):规定了输入值和输出值之间的关系,以及输出值应遵循的规律
过程性描述:通过算法说明“做什么”
-
9.5面向对象设计
9.5.1面向对象设计的概念
-
定义
将面向分析阶段得到的需求转变为符合成本和质量要求的抽象的系统实现方案。从面向对象分析到面向对象设计就是一个扩充模型的过程,即面向对象设计就是用面向对象观点建立求解域模型的过程
-
设计与分析的关系
- 分析结果可以直接映射成设计结果,而在设计过程中又会加深和补充对系统需求的理解,进一步完善分析结果
- 分析和设计活动是一个多次反复迭代的过程
- 分析是提取和整理用户需求,并建立问题域精确模型的过程。设计则是把分析阶段得到的需求转变成符合成本和质量要求的、抽象的系统实现方案的过程
-
分类
- 系统设计:确定实现系统的策略和目标系统的高层结构
- 对象设计:确定解空间中的类、关联、接口形式及实现服务的算法
9.5.2面向对象的设计准则
-
模块化
面向对象软件开发模式支持了把系统分解成模块设计的原理,对象是面向对象软件系统中的模块,它是把数据结构和操作这些数据的方法紧密地结合在一起所构成的模块
-
抽象
面向对象的程序设计语言不仅支持过程抽象,而且支持数据抽象, 对象类实际上是具有继承机制的抽象数据类型,它对外开放的公共接口构成了类的规格说明(协议),这种接口规定了外界可以使用的合法操作符,利用这些操作符可以对类实例中包含的数据进行操作
规格说明抽象: 使用者无须知道操作符的实现算法和类中数据元素的具体表示方法,就可以通过这些操作符使用类中定义的数据,这种抽象称为规格说明抽象
参数化抽象: 指当描述类的规格说明时并不具体指定所要操作的数据类型,而是把数据类型作为参数,使得类的抽象程度更高,应用范围更广,可重用性更高 -
信息隐藏
在面向对象的软件中,信息隐藏通过对象的封装来实现,即类结构分离了接口与实现,从而支持了信息隐藏。对于类,属性的表示方法和操作的实现算法都是隐藏的
-
低耦合
A:交互耦合
对象间的耦合通过消息连接来实现,则这种耦合是交互耦合。要使交互耦合尽可能松散,必须遵守下述准则。尽量降低消息连接的复杂程度。应该尽量减少消息中包含的参数个数,降低参数的复杂程度
减少对象发送或接收的消息数
B:继承耦合
继承是一般类与特殊类之间耦合的一种形式。通过继承关系结合起来的基类和派生类构成了系统中粒度更大的模块,因此,它们彼此之间应该结合得越紧密越好 -
高内聚
A:服务内聚
一个服务应该完成一个且仅完成一个功能B:类内聚
设计类的准则是,一个类应该只有一个用途,它的属性和服务应该是高内聚的。如果某个类有多个用途,应该把它分解成多个专用的类C:一般一特殊内聚
设计出的一般一特殊结构应该是对相应的领域知识的正确抽取。紧密的继承耦合与高度的一般一特殊内聚是一致的 -
可重用
- 尽量使用已有的类
- 如果需要创建新类,则在设计这些新类的协议时应该考虑将来的可重复使用性
9.5.3启发式原则和软件重用
-
启发规则
-
设计结果应该清晰易懂
保证设计结果应该清晰易懂的主要因素如下
用词一致: 应该使名字与它所代表的事物–致,而且应该尽量使用人们习惯的名字。不同类中相似服务的名字应该相同
使用已有的协议: 如果开发同一软件的其他设计人员已经建立了类的协议,或者在所使用的类库中已有相应的协议,则应该使用这些已有的协议
减少消息模式的数目: 如果已有标准的消息协议,设计人员应该遵守这些协议
避免模糊的定义:一个类的用途应该是有限的,而且应该从类名可以较容易地推出它的用途
一般一特殊结构的深度适当
使类等级中包含的层次数适当,类等级中包含的层次保持在7±2
不能仅从方便编码的角度出发随意创建派生类,应该使一般一特殊结构与领域知识或常识保持一致
-
设计简单的类
避免包含过多属性:属性过多通常表明这个类过分复杂了,它所完成的功能可能太多了
有明确的定义:为了使类的定义明确,分配给每个类的任务应该简单,最好能使用一两个简单语句描述它的任务
简化对象之间的合作关系:如果需要多个对象协同配合才能做好一件事,则破坏了类的简明性和清晰性
不要提供太多服务:一个类提供的服务过多,同样表明这个类过分复杂。典型地,一个类提供的公共服务不超过7个划分“主题”
-
使用简单的协议****使用简单的服务
避免使用复杂的服务
需要在服务中使用CASE语句时,应用一般一特殊结构代替这个类
-
把设计变动减至最小
理想的设计变动曲线如下图所示。即在设计的早期阶段,变动较大,随着时间推移,设计方案日趋成熟改动也越来越小了
-
-
软件重用
-
概述:
重用也叫再用或复用,是指同一事物不作修改或稍加改动就多次重复使用。软件重用可分为以下3个层次:
知识重用;
方法和标准的重用;
软件成分的重用 -
软件成分重用的级别
-
代码重用
源代码剪贴:是最原始的重用形式,复制或修改源代码时可能出错,且存在严重的配置管理问题
源代码包含:配置管理问题有所缓解,所有包含它的程序都必须重新编译
继承:无须修改已有的代码,就可扩充或具体化在库中找出的类。基本上不存在配置管理问题 -
设计结果重用
重用某个软件系统的设计模型(求解域模型),有助于把一个应用系统移植到完全不同的软硬件平台
-
分析结果重用
是一种更高级别的重用,重用某个系统的分析模型。特别适用于用户需求未改变,但系统体系结构发生了根本变化的场合
-
-
典型的可重用软件成分
项目计划:软件项目计划的基本结构和许多内容都是可以跨项目重用的
成本估计:在只做极少修改或根本不做修改的情况下,重用对该功能的成本估计结果
体系结构:创建一组类属的体系结构模板,并把那些模板作为可重用的设计框架
需求模型和规格说明:类和对象的模型、规格说明、用传统软件工程方法开发的分析模型是重用的候选者
设计:用传统方法开发的体系结构、数据、接口和过程设计结果,是重用的候选者
源代码:用兼容的程序设计语言书写的、经过验证的程序构件,是重用的候选者
用户文档和技术文档:即使针对的应用是不同的,也经常有可能重用用户文档和技术文档的大部分
用户界面:这可能是最广泛被重用的软件成分,GUI (图形用户界面)软件经常被重用。因为它可占到一个应用程序的60%的代码量
数据:被重用的数据包括:内部表、列表和记录结构,以及文件和完整的数据库
测试用例:一旦设计或代码构件将被重用,相关的测试用例应该“附属于”它们也被重用 -
类构件
-
可重用软件构件特点
模块独立性强: 具有单一、完整的功能,且经过反复测试被确认是正确的。它应该是一个不受或很少受外界干扰的封装体,其内部实现在外面是不可见的
具有高度可塑性:可重用的软构件必须具有高度可裁剪性,即必须提供为适应特定需求而扩充或修改已有构件的机制,而且所提供的机制必须使用起来简单方便
接口清晰、简明、可靠:软件构件应该提供清晰、简明、可靠的对外接口,而且还应该有详尽的文档说明,以方便用户使用
-
类构件的重用方式
类构件:面向对象技术中的“类”,是比较理想的可重用软构件
实例重用
除了用已有的类为样板直接创建该类的实例之外,还可以用几个简单的对象作为类的成员创建出一个更复的类
继承重用
当已有的类构件不能通过实例重用方式满足当前系统的需求时,利用继承机制从已有类派生出符合需要的子类,是安全修改已有的类构件并获得可在当前系统中使用的类构件的有效手段
-
多态重用
在设计类构件时应把注意力集中在下列这些可能妨碍重用的操作上:与表示方法有关的操作
与数据结构、数据大小等因素有关的操作
与外部设备有关的操作
实现算法在将来可能会改变的核心操作 -
-
软件重用的效益
A:质量
随着每一次重用, 都会有一些错误被发现并被清除,构件的质量也会随之改善。随着时间的推移,构件将变成实质上无错误的。重用给软件产品的质量和可靠性带来实质性的提高
B:生产率
把可重用的软件成分应用于软件开发的全过程时,创建计划、模型、文档、代码和数据所花费的时间将减少,从而用较少的投入给客户提供相同级别的产品,故生产率得到了提高
C:成本
软件重用带来的净成本节省可以用下式估算:
C=Cs−Cr−Cd
Cs:是项目从头开发时所需要的成本
Cr:是与重用相关联的成本
Cd:是交付给客户的软件的实际成本
与重用相关联的成本C,主要包括下述成本:
①领域分析与建模的成本
②设计领域体系结构的成本
③为方便重用而增加的文档的成本
④维护和完善可重用的软件成分的成本
⑤为从外部获取构件所付出的版税和许可证费用
⑥创建及运行重用库的费用
⑦对设计和实现可重用构件的人员的培训费用
9.5.4系统分解和设计问题域子系统
一:系统分解
(1)分解思想
在设计比较复杂的应用系统时,先把系统分解成若干个较小部分,然后分别设计每个部分。这样做有利于降低设计的难度,有利于分工协作,也有利于维护人员对系统理解和维护
(2)子系统
A:定义
系统的主要组成部分称为子系统,通常根据所提供的功能来划分子系统
B:划分原则
根据所提供的功能来划分子系统,子系统数目应该与系统规模基本匹配
各个子系统之间应该具有尽可能简单、明确的接口
应该尽量减少子系统彼此间的依赖性
(3)分解面向对象设计模型
A:表示
典型的面向对象设计模型如下图所示
面向对象设计模型由主题、类与对象、结构、属性、服务5个层次组成。这5个层次一层比一层表示的细节更多,可以把这5个层次想象为整个模型的水平切片
面向对象设计模型在逻辑上都由4大部分组成,分别对应于组成目标系统的4个子系统,即问题域子系统、人机交互子系统、任务管理子系统和数据管理子系统
B:子系统间交互方式
①:客户-供应商关系(Client-supplier)
作为“客户”的子系统调用作为“供应商”的子系统,后者完成某些服务工作并返回结果。作为客户的子系统必须了解作为供应商的子系统的接口,后者却无须了解前者的接口
②:平等伙伴关系
每个子系统都可能调用其他子系统,每个子系统都必须了解其他子系统的接口。由于各个子系统需要相互了解对方的接口,子系统之间的交互复杂,且还可能存在通信环路
C:组织系统的方案
①:层次组织
软件系统组织成一个层次系统,每层是一个子系统。上层在下层的基础上建立,下层为实现上层功能而提供必要的服务。每一层内所包含的对象,彼此间相互独立,而处于不同层次上的对象,彼此间有关联。在上、下层之间存在客户-供应商关系。低层子系统提供服务,上层子系统使用下层提供的服务
封闭式:每层子系统仅仅使用其直接下层提供的服务。降低了各层次之间的相互依赖性,更容易理解和修改
开放式:子系统可以使用处于其下面的任何一层子系统所提供的服务。优点是减少了需要在每层重新定义的服务数目,使系统更高效更紧凑。但其不符合信息隐藏原则
②:块状组织
把软件系统垂直地分解成若千个相对独立的、弱耦合的子系统,一个子系统相当于一块,每块提供一种类型的服务
③:层次和块状的结合
当混合使用层次结构和块状结构时,同一层次可以由若干块组成,而同一块也可以分为若干层
④:设计系统的拓扑结构
典型的拓扑结构有管道形、树形、星形等。应采用与问题结构相适应的、尽可能简单的拓扑结构,以减少子系统之间的交互数量
二:设计问题域子系统
(1)概念
面向对象分析所得出的问题域精确模型,为设计问题域子系统建立了完整的框架
保持面向对象分析所建立的问题域结构
面向对象设计仅需从实现角度对问题域模型做一-些补充或修改
问题域子系统过分复杂庞大时,应该把它进一步分解成若干个更小的子系统
(2)对问题域模型进行的处理
A:调整需求
①:需要进行调整的情况
用户需求或外部环境发生了变化
分析员对问题域理解不透彻或不能完整、准确地反映用户的真实需求
②:方法
简单地修改面向对象分析结果,然后再把这些修改反映到问题域子系统中
B:重用已有的类
步骤为:
在已有类中找出与问题域内某个最相似的类作为被重用的类
从被重用的类派生出问题域类
简化对问题域类的定义(从被重用的类继承的属性和服务无须再定义)
修改与问题域类相关的关联,必要时改为与被重用的类相关的关联
C:把问题域类组合在一起
增添一个根类而把若干个问题域类组合在一起
引入根类或基类的办法,可以为一些具体类建立一个公共的协议
D:增添一般化类以建立协议
在设计过程中常常发现,一些具体类需要有一个公共的协议,可以引入附加类以便建立这个协议
E:调整继承层次
如果面向对象分析模型中包含了多重继承关系,然而所使用的程序设计语言却并不提供多重继承机制,则必须修改面向对象分析的结果。具体如下:
多重继承机制:避免出现服务及属性的命名冲突
单继承机制:使用多重继承机制时,必须把面向对象分析模型中的多重继承结构转换成单继承结构
9.5.5设计人机交互子系统和设计任务管理子系统
一:设计人机交互子系统
(1)概念
A:主要内容
在面向对象设计过程中,对系统的人机交互子系统进行详细设计,以确定人机交互的细节,其中包括指定窗口和报表的形式、设计命令层次等项内容。
B:重要性
人机界面设计得好,则会使系统对用户产生吸引力,用户在使用系统的过程中会感到兴奋,能够激发用户的创造力,提高工作效率;
人机界面设计得不好,用户在使用过程中就会感到不方便、不习惯,甚至会产生厌烦和恼怒的情绪
(2)设计策略
A:分类用户
应该把将来可能与系统交互的用户按技能水平,或按职务,或按所属集团进行分类
B:描述用户
了解将来使用系统的每类用户的情况,把用户类型、使用目的、特征、关键的成功因素、技能水平、完成本职工作的脚本的信息记录下来
C:设计命令层次
①:研究现有的人机交互含义和准则
设计图形用户界面时,应该遵守广大用户习惯的约定,这样才会被用户接受和喜爱
②:确定初始的命令层次
命令层次实质上是用抽象机制组织起来的、可供选用的服务的表示形式,设计命令层次时,通常先从对服务的过程抽象着手,然后进一步修改它们,以适合具体应用环境的需要
③:精化命令的层次
为进一步修改完善初始的命令层次,应该考虑次序、整体部分关系、宽度和深度等因素
D:设计人机交互类
人机交互类与所使用的操作系统及编程语言密切相关
二:设计任务管理子系统
(1)必要性
许多对象之间往往存在相互依赖关系
在实际使用的硬件中,可能仅由一个处理器支持多个对象
(2)设计步骤
A:分析并发性
①:并发性
如果两个对象彼此间不存在交互,或它们同时接受事件,则它们在本质,上是并发的
②:方法
通过面向对象分析建立起来的动态模型,是分析并发性的主要依据
通过检查各个对象的状态图及它们之间交换的事件,能够把若干个非并发的对象归并到一条控制线中
③:控制线
控制线是一条遍及状态图集合的路径,在这条路径上每次只有一个对象是活动的。在计算机系统中用进程实现控制线。把多个任务的并发执行称为多任务
B:设计任务管理子系统
①:确定事件驱动型任务
某些任务是由事件驱动的,这类任务可能主要完成通信工作,具体任务有
任务处于睡眠状态,等待来自数据线或其他数据源的中断
一旦接收到中断就唤醒该任务,接收数据并把数据放入内存缓冲区或其他目的地,通知需要知道这件事的对象,然后该任务又回到睡眠状态
②:确定时钟驱动型任务
某些任务每隔一定时间间隔就被触发以执行某些处理,具体任务有
任务设置了唤醒时间后进入睡眠状态,等待来自系统的中断
接收到这种中断,任务就被唤醒并做它的工作,通知有关的对象,然后该任务又回到睡眠状态
③:确定优先任务
高优先级:有些服务是优先级的,为了在严格限定的时间内完成,把这类服务分离成独立的任务
低优先级:与高优先级相反,有些服务是低优先级的,属于低优先级处理。设计时用额外的任务把其分离出来
④:确定关键任务
关键任务是有关系统成功或失败的关键处理,这类处理通常都有严格的可靠性要求。处理方法为:在设计过程中用额外的任务把这样的关键处理分离出来,以满足高可靠性处理的要求
⑤:确定协调任务
当系统中存在三个以上任务时,就应该增加一个任务,用它作为协调任务。使用状态转换矩阵可以比较方便地描述该任务的行为。这类任务仅做协调工作,不要让它再承担其他服务工作
⑥:尽量减少任务数
⑦:确定系统资源需求
通过计算系统载荷,来估算所需要的固件的处理能力
综合权衡一致性、成本、性能以及未来的可扩充性和可修改性,决定资源需求
综合考虑各种因素,以决定哪些子系统用硬件实现,哪些子系统用软件实现
9.5.6设计数据管理子系统和设计类中的服务
一:设计数据管理子系统
(1)概念
数据库管理子系统:是系统存储或检索对象的基本设施,它建立在某种数据存储管理系统之上,并且隔离了数据存储管理模式的影响
(2)选择数据存储管理模式
A:文件管理系统
优点:文件管理系统是操作系统的一个组成部分,使用它长期保存数据具有成本低和简单的优点
缺点:文件操作的级别低,为提供适当的抽象级别还必须编写额外的代码,不同操作系统的文件管理系统往往有明显差异
B:关系数据库管理系统
优点:
理论基础坚实
提供了各种最基本的数据管理功能,例如中断恢复,多用户共享,多应用共享,完整性,事务支持等
为多种应用提供了一致的接口
标准化的语言
缺点:
运行开销大:即使完成简单的事务,也需要较长的时间
不能满足高级应用的需求:关系数据库管理系统是为商务应用服务的,商务应用中数据量虽大但数据结构却比较简单
与程序设计语言的连接不自然: SQL语言支持面向集合的操作,是一种非过程化的语言;然而大多数程序设计语言本质上却是过程性的,每次只能处理一个记录
C:面向对象数据库管理系统
扩展的关系数据库管理系统:在关系数据库的基础上,增加了抽象数据类型和继承机制,此外还增加了创建及管理类和对象的通用服务
扩展的面向对象程序设计语言:扩充了面向对象程序设计语言的语法和功能,增加了在数据库中存储和管理对象的机制。可以使用统一的面向对象观点进行设计,不需要区分存储数据结构和程序数据结构
(3)设计数据管理子系统
A:设计数据格式
文件系统:
**定义第一范式表:列出每个类的属性表;把属性表规范成第一范式, **从而得到第一范式表的定义
为每个第一范式表定义一个文件
测量性能和需要的存储容量
修改原设计的第一范式,以满足性能和存储需求
关系数据库管理系统:
定义第三范式表:列出每个类的属性表;把属性表规范成第三范式,从而得出第三范式表的定义
为每个第三范式表定义一个数据库表
测量性能和需要的存储容量
修改先前设计的第三范式,以满足性能和存储需求
面向对象数据库管理系统:
扩展的关系数据库途径: 使用与关系数据库管理系统相同的方法
扩展的面向对象程序设计语言途径: 不需要规范化属性的步骤B:设计相应的服务
文件系统:
被存储的对象需要知道打开哪个文件,怎样把文件定位到正确的记录上,怎样检索出旧值,以及怎样用现有值更新它们
定义一个 ObjectServer类,并创建它的实例
关系数据库管理系统:
被存储的对象,应该知道访问哪些数据库表,怎样访问所需要的行,怎样检索出旧值,以及怎样用现有值更新它们
定义一个 ObjectServer类,并声明它的对象
面向对象数据库管理系统:
扩展的关系数据库途径:使用与关系数据库管理系统相同的方法
扩展的面向对象程序设计语言途径:无须增加服务,只需给长期保存的对象加个标记,然后由面向对象数据库管理系统负责存储和恢复这类对象
二:设计类中的服务**(1)确定类中应有的服务**
A:确定服务的总体思想
对象模型是进行对象设计的基本框架。必须把动态模型中对象的行为以及功能模型中的数据处理转换成由适当的类所提供的服务
动态模型中状态图中的状态转换执行对象服务的结果
功能模型指明了系统必须提供的服务
B:确定操作目标对象的启发规则
如果某个处理的功能是从输入流中抽取一个值,则该输入流就是目标对象
如果某个处理具有类型相同的输入流和输出流,而且输出流实质上是输入流的另一种形式,则该输入输出流就是目标对象
如果某个处理从多个输入流得出输出值,则该处理是输出类中定义的一个服务
如果某个处理把对输入流处理的结果输出给数据存储或动作对象,则该数据存储或动作对象就是目标对象
C:确定处理归属的启发规则
如果处理影响或修改了一个对象,则最好把该处理与处理的目标联系在一起
考察处理涉及的对象类及这些类之间的关联,从中找出处于中心地位的类
(2)设计实现服务的方法
A:设计实现服务的算法
算法复杂度:选用复杂度较低(效率较高)的算法,但不能过分追求高效率,应以能满足用户需求为准
容易理解与容易实现:容易理解与容易实现的要求往往与高效率有矛盾,设计者应该对这两个因素适当折衷
易修改:预测将来可能做的修改,并在设计时预先做些准备
B:选择数据结构
选择能够方便、有效地实现算法的物理数据结构
C:算法与数据结构的关系
分析问题寻找数据特点,提炼出所有可行有效的算法
定义与所提炼算法相关联的数据结构
依据此数据结构进行算法的详细设计
进行一定规模的实验与评测
确定最佳设计
D:定义内部类和内部操作
增添一些用来存放在执行算法过程中所得出的中间结果的类,其需求陈述中没有提到。复杂操作往往可以用简单对象上的更低层操作来定义,因此,在分解高层操作时常常引入新的低层操作
9.5.7设计关联和设计优化
一:设计关联
(1)关联
A:定义
在对象模型中,关联是联结不同对象的纽带,它指定了对象相互间的访问路径
B:确定实现关联的策略
选定一个全局性的策略统一实现所有关联
分别为每个关联选择具体的实现策略,以与它在应用系统中的使用方式相适应
(2)使用关联的方式
A:关联的遍历
单向遍历
双向遍历
B:实现单向遍历
若关联的重数是一元的,则实现关联的指针是一个简单指针
若关联的重数是多元的,则需要用一个指针集合实现关联
C:实现双向关联
只用属性实现一个方向的关联:如果两个方向遍历的频度相差很大,而且需要尽量减少存储开销和修改时的开销,则这是一种很有效的实现双向关联的方法
两个方向的关联都用属性实现: 这种方法能实现快速访问。当访问次数远远多于修改次数时,这种实现方法很有效
用独立的关联对象实现双向关联:关联对象不属于相互关联的任何一个类,它是独立的关联类的实例
D:关联对象的实现
①:定义
用一个关联类来保存描述关联性质的信息,关联中的每个连接对应关联类的一个对象
②:方法
对于一对一的关联,关联对象可以与参与关联的任一个对象合并
对于一对多的关联,关联对象可以与多端对象合并
对于多对多的关联,关联链的性质不可能只与一个参与关联的对象有关
二:设计优化**(1)确定优先级**
A:必要性
系统的各项质量指标并不是同等重要的,必须确定各项质量指标的优先级,以便在优化设计时制定折中方案。系统的整体质量与制定的折中方案密切相关。最终产品成功与否,在很大程度上取决于是否选择好了系统目标
B:方法
在效率和清晰度之间寻求适当的折中方案。在折中方案中设置的优先级应当是模糊的
(2)提高效率的几项技术
增加冗余关联以提高访问效率
调整查询次序
保留派生属性
(3)调整继承关系
A:继承关系
继承关系能够为一个类族定义一个协议,并能在类之间实现代码共享以减少冗余。一个基类和它的子孙类在一起称为一个类继承。在面向对象设计中,建立良好的类继承是非常重要的。利用类继承能够把若千个类组织成一个逻辑结构
B:建立类继承
①:抽象与具体
在设计类继承时,很少使用纯粹自顶向下的方法,通常的做法如下:
创建满足具体用途的类,然后对它们进行归纳
归纳出一些通用的类以后,根据需要再派生出具体类
进行一些具体化的工作后,再次归纳
②:为提高继承程度而修改类定义
在一组相似的类中存在公共的属性和公共的行为时,可以把这些公共的属性和行为抽取出来放在一个共同的祖先类中,供其子类继承,在对现有类进行归纳的时候,要注意下述两点:
不能违背领域知识和常识
应该确保现有类的协议不变
③:利用委托实现行为共享
仅当存在真实的一般一特殊关系(子类确实是父类的一种特殊形式) 时,利用继承机制实现行为共享才是合理的
9.6面向对象实现
9.6.1面向对象实现概述和程序设计语言
一:面向对象实现概述
📌 (1)主要任务
把面向对象设计结果翻译成用某种程序语言书写的面向对象程序,测试并调试面向对象的程序
(2)面向对象程序质量的影响因素
面向对象设计的质量
采用的程序语言的特点
程序设计风格
(3)保证软件可靠性的方法
保证软件可靠性的主要措施是软件测试。面向对象测试的目标是用尽可能低的测试成本发现尽可能多的软件错误
二:程序设计语言
(1)面向对象语言的优点
A:面向对象设计结果的表达方式
面向对象语言:编译程序可以自动把面向对象概念映射到目标程序中
非面向对象语言:必须由程序员自己把面向对象概念映射到目标程序中
B:优点
一致的表示方法:面向对象开发基于不随时间变化的、一致的表示方法。 既有利于在软件开发过程中始终使用统一的概念,也有利于维护人员理解软件的各种配置成分
可重用性: 既可重用面向对象分析结果,也可重用相应的面向对象设计和面向对象程序设计结果
可维护性: 程序显式地表达问题域语义,对维护人员理解待维护的软件有很大帮助。在选择编程语言时,应该考虑的首要因素是哪个语言能最恰当地表达问题域语义
(2)面向对象语言的技术特点
在选择面向对象语言时应该着重考虑以下几种技术特点
A:支持类与对象概念的机制
①:内容
面向对象语言允许用户动态创建对象,并且可以用指针引用动态创建的对象。需要及时释放不再需要的对象所占用的内存
②:管理内存的方法
- 由语言的运行机制自动管理内存,即提供自动回收“垃圾”机制
- 由程序员编写释放内存的代码
B:实现聚集结构的机制
使用指针
使用独立的关联对象
C:实现泛化结构的机制
实现继承的机制
解决名字冲突的机制,即处理在多个基类中可能出现的重名问题
D:实现属性和服务的机制
实现属性的机制:
支持实例连接的机制
属性的可见性控制
对属性值的约束
实现服务的机制:
支持消息连接(表达对象交互关系)的机制
控制服务可见性的机制
动态联编(在发送消息前,无须知道接受消息的对象属于哪个类)
E:类型检查
①:分类
弱类型:语言仅要求每个变量或属性隶属于一个对象
强类型: 语法规定每个变量或属性必须准确地属于某个特定的类
②:强类型语言的优点
有利于在编译时发现程序错误,提高软件的可靠性
增加了优化的可能性,提高软件的运行效率
③:适用性
使用强类型编译型语言开发软件产品,使用弱类型解释型语言快速开发原型
F:类库
存在类库,许多软构件就不必由程序员从头编写了,为实现软件重用带来很大方便。主要分为以下几类
包含实现通用数据结构的类,即包容类
实现各种关联的类
独立于具体设备的接口类
用于实现窗口系统的用户界面类
G:效率
提高效率的方法有
使用拥有完整类库的面向对象语言
优化查找继承树查找过程,从而实现高效率查找
H:持久保存对象
①:原因
为实现在不同程序之间传递数据,需要保存数据
为恢复被中断了的程序的运行,首先需要保存数据
②:方法
在类库中增加对象存储管理功能
使程序设计语言语法与对象存储管理语法无缝集成
J:参数化类
①:定义
参数化类是使用一个或多个类型去参数化一个类的机制,如果程序语言提供一种能抽象出这类共性的机制,则对减少冗余和提高可重用性是大有好处的
②:方法
定义一个参数化的类模板
把数据类型作为参数传递进来
K:开发环境
编辑程序
编译程序或解释程序(最重要)
浏览工具
调试器
📌(3)选择面向对象语言的标准
将来能否占主导地位
可重用性
类库和开发环境
其他因素
9.6.2程序设计风格和测试策略
一:程序设计风格
(1)概念
A:良好的程序设计风格的重要性
能明显减少维护或扩充的开销
有助于在新项目中重用已有的程序代码
B:良好的面向对象程序设计风格内容
传统的程序设计风格准则
为适应面向对象方法所特有的概念而必须遵循的一些新准则
(2)提高可重用性
A:代码重用
内部重用:即本项目内的代码重用,主要是找出设计中相同或相似的部分,然后利用继承机制共享它们
外部重用:即新项目重用旧项目的代码,需要有长远眼光,反复考虑,精心设计
B:主要准则
①:提高方法的内聚
一个方法应该只完成单个功能。如果某个方法涉及两个或多个不相关的功能,则应该把它分解成几个更小的方法
②:减小方法的规模
③:保持方法的一致性
保持方法的一致性,有助于实现代码重用,功能相似的方法应该有一致的名字、参数特征、返回值类型、使用条件及出错条件等
④:把策略与实现分开
策略方法:策略方法应该检查系统运行状态,并处理出错情况,它们并不直接完成计算或实现复杂的算法。其紧密依赖于具体应用
实现方法:实现方法仅仅针对具体数据完成特定处理,用于实现复杂的算法。在执行过程中发现错误,它们只返回执行状态而不对错误采取行动。其相对独立于具体应用
⑤:全面覆盖
输入条件的各种组合都可能出现,应针对所有组合写出方法
一个方法对空值、极限值及界外值等异常情况也应能够做出有意义的响应
⑥:尽量不使用全局信息
⑦:利用继承机制
调用子过程:把公共的代码分离出来,构成一个被其他方法调用的公用方法。可以在基类中定义这个公用方法,供派生类中的方法调用
分解因子:提高相似类代码可重用性的一个有效途径,是从不同类的相似方法中分解出不同的代码,把余下的代码作为公用方法中的公共代码,把分解出的因子作为名字相同算法不同的方法,放在不同类中定义,并被这个公用方法调用
使用委托:继承关系的存在意味着子类“即是”父类,因此,父类的所有方法和属性都应该适用于子类,仅当确实存在一般一特殊关系时,使用继承才是恰当的,当逻辑上不存在一-般一特殊关系时,为重用已有的代码,可以利用委托机制
把代码封装在类中:重用通过其他方法编写的、解决同一类应用问题的程序代码的一个比较安全的途径是把被重用的代码封装在类中
(3)提高可扩充性
主要准则有:
封装实现策略:应该把类的实现策略封装起来,对外只提供公有的接口,否则将降低今后修改数据结构或算法的自由度
不要用一个方法遍历多条关联链:一个方法应该只包含对象模型中的有限内容。违反这条准则将导致方法过分复杂,既不易理解,也不易修改扩充
避免使用多分支语句:可以利用DO_CASE语句测试对象的内部状态,而不要用来根据对象类型选择应有的行为,否则在增添新类时将不得不修改原有的代码
精心确定公有方法
(4)提高健壮性
主要准则有:
预防用户的错误操作
检查参数的合法性
不要预先确定限制条件
先测试后优化
二:测试策略
(1)经典的测试策略
测试软件的经典策略是,从“小型测试”开始,逐步过渡到“大型测试”。用软件测试的专业术语描述,可以分为以下三步:
单元测试
集成测试
确认测试、系统测试
(2)面向对象测试策略
A:面向对象的单元测试
最小的可测试单元是封装起来的类和对象。测试面向对象软件时,不能再孤立地测试单个操作,而应该把操作作为类的一部分来测试。在测试面向对象的软件时,传统的单元测试方法是不适用的,不能再在“真空”中(即孤立地)测试单个操作
B:面向对象的集成测试
①:策略
基于线程的测试:把响应系统的一个输入或一个事件所需要的那些类集成起来。分别集成并测试每个线程,同时应用回归测试以保证没有产生副作用
基于使用的测试:不使用服务器类的独立类,把独立类都测试完之后,再测试使用独立类的下一个层次的类(称为依赖类)。对依赖类的测试一层一层次地测试,直至把整个软件系统构造完为止
②:集群测试
集群测试是面向对象软件集成测试的一个步骤。在这个测试步骤中,用精心设计的测试用例检查一群相互协作的类,这些测试用例力图发现协作错误
C:面向对象的确认测试
在确认测试或系统测试层次,不再考虑类之间相互连接的细节。面向对象软件的确认测试也集中检查用户可见的动作和用户可识别的输出。为了导出确认测试用例,测试人员应该认真研究动态模型和描述系统行为的脚本,以确定最可能发现用户交互需求错误的情景
三:设计测试用例
(1)测试类的方法
A:随机测试
B:划分测试
①:优点
采用划分测试方法可以减少测试类时所需要的测试用例的数量
②:流程
把输入和输出分类
设计测试用例以测试划分出的每个类别
③:方法
基于状态的划分:根据类操作改变类状态的能力来划分类操作
基于属性的划分:根据类操作使用的属性来划分类操作
基于功能的划分:根据类操作所完成的功能来划分类操作
C:基于故障的测试
(2)集成测试方法
A:多类测试
①:随机测试
对每个客户类,使用类操作符列表来生成一系列随机测试序列
对所生成的每个消息,确定协作类和在服务器对象中的对应操作符
对服务器对象中的每个操作符,确定传递的消息
对每个消息,确定下一层被调用的操作符,并把这些操作符结合进测试序列中
②:划分测试
应该扩充测试序列以包括那些通过发送给协作类的消息而被调用的操作
根据与特定类的接口来划分类操作
B:从动态模型导出测试用例
类的状态图可以帮助人们导出测试该类的动态行为的测试用例。通过导出大量的测试用例,保证该类的所有行为都被适当地测试了。在类的行为导致与一个或多个类协作的情况下,应该使用多个状态图去跟踪系统的行为
第十章 软件项目管理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oIxZuWMZ-1691070364404)(image/1691068764986_0bCd45NwBW.png)]