# 第一章 软件及其特点
# 软件
编写程序步骤
- 分析软件需求:文档 数据
- 设计软件系统:文档 数据
- 编写代码程序:数据 代码
不可或缺,相互依存
软件
软件是指在计算机系统的支持下,能够完成特定功能与性能的程序、数据和相关文档
文档
记录软件开发活动和阶段性成果,软件配置以及变更的阐述性资料
- 定义和理解软件
- 记录软件开发成果
- 辅助不同人员间的交流
软件需求文档、软件设计文档、软件测试文档、软件用户手册......
目的:阐述、发现问题、交流、管理
数据
数据是程序的加工处理对象和结果
✏️ 软件 程序 开发软件 编写程序
软件的规模和复杂性意味着要采用行之有效的方法 —— 工程方法
# 软件生命周期
从提出开发开始到开发出系统、运行维护以及最终退役的全过程
graph LR; | |
A[需求分析] --> B[软件设计]; | |
B --> C[编码实现]; | |
C --> D[软件测试]; | |
D --> E[部署使用]; | |
E --> F[使用维护]; |
# 软件特点
逻辑性
- 逻辑产品,思维获得的结果,不会磨损和老化
复杂性
- 规模大、运行复杂
设计开发
易变性
缺陷隐藏性
# 软件分类
应用软件、系统软件、支撑软件(辅助软件开发和运维)
# 开源软件
闭源软件
软件代码不对用户开放的一类软件,购买软件时只提供可运行软件或服务,没有提供源代码
Windows、Office
IBM、Oracle
开源软件
一种源代码可以自由获取和传播的计算机软件,其拥有者通过开源许可证赋予被许可人对软件进行使用、修改和传播的权力
- 好处:自由传播、激发创作者热情、降低成本(free of charge)
Linux、Mysql、Firefox
github, gitee, stack overflow
# 软件质量
软件质量是指软件满足给定需求的程度,它是产品生命线
可信软件的基本要求:
# 当前软件特征的变化
地位和作用:无处不在,创新工具,使能技术和重要利器,关键性基础设施
运行环境:
从前端的 PC 终端、可穿戴设备、智能手机到后端的云中心、高性能计算中心
从孤立、独立、局域和可控的计算环境到分布、开发、动态、难控、无处不在的计算环境
软件形态
系统之系统
诸多系统联盟和组合而成
生态系统
社会、信息、物理等要素共存
同构异构多样的系统联盟
要素异构,客观存在也是必然
大规模复杂信息技术系统是由大量相对独立、自我控制和管理的系统组装而成一系统联盟
开发系统联盟需要采用社会技术观点,借助系统工程的方法
非封闭环境 or 系统,是开发环境动态适应系统
环境和系统相互作用,要素、关系、联盟等持续变化,边界不明确
动态演化系统
软件子系统
由一组面向任务,服务于不同对象的子系统构成
每个子系统独立运作、通过互相交互实现全局任务
分布式系统四大形态
对等计算 | 区块链 | 网格计算 | 云计算 |
---|---|---|---|
泛化(old) | 泛化(new) | 集中化(old) | 集中化(new) |
生态系统
运行生态
软件的运行需要依靠 “生态” 的支撑,从计算和物理设备、社会系统到 OS 和运行环境
演化生态
软件的演化基于特定的 “生态” 支撑 ,从最初的 “源头” 到最新 “版本”
人员生态
涉及到大量、开放的介入人员,使用者、开发者、运维者等
软件生态:共同环境 + 诸多要素 + 独立演化 + 相互依存
软件系统的应用及示例
- 高性能计算 、信息物理系统 、智能机器人 、云计算 、健康医疗 、城市交通 、军事信息系统 、航空航天
# Summary
软件
程序 + 文档 + 数据
软件特点
逻辑产品、设计开发、需求易变、系统复杂、缺陷隐蔽
开源软件
代码可自由获取和传播,需遵循许可证,充分利用开源软件
软件质量
多要素,如正确性、可靠性、可信性等
软件发生的变化
地位和作用,形态和复杂性,运行环境,系统规模
# 第二章 软件工程发展脉络
# 软件工程产生背景
- 作坊式的个人创作
- 依靠个人能力
- 缺乏合作
- 关注计算存储时空利用, 精雕细琢
- 程序规模小且功能单一
- 无系统性方法和标准流程
- 大规模软件开发
- 当软件规模越大,上述挑战就越突出,面临的困难也就越多(规模大、复杂性高的软件)
- 代码量、人员管理、隐蔽性错误越积越多
- 当软件规模越大,上述挑战就越突出,面临的困难也就越多(规模大、复杂性高的软件)
Leading to
- 软件危机:进度延迟、质量不稳定、成本过高、维护困难、失败风险大
软件危机产生的根源
- 对软件这样一类复杂和特殊系统的认识不清
- 软件是新生事物,对其特点、规律性和复杂性认识不够
- 没有找到支持软件系统开发的有效方法
- 基础理论、关键技术、开发过程、支撑工具等
- 缺乏成功软件开发实践以及相应的开发经验
- 系统总结、认真分析、充分借鉴、吸取教训
软件开发迫切需要理论和方法指导,软件工程应运而生!
# 软件工程基本内涵
软件工程的诞生
时间:1968,地点:西德南部小城,事件:NATO 科技委出资召开的会议,人物:11 个国家 50 位代表参加
主题:如何解决软件危机,成果:提出了软件工程
软件工程产生动机
graph TD; | |
A[软件工程] -->|解决软件危机| B[软件系统开发]; | |
subgraph 要求; | |
C1[快速]; | |
C2[高效]; | |
C3[低成本]; | |
C4[高质量]; | |
end | |
A --> C1; | |
A --> C2; | |
A --> C3; | |
A --> C4; |
# 何为软件工程
软件工程:将系统的、规范的、可量化的方法应用于软件的开发、运行和维护的过程
软件是产品 (Product):面向用户,存在质量、成本、利润等特征
软件开发是一项工程 (Project):存在约束,需要质量保证,进行组织管理
要按工程化方法来组织软件生产
graph LR; | |
A[约束]-->B; | |
B[过程]-->C; | |
C[质量]-->D[成本]; |
软件开发方式的改变:从个体作坊式行为 ➡️ 基于团队的协同开发方式
# 软件工程三要素
# 过程
从管理的视角,回答软件开发、运行和维护需要开展哪些工作、按照什么样的步骤和次序来开展工作
# 方法学
从技术的视角,回答软件开发、运行和维护如何做的问题
为软件开发过程中的各项开发和维护活动提供系统性、规范性的技术支持
✏️ 结构化软件开发方法学 ✏️ 面向对象软件开发方法学 ✏️ 基于构件的软件开发方法学
# 工具
从工具辅助的视角,主要回答如何借助工具来辅助软件开发、运行和维护的问题
帮助软件开发人员更为高效地运用软件开发方法学来完成软件开发过程中的各项工作,提高软件开发效率和质量, 加快软件交付进度。
如需求分析、软件设计、编码实现、软件测试、部署运行、软件 维护、项目管理、质量保证等,简化软件开发任务
# 软件开发的本质
软件开发 = 软件创作 + 软件生产
基于软件开发者的经验和技 能,借助于智慧,进行自由创新;基于工程化的手段,遵循约束和规范
软件工程目标
在成本、进度等约束下,指导软件开发和运维,开发出满足用户要求的足够好软件
软件工程原则
抽象和建模
抽象:将与相关开发活动所关注的要素提取出来,不关心的要素扔掉,形成与该开发活动相关的软件要素
建模:基于特定的抽象,借助于建模语言(如数据流图、UML 等),建立起基于这些抽象的软件模型,进而促进对软件系统的 准确理解
模块化
- 将软件系统的功能分解和实现为若干个模块,每个模块具有独立 的功能,模块之间通过接口进行调用和访问。
- 模块内部高内聚,模块间松耦合
graph LR;
A[函数和过程]-->B;
B[对象类]-->C;
C[软构件]-->D[服务及镜像];
软件重用
在软件开发过程中尽可能利用已有的软件资源和资产(如函数 库、类库、构件库、开源软件、代码片段等)来实现软件系统
- 努力开发出可被再次重用的软件资源(如函数、类、构件等)
代码重用、设计重用、软件重用
信息隐藏
- 模块内部信息(如内部的语 句、变量等)对外不可见或不可访问,模块间仅仅交换那些为完成系统功能所必需交换的信息(如接口)
关注点分离:每一项开发活动中聚焦于某个关注点,整合多个不同视点的开发结果
分而治之:对复杂软件系统进行 分解,形成一组子系统;然后通过整合子系统的问题解决得到整个 系统的问题解;有助于简化复杂软件系统的开发,降低软件开发复杂性,从而提 高软件开发效率,确保复杂软件系统的质量。
双向追踪原则
- 当某个软件制品发生变化时,一方面要追踪这种变化会对那些软件制品产生影响,进而指导相关的开发和维护工作,此为正向追踪;另一方面要追踪产生这种变化的来源
工具辅助:利用软件工具来辅助软件开发和维护工作是一项行之有效的方法
工欲善其事必先利其器
软件工程原则: 抽象和建模、模块化、软件重用、信息隐藏、 关注点分离、分而治之、双向追踪、工具辅助
# 软件工程的发展历程
# 软件工程发展的技术特点
抽象的层次越来越高、软件重用的粒度越来越大、软件开发理念的不断变化
软件工程多学科交叉
软件工程的变与不变
- 目标、基本原则不变;理解认识改变,手段方法改变,学科进一步交叉融合
# Summary
软件工程产生的背景和目的
软件危机,持续存在,关注点不同
软件工程的本质
软件视为产品,软件开发视为工程、创作和生产相结合的过程
三要素:过程、方法学和工具
软件工程的基本原则
总则
抽象和建模、模块化、软件重用、信息隐藏、 关注点分离、分而治之、双向追踪、工具辅助
软件工程的发展
不同阶段和时期,不同的思想和技术
软件工程教育
软件工程知识体系及课程特点
# 第三章 软件过程模型
review
软件的概念:文档、数据和程序的集合
软件的特点:逻辑产品、需求多变、设计开发、缺陷隐蔽性
软件生命周期:经历多个不同的阶段
闭源软件和开源软件:实践状况及优势
软件 程序;开发软件 编写程序;
生命周期:从提出开发开始到开发出系统、运行维护以及最终退役的全过程
# 何为软件过程模型
# 软件开发的特点
✏️基于智力的协作过程
- 智力活动:基于逻辑思维来构造软件
- 交流协作:软件工程师、用户间的交流和讨论
🪄软件项目内在复杂性
- 介入的人多、考虑的内容多、产生的制品多
- 不同要素间存在关联
🎢循序渐进的开发过程
- 开展有序的开发活动,如编码、分析、设计
- 体现了工程的思想:按步骤、分阶段
# 软件过程
- 过程:获得 + 关系(次序……)
- 软件过程:一系列有序软件开发活动(技术活动和管理活动)
# 软件过程模型 Software Process Model
开发活动
- 任务、目标、输入和输出
- 投入人员、工具、资源和成本等
- 活动间的关系和次序
# 有哪些软件过程模型
瀑布模型、增量模型、迭代模型、原型模型、螺旋模型
需要系统、规范性的软件过程模型的指导;每种软件过程模型有其各自的特点和适用的场所
# 瀑布模型 Waterfall Model
# Waterfall
- 需求分析→概要设计→详细设计→编码实现→集成测试→确认测试
- 适合于需求易于定义、 不易变动的软件系统
# Detail
需求分析 (Requirement Analysis)—— 问题是什么
定义软件需求,包括功能、非功能需求
产出:软件需求模型、软件需求文档、软件确认测试计划 —— 文档类的软件制品
概要设计 (Architecture Design)—— 问题如何解决
- 建立软件总体架构、制定集成测试计划
- 产出:软件概要设计模型、软件概要设计文档、软件集成测试计划 —— 文档类的软件制品
详细设计 (Detailed Design)—— 问题如何解决
- 设计模块内部细节 (算法、数据结构),制订单元测试计划
- 产出:软件详细设计模型、软件详细设计文档、单元测试计划 —— 文档类的软件制品
编程实现 (Implementation)—— 实际解决问题
- 编写程序代码并进行单元测试和调试
- 产出:经过单元测试的源程序代码 —— 程序类的软件制品
集成测试 (Integration Test)—— 问题解决如何?软件有缺陷吗?
- 组装软件模块并进行测试以发现问题
- 产出:经过集成测试、修复缺陷的源程序代码,集成测试报告 —— 数据、文档和代码类的软件制品
确认测试 (Validation Test)—— 问题解决如何?软件有缺陷吗?
- 测试软件是否满足用户需求
- 产出:经过确认测试、修复缺陷后的代码,软件确认测试报告 —— 数据、文档和代码类的软件制品
# Limitation
软件需求具有易变、多变的特点
瀑布模型的需求确定,过于理想化,缺乏变通难以应对变化
# 改进的瀑布模型:带反馈和回溯
- 后期活动发现有问题后,可返回到前面活动加以解决
- 提高了过程模型的灵活性
- 不足
- 软件开发处于动荡之中
- 需等到所有功能实现后, 才能得到可运行软件
# 增量模型 (Incremental Model)
需求设计→概要设计→详细设计→编码实现→集成测试→确认测试
详细设计→编码实现→集成测试可以增量(执行 n 次)
- 渐进式:增量的实现软件功能
- 优点:渐进快速交付,并行开发,提高效率
- 局限性:小项目可能不适用;软件需求确定且不易于变化
# 迭代模型 (Iterative Model)
迭代 n 次:需求分析→概要设计→详细设计→编码实现→软件测试
每次迭代完成部分可确定的软件需求
- 每次迭代是一完整过程,体现了小步快跑的开发理念,适合需求难导出、不易确定且持续变动的软件
- 局限性:迭代多少次不确定、管理较为复杂
增量过程模型与迭代过程模型有何区别?
增量模型(横向扩展)迭代模型(纵向扩展)
软件需求获取是一关键和瓶颈问题
- 软件需求非常关键 —— 软件开发的基础、验收的依据
- 用户讲不清楚软件需求有哪些、是什么?
- 用户与软件工程师对软件需求理解存在偏差(对软件需求描述的歧义性、二义性、不准确等造成的)
# 原型模型 (Prototype Model)
软件原型作为交流载体和媒介 、支持用户参与到软件开发中持续、渐进地导出用户要求
适合于需求难导出、不易确定且持续变动的软件
缺点:对于分析人员要求高,既需要懂客户,也需要懂技术,还要懂管理,把握软件开发的约束性条件。
# 螺旋模型 (Spiral Model)
软件风险:使软件开发受到影响和损失、 甚至导致失败的、可能会发生的事件
- 集成迭代模型和原型模型,引入风险分析,风险驱动的过程模型
- 每个迭代四个阶段,若干活动
- 适合于需求不明确、开发风险高、开发过程中需求变更大的软件项目
- 不足:管理复杂
# 不同软件模型的特点
模型名称 | 指导思想 | 关注点 | 适合软件 | 管理难度 |
---|---|---|---|---|
瀑布模型 | 提供系统性指导 | 与软件生命周期相一致 | 需求变动不大、较为明确、易可预先定义的应用 | 易 |
原型模型 | 以原型为媒介指导用户的需求导出和确认 | 需求获取、导出和确认 | 理解需求难以表达清楚、不易导出和获取的应用 | 易 |
增量模型 | 快速交付和并行开发 | 软件详细设计、编码和测试的增量式完成 | 需求变动不大、较为明确、易可预先定义的应用 | 易 |
迭代模型 | 多次迭代,每次仅针对部分明确软件需求 | 分多次迭代来开发软件,每次仅关注部分需求 | 需求变动大、难以一次性说清楚的应用 | 中等 |
螺旋模型 | 集成迭代模型和原型模型,引入风险分析 | 软件计划制定和实现阶段,软件风险管理 | 开发风险大、需求难以确定的应用 | 难 |
# 敏捷软件开发方法 (Agile Method)
重视人和交互、重视可运行软件系统、重视客户合作、重视响应用户需求变化。少写软件文档,以代码为中心,快速响应变化
准则
快速反馈、简单性假设、逐步修改、提倡更改、优质工作
代表方法
自适应开发、水晶方法、特征驱动开发、SCRUM、极限编程
# Summary
软件开发需要过程指导
明确步骤、活动、次序、关系
多样化的软件过程模型
瀑布、增量、迭代、原型、螺旋等
选择合适的软件过程模型
- 考虑软件项目的特点和要求
- 结合软件过程模型的优缺点
- 考虑开发团队的经验和水平
# 第四章 软件需求分析基础
软件利益相关方→软件需求→软件开发→软件产品
软件利益相关方是软件需求的价值所在
软件需求是软件开发的开发依据、是软件产品的验收标准
# 需求挖掘的来源:利益相关方
利益相关方 (stakeholder)
- 从软件系统中受益或与软件系统相关的人、组织或者系统
- 受益:使用、获益、盈利
- 相关:发生操作和交互、存在关联性
软件利益相关方的表现形式
- 用户:最终使用软件的人
- 客户:从中获取利益的组织
- 系统:与待开发系统进行交互的系统
- 开发者:负责开发软件系统的人
实例
空巢老人看护软件的利益相关方
- 老人、家属、医生、投资方、机器人
- 用户:老人、家属、医生
- 客户:投资方
- 系统:机器人
# 软件需求的类别
软件功能性需求 (Functional)
能够完成的功能及在某些场景下可展现的外部可见行为或效果
软件质量方面的需求 (Quality)
- 外部质量属性:外部可展现的,用户、客户等会非常关心,如运行性能、可靠性、易用性等
- 内部质量属性:隐藏在内部的,软件开发工程师会非常关心,如可扩展性、可维护性、可理解性
软件开发约束性需求 (Constraint)
开发成本、交付进度、技术选型、遵循标准等方面提出的要求
非功能需求:产品需求(可用性、效率、可移植性等)、机构需求、外部需求(道德、法律)
获取软件需求方法
访谈和会议、调查问卷、现场观摩、分析业务资料、软件原型、群体化方法(集思广益)
# 面向对象需求分析方法
# 需求工程
通过一系列的过程、策略、方法学和工具尽可能获得准确、一致和完整的软件需求
抽象、建模、分析
# 面相对象的 4 个要点
- 认为客观世界是由各种对象组成的,任何事物都是对象,复杂的对象可以由比较简单的对象以某种方式组合而成。
- 把所有对象都划分成各种对象类(类 CLASS),每个对象类都定义了一组数据和一组方法。
- 按照子类(派生类)和父类(基类)的继承关系,把若干个对象类组成一个层次结构的系统(类等级)。
- 对象彼此之间仅能通过传递消息互相联系。
万物皆对象
高度分工,高度复用,Only 消息传递
面向对象 = 对象 + 类 + 继承 + 传递消息
只有同时使用对象,类,继承,消息的方法才是真正面相对象方法
对象 + 传递消息 = 基于对象的方法 类 + 继承 = 基于类的方法
# 对象 (Object),属性(Attribute),方法(Method)
- 对象:对象是具体、有意义的、存在的实体
- 属性 (Attribute):对象的性质,其值定义了对象状态
- 操作 (Operation):也称方法,对象行为,表示对象提供的服务
# 面向对象的概念 - 类(Class)与实例(Instance)
- 类是对一组具有相同特征对象的抽象
- 对象与类的关系
- 对象是类的实例,类是创建对象的模板
- 类是静态的抽象;对象是动态、可运行的实体
# 面向对象的概念 - 消息 (Message)
消息传递是实现对象间通讯和协作的基本手段
一个对象向另一个对象发送消息来请求其服务
消息描述:接收对象名、操作名和参数: received-obj.msg-name (para.)
消息类型
- 同步消息:请求者需要等待响应者的处理结果
- 异步消息:请求者发出消息后继续工作,无需等待
# 面向对象的核心概念
# 封装(Encapsulation)
面向对象的程序中把数据和实现操作的代码集中起来放在对象内部。 使用一个对象的时候,只需知道它向外界提供的接口形式,而无需知道它的数据结构细节和实现操作的算法。
具有封装性的条件:
- 有一个清晰的边界;
- 有明确的接口;
- 受保护的内部实现
# 继承(Inheritance)
- 表示类与类间的一般与特殊关系
- 子 (特殊) 类可共享父 (一般) 类的属性和操作
- 借助继承可形成系统的层次化类结构
继承的传递性:继承具有传递性,如果类 C 继承类 B,类 B 继承类 A,则类 C 继承类 A。
uml: 直线三角空心箭头 指向父类
# 多态性
class Animal { | |
public void makeSound() { | |
System.out.println("Animal makes a sound"); | |
} | |
} | |
class Dog extends Animal { | |
@Override | |
public void makeSound() { | |
System.out.println("Dog barks"); | |
} | |
} | |
class Cat extends Animal { | |
@Override | |
public void makeSound() { | |
System.out.println("Cat meows"); | |
} | |
} | |
public class PolymorphismExample { | |
public static void main(String[] args) { | |
Animal animal1 = new Dog(); | |
Animal animal2 = new Cat(); | |
animal1.makeSound(); // Dog barks | |
animal2.makeSound(); // Cat meows | |
Animal ref; | |
ref = animal1; | |
ref.makeSound(); // Dog barks | |
ref = animal2; | |
ref.makeSound(); // Cat meows | |
} | |
} |
可以通过父类实例化子类,或者引用子类
多态性
多态是面向对象编程的重要特性之一,它允许不同类型的对象对同一消息作出不同的响应。通过父类引用指向子类对象,可以在运行时根据实际的对象类型来决定调用哪个具体的方法实现,增加了程序的灵活性和可扩展性。
this指针
class Animal { | |
public void makeSound() { | |
System.out.println("Animal makes a sound"); | |
} | |
public void callMakeSound() { | |
this.makeSound(); | |
} | |
} | |
class Dog extends Animal { | |
@Override | |
public void makeSound() { | |
System.out.println("Dog barks"); | |
} | |
// @Override | |
// public void callMakeSound() { | |
// System.out.println("In Dog's showThis(), this is a Dog object."); | |
// // 调用父类的方法,展示不同的 this | |
// super.showThis(); | |
// } | |
} |
this 指针严格执行本体函数,而非覆盖过后的
super 严格指向父类,而非覆盖后的
对象声明是为对象的引用创建一个空间,而对象生成则是创建一个类的实例,即为对象分配空间,并将对象空间的地址赋给其引用
# 引用
public class PolymorphismExample { | |
public static void main(String[] args) { | |
A a = new B(); // a 是 A 类型的引用, 但对象是 B 类型(这里 B 是 A 的子类) | |
B b = (B)a; // 将 a 转为 B 的引用 | |
b.fun3(); //fun3 是 B 独自实现的,正确 | |
// A a = new A (); // a 是 A 类型的引用, 但对象是 A 类型(这里 B 是 A 的子类) | |
// B b = (B) a; // 将 a 转为 B 的引用 | |
//b.fun3 (); //fun3 是 B 独自实现的,错误,对象是 A 没有 B 的方法!! | |
} | |
} |
“引用” 所能够调用的 “方法” 取决于 “引用” 的类型, 而该 “方法” 如何具体实现取决于 "对象" 的类型
# 覆盖 (Override)
子类增加或重新定义所继承的属性或方法,从而用新定义的属性和方法来覆盖所继承的、来自父类中的属性或方法
public class A{ | |
String name; | |
public String getValue(){ | |
return “Value is:” + name; | |
} | |
} | |
public class B extends A { | |
String address; | |
public String getValue () { | |
return “Value is:” + address; | |
} | |
// 子类 B 重新定义所继承的方法 getValue | |
} |
# 重载 (Overload)
一个类中有多个同名操作,但它们在操作数或操作数类型上有区别,系统根据实参引用不同方法
Public class A { | |
int age; | |
String name; | |
public void setValue(int agePara){ | |
age = agePara; | |
} | |
public void setValue(String namePara){ | |
name = namePara; | |
} | |
} |
# 初步软件需求
# 自然语言描述
自然语言是最为常用的需求描述手段;描述软件的功能性需求、质量需求和开发约束需求等
- 不具体
- 不准确
- 有二义
- 不直观
# 软件原型描述
- 优势
- 直观、可展示和可操作
- 不足
- 主要以操作界面的形式展示软件需求的 梗概,主要是软件与用户之间的输入和 输出,业务的大致流程,无法描述软件需求的具体细节
# UML 建模语言描述
- Unified (统一):提取不同方法中最好建模技术,如 OMT (James Rumbaugh),Booch method (Grady Booch ) 和 OOSE (Ivar Jacobson);采用统一、标准化的表示方式,支持不同 人员之间的交流;
- Modeling (建模):基于面向对象的概念和抽象,对现实系统和软件系统进行可视化建模; 、
- Language (语言):提供图形化的图符,用来表示软件系统的一种语言。
视点 | 图 (diagram) | 说明 |
---|---|---|
结构 | 包图 (package diagram) | 从包层面描述系统的静态结构 |
类图 (class diagram) | 从类层面描述系统的静态结构 | |
对象图 (object diagram) | 从对象层面描述系统的静态结构 | |
构件图 (component diagram) | 描述系统中构件及其依赖关系 | |
状态图 (statechart diagram) | 描述状态的变迁 | |
行为 | 活动图 (activity diagram) | 描述系统活动的实现 |
通信图 (communication diagram) | 描述对象间的消息传递与协作 | |
顺序图 (sequence diagram) | 描述对象间的消息传递与协作 | |
部署 | 部署图 (deployment diagram) | 描述系统在工作在物理运行环境中的部署情况 |
用例图 | 用例图 (use case diagram) | 从外部用户角度描述系统功能 |
# RUP(Rational Unified Process)
- 初始阶段主要任务是理解和分析 需求,生成用例模型框架;
- 精化阶段是完成高优先级的用例 开发,完善用例模型;
- 构建阶段真是多次迭代,完成不 同优先级用例开发;
- 交付阶段则是进行各种功能,性 能测试,进行产品化部署,完成 系统开发。
# 用例图描述
用例:文本形式的情节描述,用以说明某参与者使用系统以实现某一特定目标的情形,描述的是外部行为者所理解的系统功能。
用例建模:用于描述一个系统应该做什么(功能需求),用用例图来描述 (可能有多幅)
用例图:把系统看做黑盒子,给出了用户所感受到的系统行为,但不描述系统如何实现该功能,驱动了需求分析之后各阶段的开发工作。
# 用例建模
在 UML 中,用例图的主要元素是系统、用例、行为者以及用例之间的关系:
- 系统边界、用例
- 执行者 (参与者):可能使用这些用例的人或外部系统,参与者与用例连接表示参与者使用了该用例
- 模型元素间关系:关联、扩展、包含、泛化等
# 系统
代表系统的方框的边线表示系统的边界,用于划定系统的功能范围、定义了系统所具有的功能
描述该系统功能的用例置于方框内,代表外部实体的行为都置于方框外
# 执行者 (Actor)
行为者是指与系统交互的人或其他系统,它代表外部实体
在用例图中,连接行为者和用例的直线,表示两者之间交换信息,称为通信联系
单个行为者可与多个用例联系,一个用例也可与多个行为者联系
# 用例 (Use Case)
用例是一个类,它代表一类功能而不是使用该功能的某个具体实例
- 用例代表某些用户可见的功能,实现一个具体的用户目标;
- 用例总是被行为者启动的,并向行为者提供可识别的值;
- 用例必须是完整的
通常把用例的实例称为脚本
# 用例间的关系
扩展关系
向一个用例中添加一些动作后构成了另一个用例,这两个用例之间的关系就是扩展关系 。后者继承前者的一些行为,通常把后者称为扩展用例,用例之间的扩展关系图示为带构造型 <<extend>> 的关系
-->
包含关系
当一个用例包含另一个用例的全部步骤时,这两个用例之间就构成了包含关系,用带构造型 <<include>> 的关系
-->
泛化关系
一个一般用况与一个更特殊的用况之间的关系,特殊用况可继承一般用况的特征,用
—▷
表示
步骤:定义系统 (总体范围);确定参与者;确定用例;描述用例;定义用例间的关系;确认 模型。
# 需求分析过程
初步软件需求:分析和确定软件需求优先级,得到软件需求优先级列表;
软件需求优先级列表:分析和建立软件需求模型;
软件需求模型:文档化软件需求,形成软件需求文档;
软件需求文档:对软件需求进行确认和验证。
得到确认和验证后的软件需求模型和文档。
整个过程不断迭代。
需求工程的输出软件需求制品
一、软件原型
以可运行软件的形式,直观地展示软件的业务工作流程、操作界面、用户的输入和输出等方面的功能性需求信息。
二、软件需求模型
以可视化的图形方式,从多个不同视角直观地描述软件的功能性需求,包括用例模型、用例的交互模型、分析类模型、状态模型等。
三、软件需求文档
以图文并茂的方式,结合需求模型以及需求的自然语言描述,详尽刻画软件需求,包括功能性和非功能性软件需求以及软件需求的优先级列表等。
# Summary
- 软件需求来自软件利益相关者,表现形式多样且多变易变。
- 需求工程是用工程手段支持需求的获取、分析、建模和文档化。
- 面向对象需求分析方法学的基本思想是系统中的对象及其功能、行为和协作,基本概念有对象、类、消息传递等,建模语言为 UML 及不同视角的图。
- 分析软件需求的步骤有分析确立需求优先级、建立需求模型、撰写需求规格说明书、评审需求文档。🎖️
# 第五章 软件分析
顺序图、类图、状态图
分析软件需求过程
软件需求文档
# 软件分析的任务
基于初步软件需求,进一步精化和分析软件需求,确定软件需求优先级,建立软件需求模型,发现和解决软件需求缺陷,形成高质量的软件需求模型和软件需求规格说明书
# 类图
类名 、属性 、操作
结点:表示系统中的 类(或接口)及其属 性和操作
边:类之间的关系
在 UML 类图中,常见的有以下几种关系: 泛化(Generalization), 实现 (Realization),关联(Association),聚合(Aggregation),组合 (Composition),依赖 (Dependency)。
各种关系的强弱顺序: 泛化 = 实现 > 组合 > 聚合 > 关联 > 依赖
在 UML(Unified Modeling Language)中,类之间有多种关系来表示不同的关联方式。
- 关联 (Association):
- 表示两个类之间的基本关系,通常是某个类包含对另一个类的引用。
- 关联可以是单向或双向的,表示对象之间的通信。
- 符号:实线,可能有箭头(单向关联)或没有箭头(双向关联)。
—
- 聚合 (Aggregation):
- 表示 “整体 - 部分” 关系,但部分可以独立于整体存在。
- 例如,班级和学生之间的关系,学生属于班级,但学生可以独立存在。
- 符号:带空心菱形的实线,菱形指向整体的一端。
-◇
组合 (Composition):
- 表示 “整体 - 部分” 关系,部分不能独立于整体存在。更强的一种整体与部分间的拥有关系,整体负责部 分的创建和删除
- 例如,房间和墙壁之间的关系,房间消失时,墙壁也随之消失。
- 符号:带实心菱形的实线,菱形指向整体的一端。
-◆
继承 / 泛化 (Generalization):
- 表示类之间的 “is-a” 关系,子类继承父类的属性和方法。
- 例如,动物类和狗类之间的关系,狗是动物的一种。
- 符号:空心三角形箭头指向父类。
-▷
实现 (Realization):
- 表示类与接口之间的关系,类实现接口的行为。
- 例如,飞行接口和鸟类之间的关系,鸟实现了飞行的接口。
- 符号:带空心三角形的虚线箭头指向接口。
- - - -▷
依赖 (Dependency):
- 两个元素之间的依赖关系。
- 例如,方法中使用的参数类型或局部变量类型。
- 符号:虚线箭头,箭头指向被依赖的类 / 被使用的类。
--->
注意数字关系
# 状态图
状态图:是一种由状态、变迁、事件和活动组成的状态机,用来描述类的对象所有可能的状态以及时间发生时状态的转移条件。
状态变量:是状态机图所显示的类的属性,也可以是临时变量
活动:列出了处于该状态时要执行的事件和动
UML 状态图(State Diagram)用于描述对象在生命周期中的状态变化,尤其是在响应事件或条件时的变化。它适合用于模型化具有明确状态转换的复杂对象行为,通常用于描述单个对象的状态机。
# UML 状态图的基本组成要素
状态 (State):
- 表示对象在生命周期中的某个特定状态或条件。状态用带标签的矩形框表示。
- 每个状态可以包含进入活动 (entry action)、退出活动 (exit action)、内部活动 (internal activity)、以及子状态。
初始状态 (Initial State):
- 表示对象在生命周期中的起始状态。用一个填充的黑色圆圈表示。
- 初始状态指向第一个有效的状态。
终止状态 (Final State):
- 表示对象生命周期的结束。用一个黑色圆圈,外部包裹一个空心圆圈来表示。
- 当状态机到达终止状态时,对象的生命周期就结束了。
转换 (Transition):
- 表示状态之间的转换关系,即对象在满足某些条件或事件触发时从一个状态迁移到另一个状态。
- 转换用带箭头的线表示,并可标注触发事件、条件守护 (guard condition)、以及动作 (action)。
- 例如:
事件名[条件] / 动作
。
事件 (Event):
- 是状态之间发生转换的触发因素,可能是用户输入、方法调用或系统触发。
- 事件通常写在转换箭头旁边。
动作 (Action):
- 是在状态转换过程中执行的活动。可以在转换箭头上标注执行的动作。
子状态 (Substate):
- 状态图支持嵌套状态,称为复合状态 (Composite State)。复合状态中包含的子状态可以是顺序 (Sequential) 的,也可以是并行 (Concurrent) 的。
复杂状态机行为的系统时非常有用,比如电梯控制系统、交易处理系统等。
# 顺序图
交互图的一种,描述了对象之间消息发送的先后顺序,强调时间顺序。序列图的主要用途是把用例表达的需求,转化为进一步、更加正式层次的精细表达。用例常常被细化为一个或者更多的序列图。同时序列图更有效地描述如何分配各个类的职责以及各类具有相应职责的原因。
- 描述对象间的消息交互序列
- 纵向:时间轴,对象及其生命线 (虚线),活跃期 (长条矩形)
- 横向:对象间的消息传递,用一个矩形框表示,框内标有对象名。名称可带下划线,意味 着序列图中的生命线代表一个类的特定实例。
- 对象间的通信用对象生命线之间的水平消息线来表示,消息箭头的形状表明消息的类型。 箭头以时间顺序在图中从上到下排列。
# 软件分析过程
# 面向对象分析
面向对象分析,就是抽取和整理用户需求,并建立问题域精确模型的过程。
面向对象建模得到的模型包含对象的三个要素(子模型):静态结构(对象模型), 交互次序(动态模型),和数据变换(功能模型)。
对象模型
定义做事情的实体 —— 静态数据结构
用例图
动态模型
规定什么时候做 —— 执行操作
状态图、序列图、活动图
功能模型
系统应该做什么 —— 数值变化
用例图
用面向对象方法开发软件,在任何情况下,对象模型始终都是最重要、最基本、最核心的
# 构建对象模型
复杂问题(大型系统)的对象模型由下述五个层次组成:主题层(范畴层)、结构层, 类 & 对象层,属性层和服务层。
- 找出类 -&- 对象;定义属性;定义服务;识别结构;识别主题;反复修改。
定义结构
对象(以及它们的类)与外部的关系有如下四种:
(1)一般 - 特殊关系(继承关系):即对象之间的分类关系,用一般 - 特殊结构表示;
(2)整体 - 部分关系:即对象之间的组成关系,用整体 - 部分结构表示;
(3)静态连接关系:即通过对象属性所反映出来的联系,用实例连接表 示;
(4)动态连接关系:即对象行为之间的依赖关系,用消息连接表示。
# 动态模型
每个类的动态行为用一张状态图来描绘,各个类的状态图通过共享事件合并起来(交互图),从而构成系统的动态模型。
动态模型是基于事件共享而互相关联的一组状态图的集合。
行为分类
系统行为 — 如创建、删除、复制、转存。
对象自身的简单行为 —— 如读、写属性值。
对象自身的复杂行为 —— 如计算或监控。
# 功能模型
功能模型表示变化的系统的 “功能” 性质,它指明了系统应该 “做什么”,因此更直接地反映了用户对目标系统的需求。
功能模型由一组数据流图组成。一般说来,与对象模型和动态模型比较起来,数据流图并没有增加新的信息,但是建立功能模型有助于软件开发人员更深入地理解问题域,改进和完善自己的设计。
# 小结
三个模型从三个不同的角度对所要开发的系统进行了描述,三个模型相互补充、相互配合,使得开发人员能够更全面的认识系统。
对象模型:以对象、属性、关系和操作形式描述系统结构;
动态模型:描述系统的内部行为;
功能模型:从户用的观点描述系统功能。
功能模型指出发生了什么,动态模型确定什么时候发生,而对象模型确定发生的客体。
# Summary
- 分析软件是要精化和深化软件需求
- 基于初步软件需求,循序渐进
- 确保软件需求的完整性、一致性和准确性
- UML 提供的、用于描述软件需求的图
- 用例图、交互图、分析类图、状态图
- 软件需求分析的难点
- 软件需求不易捕获
- 软件变化是无可避免
需求变更永无停止!!!
# 第六章 软件设计基础
# 软件设计的质量要求
软件设计是一个创作的过程
一个软件需求会有多种软件设计方案
- 高可靠性
- 高效率
- 高可维护性
- 高可理解性
# 软件设计基本原则
总则
- 抽象与逐步求精
- 模块化,高内聚度、低耦合度
- 信息隐藏
- 多视点和关注点分离
- 软件重用
- 迭代设计
- 可追踪性
# 抽象原则
在认识事物、分析和解决问题的过程中,忽略那些与当前研究目标不相关的部分,以便将注意力集中于与当前目标相关的方面
不要过早地考虑细节
抽象是管理和控制复杂性的基本策略
体系结构设计抽象→类设计抽象→算法设计抽象
结构性 全局性 关键性
→ 过程性 局部性 细节性
# 模块化、高内聚度和低耦合度原则
将软件系统的整体结构分解为一组相关模块
- 每个模块实现单一的功能
- 模块内部强内聚、模块之间低耦合
# 例题
模块独立性是软件模块化所提出的要求,衡量模块独立性的度量标准是。
对软件的过分分解,必然导致.
模块中所有成分引用共同的数据,该模块的内聚度是.
一个模块把开关量作为参数传送给另一模块,这两个模块之间的耦合是.
# 信息隐藏原则
模块应该设计得使其所含的信息对那些不需要这些信息的模块不可访问;模块间仅仅交换那些为完成系统功能所必需交换的信息
- 模块的独立性更好
- 支持模块的并行开发(设计和编码)
- 便于测试和维护,减少错误向外传播
- 便于增加新的功能
模块只提供对外接口,不 提供内部实现细节
# 关注点分离原则
设计师将若干性质不同的关注点分离开来,以便在适当的时间处理不同的关注点,随后将这些关注点整合起来,形成局部或者全局性的设计结果
防止 “胡子眉毛一把抓”
# 软件重用原则
尽可能地重用已有的软件资产来实现软件系统的功能,同时要确保所开发的软件系统易于为其他软件系统所重用
支持软件重用的技术手段
- 封装、接口、继承、多态等
软件重用的对象
- 过程和函数、类、软构件、开源软件
- 软件设计模式、软件开发知识
# 软件设计的其他原则
Others
- 设计可追溯到分析模型
- 经常关注待建系统的架构
- 数据设计和功能设计同样重要
- 必须设计接口
- 用户界面设计必须符合最终用户要求
- 设计表述要尽可能易于理解
- 设计应该迭代进行
# 第七章 面向对象的软件设计
# 软件体系结构设计
常用软件体系结构风格
# 分层体系结构风格
界面,业务,服务
低层向上层提供服务,上层向下层请求服务 —— 交互与约束
合理地设计抽象层次和组织软构件是关键
# 管道与过滤器风格
构件
将软件功能实现为一系列处理步骤,每个步骤封装在一个过滤器构件中
连接子
相邻过滤器间以管道连接,一个过滤器的输出数据借助管道流向后续过滤器,作为其输入数据
数据
软件系统的输入由数据源(data source)提供
软件最终输出由源自某个过滤器的管道流向数据汇(data sink)
典型数据源和数据汇包括数据库、文件、其他软件系统、物理设备等
graph LR; | |
源代码-->A[词法分析]; | |
A-->B[语法分析]; | |
B-->C[生成中间码]; | |
C-->D[代码优化]-->可执行代码; |
- 自然地解决具有数据流特征的软件需求
- 可独立地更新、升级过滤器来实现软件系统的扩展和进化
# 黑板风格
将软件系统划分为黑板、知识源和控制器三类构件
- 黑板:负责保存问题求解过程中的状态数据,并提供这些数据的读写服务
- 知识源:负责根据黑板中存储的问题求解状态评价其自身的可应用性,进行部分问题求解工作,并将此工作的结果数据写入黑板
- 控制器:负责监视黑板中不断更新的状态数据,安排(多个)知识源的活动。
# 黑板风格的约束
- 控制构件通过观察黑板中的状态数据来决定哪些知识源对后续的问题求解可能有所贡献,然后调用这些知识源的评价功能以选取参与下一步求解活动的知识源
- 被选中的知识源基于黑板中的状态数据将问题求解工作向前推进,并根据结果更新黑板中的状态数据
- 控制构件不断重复上述控制过程,及至问题求解获得满意的结果
案例:智慧树、Notion
# 黑板风格的特点
优点
- 可灵活升级和更换知识源和控制构件
- 知识源的独立性和可重用性好。知识源之间没有交互。
- 软件系统具有较好的容错性和健壮性。知识源的问题求解动作是探索性的, 允许失败和纠错。
缺陷
- 为了共享数据,各子系统必须有一致的数据视图,不可避免地会影响了整个系统的性能。
- 子系统的改变,使产生的数据结构也可能发生改变。
- 统一的数据库结构 (备份、安全、访问控制和恢复的策), 将影响子系统的效率。
# MVC 风格
模型构件
- 负责存储业务数据并提供业务逻辑处理功能
视图构件
- 负责向用户呈现模型中的数据
控制器
- 在接获模型的业务逻辑处理结果后,负责选择适当的视图作为软件系统对用户的界面动作的响应
# MVC 风格约束
创建视图,视图对象从模型中获取数据并呈现用户界面
控制器将用户界面事件转换为业务逻辑处理功能的调用
模型进行业务逻辑处理,将处理结果回送给控制器,必要时还需将业务数据变化事件通知给所有视图
# SOA 风格
将服务提供方和服务请求方独立开来,因而支持服务间的松耦合定义
允许任何一个服务在运行过程中所扮演角色的动态调整, 支持服务集合在运行过程中的动态变化,因而具有非常强的灵活性
提供了诸如 UDDI、SOAP、WSDL
等协议来支持服务的注册、描述和绑定等,因而可有效支持异构服务间的交互
服务请求方→服务注册中心:查询服务。服务请求方→服务提供方:访问获得服务。服务提供方→服务注册中心:注册和发布服务
# 消息总线风格
包含了一组软构件和一条称为 “消息总线” 的连接件来连接各个软构件
- 消息总线成为软构件之间的通信桥梁,实现各个软构件之间的消息发送、接收、转发、处理等功能
- 每一个软构件通过接入总线,实现消息的发送和接收功能
软件总线
“软件总线 (Software Bus)” 的中间件 (Middleware) 即对象请求代理 (Object Request Broker,简称 ORB) . 分布式对象结构具有很好的开放性和透明性,用户可以非常方便地在总线上添加、更新或删除组件对象。
流行的 ORB 技术标准有两种: CORBA(Common Object Request Broker Architecture)
, DCOM(Distributed Component Object Model)
.
对象 (Object)”—— 提供服务的系统组件 (System Component)。每个对象在逻辑上是平等的,它们可以互相为对方提供所需的服务。提供服务的对象就是服务器,而提出服务请求的对象就是客户。
# 软件详细设计
详细设计是高层设计和底层实现间的桥梁
高层软件体系结构设计需要通过软件详细设计落实和细化;
软件的实现需要软件详细设计的辅助和支持。
所以说软件详细设计是高层软件体系结构设计和软件实现的
桥梁
。
# 详细设计的工具
详细设计阶段的任务是开发一个可以直接转换为程序的软件表示,即对系统中每个模块的内部过程进行设计和描述。
常用的描述方法工具
- 流程图
- 结构化流程图(N-S 图)
- PAD 图 — 问题分析图
- PDL 语言
# 结构化流程图(N-S 图)
由顺序、选择、循环三种基本结构组成。
例子
# PAD 图
例子
# 设计模式类型
23 种设计模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)
创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程。
结构型模式:把类或对象结合在一起形成一个更大的结构。
行为型模式:类和对象如何交互,及划分责任和算法
# 简单工厂模式
首先,简单工厂模式不属于 23 种设计模式,简单工厂一般分为:普通简单工厂、多方法简单工厂、静态方法简单工厂。
普通简单工厂就是建立一个工厂类,对实现了同一 接口的一些类进行实例的创建。
非静态
// 产品接口 | |
interface Product { | |
String operation(); | |
} | |
// 具体产品 A | |
class ConcreteProductA implements Product { | |
@Override | |
public String operation() { | |
return "Result of ConcreteProductA"; | |
} | |
} | |
// 具体产品 B | |
class ConcreteProductB implements Product { | |
@Override | |
public String operation() { | |
return "Result of ConcreteProductB"; | |
} | |
} | |
// 简单工厂 | |
class SimpleFactory { | |
public Product createProduct(String productType) { | |
switch (productType) { | |
case "A": | |
return new ConcreteProductA(); | |
case "B": | |
return new ConcreteProductB(); | |
default: | |
throw new IllegalArgumentException("Unknown product type"); | |
} | |
} | |
} | |
// 客户端代码 | |
public class Client { | |
public static void main(String[] args) { | |
SimpleFactory factory = new SimpleFactory(); | |
Product productA = factory.createProduct("A"); | |
System.out.println(productA.operation()); // 输出: Result of ConcreteProductA | |
} | |
} |
静态
// 产品接口 | |
interface Product { | |
String operation(); | |
} | |
// 具体产品 A | |
class ConcreteProductA implements Product { | |
@Override | |
public String operation() { | |
return "Result of ConcreteProductA"; | |
} | |
} | |
// 具体产品 B | |
class ConcreteProductB implements Product { | |
@Override | |
public String operation() { | |
return "Result of ConcreteProductB"; | |
} | |
} | |
// 简单工厂 | |
class SimpleFactory { | |
public static Product createProduct(String productType) { | |
switch (productType) { | |
case "A": | |
return new ConcreteProductA(); | |
case "B": | |
return new ConcreteProductB(); | |
default: | |
throw new IllegalArgumentException("Unknown product type"); | |
} | |
} | |
} | |
// 客户端代码 | |
public class Client { | |
public static void main(String[] args) { | |
Product productB = SimpleFactory.createProduct("B"); | |
System.out.println(productB.operation()); // 输出: Result of ConcreteProductB | |
} | |
} |
注意:静态的不需要使用 new
。
活动图、用例设计、类设计、数据设计
# 第九章 软件测试
# 软件测试的过程
缺陷潜在位置:程序模块内部、程序模块接口、程序模块间的交互、整个软件系统
循序渐进 、逐层递进地开展软件测试
# 单元测试
对软件基本模块单元进行测试
过程、函数、方法、类
大多采用白盒测试技术
测试内容
- 模块接口测试
- 模块局部数据结构测试
- 模块独立执行路径测试
- 模块错误处理通道测试
- 模块边界条件测试
产生测试用例、执行测试程序、 生成测试报告
# 程序单元测试
- 测试对象 - 程序单元
- 基本模块单元
- 基本类方法、函数和过程
- 测试内容 - 四个方面
- 执行路径
- 错误处理
- 模块接口
- 边界条件
- 测试依据 - 设计文档
- 程序流程
程序单元是构成软件系统 的基本要素,必须对其进行测试以发现缺陷
考虑因素
作业:
① 调用其他模块时所给实际参数的个数是否与被调模块的形参个数相同;
② 调用其他模块时所给实际参数的属性是否与被调模块的形参属性匹配;
③ 调用其他模块时所给实际参数的量纲是否与被调模块的形参量纲一致;
④ 调用预定义函数时所用参数的个数、属性和次序是否正确;
⑤ 是否存在与当前入口点无关的参数引用;
⑥ 是否修改了只读型参数;
⑦ 对全程变量的定义各模块是否一致;
⑧ 是否把某些约束作为参数传递。
# quiz
书写标识符时忽略了大小写字母的区别。。
int a=5;
printf("%d", A);
在 C 语言中,标识符是区分大小写的。如果在程序中使用了大小写不同但名称相似的标识符,可能会导致错误。
忽略了变量的类型,进行了不合法的运算。。
float a, b;
printf("%d", a%b);
在 C 语言中,不同类型的变量有不同的合法运算。这里定义了
float
类型的变量却进行了不适合浮点数的运算。输入变量时忘记加地址运算符 “&”。。
int a, b;
scanf("%d%d",a,b);
int a, b;
scanf("%d%d",&a,&b);
输入数据的方式与要求不符。。
(1)scanf("%d%d",&a,&b);
(2)scanf("%d,%d", &a, &b);
又如: scanf("a=%d,b=%d",&a,&b);
判断输入满足要求: c1=a,c2=b,c3=c 。
scanf("%c%c%c", &c1,&c2,&c3);
输入:a b c
在 C 语言中,使用
%c
格式输入字符时,空格字符和转义字符都作为有效字符输入,与预期的输入格式可能不一致。输入输出的数据类型与所用格式说明符不一致。
a 已定义为整型,b 定义为实型。
a = 3; b = 4.5;
printf("%f%d\n", a, b);
在 C 语言中,输入输出的数据类型需要与所用的格式说明符匹配,否则会导致错误的结果。
将字符常量与字符串常量混淆.
char c;
字符串用双引号,char 是单引号.
# 单元测试 - 独立路径
在模块中应对每一条独立执行路径进行测试,单元测试的基本任务是保证模块中每条语句至少执行一次。此时基本路径测试和循环测试是最常用且最有效的测试技术。
# 集成测试
- 测试对象
- 对软件模块之间的接口进行测试
- 过程调用、函数调用、消息传递、远程过程调用
- 测试技术
- 采用黑盒测试技术
- 集成测试的内容
- 过程调用
- 函数调用
- 消息传递
- 远程过程调用
- 网络消息
# 集成测试方法
- 自顶向下集成:深度优先或者广度优先的策略把各个模块进行组装和测试
- 自底向上集成:自底向上进行组装和测试
# 确认测试
一、确认测试概述
- 测试对象:对软件的功能和性能进行测试,判断目标软件系统是否满足用户需求。
- 依据和标准:软件需求规格说明书。
- 测试技术:采用黑盒测试技术,包括性能、安全、强度等其他测试。
二、单元测试、集成测试与确认测试的关系
提及单元测试和集成测试,与确认测试一同作为软件测试的不同阶段。
三、确认测试计划设计时间
在需求分析阶段就可以设计确认测试用例及计划。
四、α 测试
- 由软件开发公司组织内部人员模拟各类用户行为对即将面市的软件产品(称为 α 版本、内部测试版)进行测试,发现错误并修正。
- 尽可能逼真地模拟实际运行环境和用户对软件产品的操作,并尽最大努力涵盖所有可能的用户操作方式。
- 经 α 测试并进行修改后的软件产品称为 β 版本(也称外部测试版)
五、β 测试
- 软件开发公司组织各方面的典型用户在日常工作中实际使用 β 版本,或为对外进行宣传而将 β 版本免费赠送给典型用户(很多情况下,β 版本可以通过 Internet 免费下载,也可以向软件公司索取),并要求用户报告异常情况、提出批评意见。
- β 测试是在与开发者无法控制的环境下进行的软件现场应用。
# 非功能性测试
性能测试、强度测试、配置和兼容性测试、安全性测试、可靠性测试、用户界面测试、本地化测试、Web 测试、安装测试。
# 软件测试原则
软件测试原则如下:
- 测试应有计划:在开发初期就应制定测试计划,且应在测试之前完成。
- 以用户需求为导向:所有测试都应追溯到用户需求。
- 应用 Pareto 原则:80% 的错误在大概 20% 的模块中能找到根源。
- 测试从微观到宏观。
- 穷举测试不可行。
- 定义预期输出:每个测试用例都必须定义预期的输出或结果。
- 避免开发人员自测:尽量避免程序的开发人员测试自己编写的代码,也尽量避免程序开发组织测试其开发的程序。
- 详细检查测试结果:应详细检查每次测试的结果,测试用例不仅要测试有效的输入条件,还要测试不期望的非法输入条件。
- 全面检查程序行为:检查一个程序没有完成希望它做的事情只是测试的一半任务,还应检查程序是否执行了不希望它做的事情。
- 不随意舍弃测试用例:避免随意舍弃任何测试用例,即使非常简单的测试用例。
- 避免预设无错:不应在事先假设不会发现错误的情况下来制定测试计划。
- 错误可能性与模块规模相关:程序模块存在更多错误的可能性与现存错误的数量成正比。
- 测试具有创新性和挑战:测试是一个具有相当创新性和智力挑战的活动。
# 软件测试技术
白盒测试技术
基于程序内部的执行流程来设计测试用例
黑盒测试技术
基于程序的外在功能和接口来设计测试用例
# 白盒测试技术
- 设计测试用例思想
- 根据程序单元内部工作流程来设计测试用例
- 发现程序单元缺陷
- 运行待测试的程序,检验程序是否按内部工作流程来运行的,如果不是则存在缺陷
- 特点
- 必须了解程序的内部工作流程才能设计测试用例
白盒测试用例设计的指导原则包括:
- 设计测试用例需考虑内部执行流程并生成测试数据。
- 确定设计多少测试用例需遵循覆盖原则。
- 测试用例覆盖准则有语句覆盖、分支覆盖、路径覆盖和基本路径覆盖。
判定覆盖 = 分支覆盖
条件覆盖
条件覆盖:每个条件真假都出现至少一次
判定条件覆盖:每个条件真假都出现至少一次的同时,每个判断的真假都出现至少一次
组合条件覆盖:所有条件真假进行组合
# 用例覆盖准则例题
软件测试的目的是.
使用白盒测试方法时确定测试数据应根据 AB。
属于白盒测试的技术是 BCD。
软件测试是保证软件质量的重要措施,它的实施应该在。
# 白盒测试 —— 路径测试
一、点覆盖
点覆盖是指选取足够的测试路径,使得程序控制流图中的每个节点至少被覆盖一次。
二、边覆盖
边覆盖要求选取足够的测试路径,使得程序控制流图中的每条边至少被覆盖一次。
三、路径覆盖
路径覆盖是选取足够多的测试路径,使得程序中所有可能的路径都至少被执行一次。
# 黑盒测试
- 思想
- 根据已知的程序功能和性能 (而非内部细节), 设计测试用例并通过测试检验程序的每个功能和性能是否正常
- 依据
- 程序的功能和性能描述
- 特点
- 知道程序功能和性能,不必了解程序内部结构和处理细节
黑盒测试发现的错误类型
不正确或遗漏的功能
界面错误
数据结构或外部数据库访问错误
性能错误
初始化和终止条件错误
# 黑盒测试的特点
黑盒测试与软件如何实现无关,如果软件实现发生变化, 测试用例仍然可以使用
黑盒测试用例的开发可以与软件实现并行进行,能够缩短软件开发周期
# 黑盒测试策略
黑盒测试法也称为功能测试,用黑盒测试法设计测试用例有 4 种常用技术:
- 等价分类法;
- 边界值分析;
- 因果图法;
- 错误猜测法。
# 等价分类法
- 思想
- 把程序的输入数据集合按输入条件划分为若干个等价类
- 每一个等价类对于输入条件而言为一组有效或无效的输入
- 为每一个等价类设计一个测试用例
- 优点
- 减少测试次数,不丢失发现错误的机会
每个等价类中的数据具有相同的测试特征
等价分类法的基本原则
- 有效等价类:是指对于程序的规格说明来说,是合理的有意义的输入数据构成的集合。利用它可以检验程序是否实现预先规定的功能和性能。
- 无效等价类:是指对于程序的规格说明来说,是不合理的,是无意义的输入数据构成的集合。程序员主要利用这一类测试用例来检查程序中功能和性能的实现是否不符合规格说明要求。
确定等价类的原则
- 如果输入条件规定了取值范围,或者是值的个数,则可以确立一个有效等价类和两个无效等价类。
- 如果输入条件规定了输入值的集合,或 者是规定了 “必须如何” 的条件,这时可确立 ++ 一个有效等价类 {.dot} 和一个无效等价类 ++{.dot}。
- 如果输入条件是一个布尔量,则可以确定一个有效等价类和一个无效等价类。
- 如果规定了输入数据是一组值,而且程序要对每个输入值分别进行处理。这时可为 ++ 每一个输入值确立一个有效等价类 {.dot}。此外再针对这组确立一个无效等价类 ++,它应是所有不允许输入值的集合。
- 如果规定了输入数据必须遵守的规则,则可以确定一个有效等价类 (符合规则) ,和若干个无效等价类 (从不同角度违反则)。
- 如果确知,已划分的等价类中各元素在程序中的处理方式不同,则应将此等价类进一步划分成更小的等价类。
划分等价类不仅要考虑代表 “有效” 输入值的有效等价类,还需考虑代表 “无效” 输入值的无效等价类。
每一无效等价类至少要用一个测试用例 ,不然就可能漏掉某一类错误,但允许若干有效等价类合用同一个测试用例,以便进一步减少测试的次数。
等价类测试用例确立原则如下:
- 为每一个等价类规定唯一编号。
- 设计新测试用例尽可能覆盖尚未被覆盖的有效等价类,重复此步骤直至所有有效等价类都被覆盖。
- 设计新测试用例使其仅覆盖尚未被覆盖的无效等价类,重复此步骤直至所有无效等价类都被覆盖。
# 边界值分析法
黑盒测试中的边界值分析法总结如下:
- 当输入条件是一范围
(a,b)
时,a、b
以及紧挨a、b
左右的值应作为测试用例。 - 当输入条件为一组数时,选择这组数中的
最大者
和最小者
以及次大和次小者
作为测试用例。 - 如果程序的内部数据结构是有界的,应设计测试用例使它能够检查该数据结构的边界。
- 测试数据选取不同:等价分类法的测试数据在各个等价类允许的值域内任意选取;边界值分析法的测试数据必须在边界值附近选取。
- 在公开招工的例子中,采用等价分类法设计了 8 个测试用例而边界值分析法则设计了 13 个,所以,一般来说 ,用边界值分析法设计的测试用例要比等价分类法的代 表性更广,发现错误的能力也更强。但是对边界的分析与确定比较复杂,它要求测试人员具有更多的经验和创造性。
# 错误猜测法
所谓猜测,就是猜测被测程序在哪些地方容易出错,然后针对可能的薄弱环节来设计测试用例。 显然它比前两种方法更多地依靠测试人员的直觉与经验。所以一般都先用前两方法设计测试用例然后 再用猜测法去补充一些例子作为辅助的手段。
# 因果图法
因果图是借助图形来设计测试用例的一种系统方法。它适用于被测程序具有多种输入条件, 程序的输出又依赖于输入条件的各种组合的情况 。
因果图是一种简化了的逻辑图,它能直观地表明程序输入条件(原因)和输出动作(结果) 之间的相互关系。
# 测试技术小结
分类 | 测试对象 | 测试要求 | 采用技术 |
---|---|---|---|
黑盒测试 | 程序的功能 | 逐一验证程序的功能 | 等价分类法、边界分析法、错误猜测法、因果图法 |
白盒测试 | 程序的结构 | 程序的每一组成部分至少被测试一次 | 逻辑覆盖法、路径测试法 |
# Summary
- 软件测试是为了发现软件中的缺陷,原理是设计和运行测试用例。
- 软件测试方法包括白盒测试、黑盒测试,具体为设计测试用例、运行测试代码、发现问题。
- 软件测试的活动、过程和策略有单元测试、集成测试、确认测试。
- 基于测试结果进行纠错,包括测试、调试和纠错。
# 第十章 软件维护与演化
软件维护的形式
・软件维护技术
软件逻辑老化
软件在维护和演化的过程中出现的用户满意度降低、质量逐渐下降、变更成本不断上升这样一种现象.
# 软件维护的形式
❑纠正性维护 ❑完善性维护 ❑适应性维护 ❑预防性维护
软件维护的形式
纠错性维护(Corrective Maintenance)对在测试阶段未能发现的,在软件投入使用后才逐渐暴露出来的错误的测试、诊断、定位、纠错以及验证、修改的回归测试过程
适应性维护(Adaptive Maintenance) 由于新的硬件设备不断推出,操作系统和编译系统也不断地升级,为了使软件能适应新的环境而引起的程序修改和扩充活动称为适应性维护。
改善性维护(Perfective Maintenance) 在软件的使用过程中,用户往往会对软件提出新的功能与性能要求。为了满足这些要求,需要修改或再开发软件,以扩充软件功能、增强软件性能、改进加工效率、提高软件的可维护性。
预防性维护(Preventive Maintenance) 为了进一步改善软件的可靠性和易维护性,或者为将来的维护奠定更好的基础而对软件进行修改。这类维护很难。
# 软件维护的特点
- 同步性:软件维护需要与软件使用同步进行。
- 周期长:软件维护周期比开发周期更长,一些软件会服役十几年甚至几十年。
- 费用高:维护成本高达总成本的 80% 以上,维护费用是开发费用的 3 倍以上。
- 难度大:充分理解待维护软件的架构、设计和代码极其困难,尤其是在软件设计文档缺失的情况下问题更为突出,50%-90% 的时间被消耗在理解程序上。
# 习题
微软的操作系统补丁是属于
纠错性
维护。某政府发现数据库容量日益增大,原有 ACCESS 小型报表系统不能满足要求,需要将数据库移植到 SQL SERVER 中,此属于
适应性
维护。QQ 的版本升级属于
完善性
维护。杀毒软件病毒库的更新属于
完善性
维护。Delphi 原来是运行在 windows 下的编程软件,现在 Borland 公司要开发新版本可以运行在 Unix 下,请问这是
适应性
维护。
# 软件维护的技术
一、面向维护的技术
在软件开发阶段用来减少错误、提高软件可维护性的技术,涉及软件开发的所有阶段,主要关注可维护性的三个方面即可测试性、可理解性、可修改性。
二、软件支援技术
在软件维护阶段用于提高维护工作效率和质量的技术,主要用到测试阶段的技术,包括信息收集、错误原因分析、软件分析与理解、维护方案评价、代码与文档的修改、修改后的确认等。
三、软件维护中应注意的问题
维护过程中应谨慎,合理使用工具。
# 代码重组
在不改变软件功能的前提下,对程序代码进行重新组织,使得重组后的代码具有更好的可维护性,能够有效支持对代码的变更
# 逆向工程
- 基于低抽象层次软件制品,通过对其进行理解和分析,产生高抽象层次的软件制品。
- 通过对程序代码进行逆向的分析:产生与代码相一致的设计模型和文档。
- 基于对程序代码和设计模型的理解,逆向分析出软件系统的需求模型和文档。
- 典型应用场景:
- 分析已有程序:寻求比源代码更高层次的抽象形式(如设计甚至需求)
# 设计重构
一、设计重构的背景
如果软件设计文档缺失、软件文档与程序代码不一致或软件设计内容不详实。
二、设计重构的方法
通过读入程序代码,理解和分析代码中的以下方面信息:
- 变量使用;
- 模块内部封装;
- 模块之间的调用或消息传递;
- 程序的控制路径。
三、设计重构的性质
是逆向工程的一种具体表现形式。产生用自然语言或图形化信息所描述的软件设计文档。
# 再工程
通过分析和变更软件的架构,实现更高质量的软件系统的 过程
再工程既包括逆向工程也包括正向工程
软件再工程过程
(1)库存目录分析
(2)文档重构
(3)逆向工程:分析程序以便在比源代码更高的抽象层次上创建出程序的某种描述的过程
(4)代码重构
(5)数据重构
(6)正向工程:也称革新或改造,不仅仅从现有程序恢复设计信息,而且使用该信息去改变或重构现有系统,以提高其整体质量。
ƪ(˘⌣˘)ʃ END