软件工程学概述
计算机系统经历了 4 个不同的发展阶段
软件危机:计算机软件的开发和维护过程中所遇到的一系列严重问题
软件危机的典型表现
软件危机的产生原因
消除软件危机的途径
软件工程的三个要素:方法、工具和过程
软件工程的本质特性
软件工程的 7 条基本原理
软件工程包括技术和管理两方面的内容
最广泛的软件工程方法学:传统方法学(生命周期方法学或结构化泛型)和面对对象方法学
面对对象方法学的 4 个要点
软件生命周期由软件定义(问题定义、可行性研究和需求分析)、软件开发(总体设计、详细设计、编码和单元测试、综合测试)和运行(软件)维护
生命周期模型(过程模型)
- 瀑布模型
- 快速原型模型
- 增量模型
- 螺旋模型
- 喷泉模型
RUP 将软件生命周期分为 4 个连续的阶段:初始阶段、细化阶段、构建阶段、移交阶段
可行性研究
可行性研究的目的不是解决问题,而是确定问题是否值得去解决
可行性研究可从 5 个方面来考虑:经济可行性、运行可行性、技术可行性、进程可行性、人员可行性
可行性研究的步骤
系统流程图是概括地描绘物理系统的传统工具
数据流图和数据字典共同定义了新系统的逻辑模型
数据流图中没有具体的物理部件,他只是描绘数据在软件中流动和被处理的逻辑过程
数据流图的符号
成本估计的技术:
- 代码行技术
- 任务分解技术
- 自动估计成本技术
货币的时间价值
年利率为
i
i
i,现在存入
P
P
P 元,
n
n
n 年后的到的钱数
F
F
F 为:
F
=
P
(
1
+
i
)
n
F=P(1+i)^n
F=P(1+i)n
反之,
n
n
n 年后的钱数为
F
F
F,现在这些钱的价值为:
P
=
F
/
(
1
+
i
)
n
P=F/(1+i)^n
P=F/(1+i)n
投资回收期
使累计的经济效益等于最初投资所需要的时间
找到不大于投资的时间
i
i
i,计算差值
d
d
d ,下一年的经济效益设为
P
P
P,则投资回收期为
i
+
d
/
P
i+d/P
i+d/P
纯收入
整个生命周期中累计的经济效益(折合成现在的价值)与投资之差
投资回收率(拿来跟年利率衡量,最重要的参考数据)
假设现在的投资为
P
P
P,软件寿命为
n
n
n,将来每年的经济效益为
F
1
,
F
2
,
…
,
F
n
F_1,F_2,\dots,F_n
F1,F2,…,Fn,解方程:
P
=
F
1
/
(
1
+
j
)
+
F
2
/
(
a
+
j
)
2
+
⋯
+
F
n
/
(
1
+
j
)
n
P=F_1/(1+j)+F_2/(a+j)^2+\dots +F_n/(1+j)^n
P=F1/(1+j)+F2/(a+j)2+⋯+Fn/(1+j)n
其中
j
j
j 即为投资回收率
需求分析
需求分析遵守的原则:
- 数据模型(ER图)
- 功能模型(数据流图)
- 行为模型(状态图)
- 用层次的方式展示细节
数据字典描述在数据模型、功能模型和行为模型中出现的数据对象及控制信息的特性,是 3 种分析模型的”粘合剂“,是分析模型的核心
ER图 例子:
快速建立软件原型是最准确、最有效、最强大的需求分析技术(“快速”、“容易修改”)
在大多数场合使用第三范式减少数据冗余,避免出现插入异常或删除异常
状态转换图(状态图):
验证软件需求正确性的 4 个方面:
- 一致性
- 完整性
- 现实性
- 有效性
总体设计
总体设计包括:
- 系统设计,确定系统的具体实现方案
- 结构设计,确定软件结构
典型的总体设计过程的 9 个步骤
模块化是为了是一个复杂的大型程序能被人的智力所管理、软件应该具备的唯一属性
抽象和求精是一对互补的概念
模块独立的概念是模块化、抽象、信息隐藏和局部化概念的直接结果
耦合衡量不同模块彼此间互相依赖(连接)的紧密程度
采取的设计原则:尽量使用数据耦合,少用控制耦合和特征耦合,限制公共环境耦合的范围,完全不用内容耦合
内聚衡量一个模块内部各个元素彼此结合的紧密程度
模块内的高内聚往往意味着模块间的松耦合
启发规则
- 改进软件结构提高模块独立性
- 模块规模应该适中
- 深度、宽度、扇出和扇入都应该适当
- 模块的作用域应该在控制域之内
- 力争降低规模接口的复杂程度
- 设计单入口单出口的模块
- 模块功能应该可以预测
描绘软件结构的图形工具:层次图和 HIPO 图,结构图
层次图:
HIPO 图:
结构图:
2 个特殊符号
层次图和结构图并不严格表示模块的调用次序,不指明什么时候调用下层模块
变换流(输入流和输出流(外部表示)、变换流(内部表示)),原则上所有信息流都可以归结为变换流
事务流(事务中心)
把具有变换流特点的数据流图映射成软件结构步骤:
- 复查基本系统模型
- 复查并精化数据流图
- 确定数据流图具有变换特性还是事务特性
- 确定输入流和输出流的边界,从而独立变换中心
- 完成“第一级分解”
- 完成“第二级分解”
- 实用设计度量和启发式规则对第一次分解得到的软件结构进一步精化
模块的作用域应该在控制域之内
详细设计
对于交互式系统来说,人机界面设计和数据设计、体系结构设计及过程设计一样重要
人机界面遇到的 4 个问题
- 系统响应时间
- 用户帮助设施
- 出错信息处理
- 命令交互
人机界面设计指南
- 一般交互指南
- 信息显示指南
- 数据输入指南
过程设计的工具
- 程序流程图
- 盒图(N-S 图)
- PAD 图
- 判定表
- 判定树
- 过程设计语言(PDL)
面向数据结构的设计方法(Jackson 和 Warnier 方法)
Jackson 图
- 顺序结构
- 选择结构
- 重复结构
改进的 Jackson (可以表达选择或重复结构的条件;可以在行式打印机上输出)图
Jackson 方法的 5 个步骤
流图
McCabe 环形复杂的计算方法:
- V(G)=流图中的区域数
- V(G)=E-N+2,E 是流图中边的条数,N 是节点数
- V(G)=P+1,P 是流图中判定结点的数目
V(G)=10 是模块规模的一个更科学更精确的上限
Halstead 预测程序长度及包含错误的个数
实现
编码风格的规则
- 程序内部的文档
- 数据说明
- 语句构造
- 输入输出
- 效率(程序运行时间、存储器效率、输入输出的效率)
软件测试的目的就是在软件投入生产性运行之前,尽可能多地发现软件中的错误
测试阶段的根本目标是尽可能多的发现并排除软件中潜藏的错误,最终把一个高质量的软件系统交给用户使用
软件测试准则
- 所有测试都应该能追溯到用户需求
- 应该远在测试开始之前就制定出测试计划
- 把 Pareto 原理应用到软件测试中
- 应该从“小规模”测试开始,并逐步进行“大规模”测试
- 穷举测试是不可能的
- 测试只能证明程序中有错误,不能证明程序中没有错误
- 为了达到最佳的测试效果,应该由独立的第三方从事测试工作
黑盒测试又叫功能测试,白盒测试又叫结构测试
测试的步骤
- 模块测试(单元测试)(模块接口、局部数据结构、重要的执行通路、出错处理通路、边界条件)
- 子系统测试
- 系统测试(和子系统测试一样都为集成测试)
- 验收测试
- 平行运行
人工测试和计算机测试相辅相成
由模块组装成程序时有两种方法:非渐增式测试和渐增式测试
自顶向下测试方法需要存根程序,自底向上方法需要驱动程序
在集成测试的范畴中,回归测试是指重新执行已经做过的测试的某个子集,以保证在集成测试过程中每当一个新模块结合进来时,程序发生的变化没有带来非预期的副作用
确认测试(也叫验收测试;用户参与;黑盒测试),他的目标是验证软件的有效性,其中一个重要内容是软件配置复查
一个软件为许多客户开发时使用的测试:Alpha(在受控的环境中进行)、Beta 测试(在一个或多个客户场所进行)
测试方案(具体的测试目的、应该输入的测试数据和预期的结果)的基本目的是确定一组最可能发现某个错误或某类错误的测试数据
逻辑覆盖
- 根据测试数据对源程序语句检测的详尽程度
-
- 语句覆盖
-
- 判定覆盖
-
- 条件覆盖(通常比判定覆盖强,但两者没有包含关系)
-
- 判定/条件覆盖(有时判定/条件覆盖并不比条件覆盖更强)
-
- 条件组合覆盖(覆盖标准中最强的,但不一定都使程序中的每条路径都执行到)
- 对程序路径的覆盖程度分析
-
- 点覆盖(语句覆盖)
-
- 边覆盖(判定覆盖)
-
- 路径覆盖
控制结构测试
- 基本路径测试(白盒测试技术)步骤
- 根据过程设计结果画出相应的流图
- 计算流图的环形复杂度
- 确定线性独立路径的基本集合
-
条件测试
其目的不仅是检测程序条件中的错误,而且是检测程序中的其他错误
其策略有:分支测试、域测试,BRO 测试 -
循环测试
白盒测试在测试过程的早期阶段进行,黑盒测试主要用于测试过程的后期
等价划分法(黑盒测试技术)力图设计出能发现若干程序错误的测试用例,从而减少必须设计的测试用例的数目
应该使每个测试方案中只覆盖一个无效的等价类
边界值分析(输入等价类和输出等价类的边界)
在一段程序中已经发现的错误数目往往和尚未发现的错误数成正比
选择输入组合的有效途径:利用判定表或判定树为工具、把计算机测试和人工检查代码结合起来
调试是作为成功测试的后果出现的
其过程如下:
调试途径
- 蛮干法
- 回溯法(相当常用)
- 原因排除法(对分查找法、归纳法、演绎法)
软件可靠性:程序在给定的时间间隔内,按照规格说明书的规定成功的运行的概率
软件可用性:程序在给定的时间点,按照规格说明书的规定成功的运行的概率
系统的稳态可用性:
A
s
s
=
T
u
p
/
(
T
u
p
+
T
d
o
w
n
)
A_{ss}=T_{up}/(T_{up}+T_{down})
Ass=Tup/(Tup+Tdown)
其中
T
u
p
T_{up}
Tup 为正常运行时间之和,
T
d
o
w
n
T_{down}
Tdown 为故障停机时间之和
A
s
s
=
M
T
T
F
/
(
M
T
T
F
+
M
T
T
R
)
A_{ss}=MTTF/(MTTF+MTTR)
Ass=MTTF/(MTTF+MTTR)
其中
M
T
T
F
MTTF
MTTF 为系统平均无故障时间,
M
T
T
R
MTTR
MTTR 为平均维修时间
估算平均无故障时间
E T E_{T} ET:测试之前程序中错误总数
I T I_{T} IT:程序长度(机器指令总数)
τ τ τ:测试(包括调试)时间
E d ( τ ) E_d(τ) Ed(τ):在 0 至 τ 期间发现的错误数
E c ( τ ) E_c(τ) Ec(τ):在 0 至 τ 期间改正的错误数
M T T F = 1 / [ K ( E T / I − T − E c ( τ ) / I T ] MTTF=1/[K(E_T/I-T-E_c(τ)/I_T] MTTF=1/[K(ET/I−T−Ec(τ)/IT],其中 K = 200 K=200 K=200
E c = E T − I T / ( K × M T T F ) E_c=E_T-I_T/(K\times MTTF) Ec=ET−IT/(K×MTTF),说明根据软件平均无故障时间的要求,估计需要改正多少个错误之后,测试工作才能结束
估计错误总数的方法
- 植入错误法
估 计 值 = n / n s × N s 估计值=n/n_s\times N_s 估计值=n/ns×Ns
其中 N s N_s Ns 为开始时植入的错误数, n s n_s ns 为一段时间的测试之后有 n s n_s ns 个植入的错误,有 n n n 个原有的错误 - 分别测试法
τ = τ 1 τ=τ_1 τ=τ1 时甲发现的错误数为 B 1 B_1 B1
τ = τ 1 τ=τ_1 τ=τ1 时乙发现的错误数为 B 2 B_2 B2
τ = τ 1 τ=τ_1 τ=τ1 时甲乙发现的相同错误数为 b c b_c bc
估 计 值 = B 2 / b c × B 1 估计值=B_2/b_c\times B_1 估计值=B2/bc×B1
维护
软件维护:软件在已经交付使用后,为了改正错误或满足新的需要而修改软件的过程
软件维护的种类
- 改正性维护(纠正正在使用过程中暴露出来的错误)
- 适应性维护(适应外部环境的变化)
- 完善性维护(改进原有的软件)
- 预防性维护(改进将来的可维护性和可靠性)
软件维护的特点
- 结构化维护与非结构化维护差别巨大
- 维护的代价高昂
维护工作量的模型:
M
=
P
+
K
×
e
c
−
d
M=P+K\times e^{c-d}
M=P+K×ec−d
其中,
M
M
M 时维护用的总工作量,
P
P
P 是生产性工作量,
K
K
K 是经验常数,
c
c
c 是软件复杂程度,
d
d
d 是维护人员对软件的熟悉程度
维护过程本质上是修改和压缩了的软件定义和开发过程(维护组织、维护报告、维护的事件流、保存维护记录、评价维护活动)
软件可维护性的定性定义:维护人员理解、改正、改动或改进这个软件的难易程度
软件可维护性的因素:
- 可理解性
- 可测试性
- 可修改性
- 可移植性
- 可重用性
软件重用技术是能从根本上提高软件可维护性的重要技术
文档是影响软件可维护性的决定因素,甚至比可执行的程序代码更重要
预防性维护实质上是软件再工程,有库存目录分析、文档重构、逆向工程、代码重构、数据重构和正向工程 6 类活动
面向对象方法学引论
面向对象方法 = 类 + 对象 + 继承 + 消息
面向对象方法学的优点
- 与人类习惯的思维方法一致
- 稳定性好
- 可重用性好
- 交易开发大型软件产品
- 可维护性好
对象的特点:以数据为中心、主动、实现了数据的封装、本质上具有并行性、模块独立性好
三种形式的模型
- 描述系统数据结构的对象模型
- 描述系统控制结构的动态模型
- 描述系统功能的功能模型
解决的问题不同, 3 个模型的重要程度也不同,对象模型始终是最重要、最基本、最核心的
动态模型和功能模型都包含了对象模型中的操作(即服务或方法)
对象模型
- 类图
功能模型
- 用例图
动态模型
-
序列图
-
协作图
-
交互图
-
- 状态图
- 状态图
-
- 活动图
- 活动图
三种模型相互补充、相互配合,功能模型指明了系统应该“做什么”,对象模型定义了做事情的实体,动态模型描述了对象之间的交互以及对象工作的状态和流程
面向对象建模得到的模型包含系统的 3 个要素:静态结构(对象模型)、交互次序(动态模型)和数据变化(功能模型)
复杂问题(大型系统)的 5 个层次:
Rational Rose