SDU软件工程复习笔记
本文最后更新于:2022年7月7日 上午
第一章 软件工程概述
Error, Fault, Failure的含义与联系
错误Error:软件开发过程中人为产生的错误
缺陷Fault:在实现软件功能的时候存在的问题 是静态的
失败Failure:在运行时软件违背了它应该有的行为 是动态的
联系:人为错误 —可能导致—> 故障 —可能导致—> 失效
故障是系统的内部视图,从开发人员的角度看待系统
失效是系统的外部视图,它是用户所看到的问题
并非每一个故障都对应一个失效
高质量软件的三个方面
产品的质量
用户:外部特性。如果软件具有足够的功能,并且易于学习和使用;或者虽然难以学习和使用,但是由于功能值得这些付出,用户就断定软件是高质量的
开发者:内部特性,比如缺陷的数量
过程的质量
量化模型CMM
商业环境背景下的质量
考虑的是产品的技术价值,假定改进的技术质量会自动转化为商业价值
系统的要素
对象(实体)+ 活动 + 关系 + 系统边界
Wasserman规范
抽象
在某种概括层次上对问题的描述,使得我们能够集中于问题的关键方面而不会陷入细节
分析、设计方法和符号描述系统
使用标准对程序进行描述,不仅提供了交流媒介,还利于建立模型并检查模型的完整性和一致性
用户界面原型化
构建一个系统的小版本用于帮助用户或客户表示系统的关键需求、证明设计或方法的可行性
软件体系结构
不仅对实现和测试的方便性很重要,而且对维护和修改系统的速度和有效性也很重要
软件过程
活动中的组织和规范 不同的软件类型需要不同的过程
复用
复用以前开发项目中的项来利用应用程序之间的共性
复用的不仅仅是代码
测度
用通用数学语言描述行动和结果,使过程和产品的特定特性更加可见
工具和集成环境
标准化的集成开发环境可以增强软件开发,还允许我们检验每个软件工程环境提供的服务,决定哪一个环境最适合于给定的问题或应用程序的开发
CASE计算机辅助软件工程
第二章 过程和生命周期的建模
过程的含义和重要性
过程:一组有序的任务,涉及活动、约束和资源使用的一系列步骤
重要性:
- 通用性:软件过程可以让一系列开发活动保持一致性和结构性
- 指导性:软件过程使我们可以分析、检查、理解、控制和改善软件开发活动
软件生命周期及各阶段文档
软件生命周期:软件产品从概念到实现、交付、使用和维护的整个开发过程
需求分析
完成需求规格说明书(SRS)系统设计
完成系统结构图(SAD)程序设计
完成模块功能与数据描述文档软件开发
完成开发记录文档单元测试
按照程序设计中的要求进行测试,完成单元测试文档集成测试
按照SAD测试,完成集成测试文档系统测试
按照SRS进行测试,完成系统测试文档系统提交
提交系统说明文档维护
提交维护记录文档
瀑布模型
需求分析——《SRS》
系统设计——《SAD》
程序设计——《算法和数据描述文档》
编码——《源程序及注释》
单元测试和集成测试——《单元测试报告》
系统测试——《系统测试报告》
验收测试——《验收测试报告》
运行与维护——《维护报告》
优点:
- 为项目提供了按阶段划分的检查点
- 当前一阶段完成后,只需要关注后续阶段
- 简单性:很容易向用户解释
- 提供了模板,使分析、设计、编码、测试和支持的方法有共同的指导
缺点:
- 不能适应用户需求的变化,不能反映实际的代码开发方式(迭代)
- 各个阶段的划分完全固定,阶段之间产生大量文档,极大地增加了工作量
- 由于开发模型是线性的,用户只有在整个过程末期才能见到开发成果,增加了开发风险
- 通过过多的强制完成日期和里程碑来跟踪各个项目阶段
原型
原型:一种部分开发的产品,用来让用户和开发者共同研究、提出意见,为最终的产品定型。
优点:
- 有助于增进软件人员和用户对系统服务需求的理解
- 提供了一种有力的学习手段
- 容易确定系统的性能、服务的可应用性、设计的可行性和产品的结果
- 原型的最终版本可作为最终产品或最终系统的一部分
缺点:
- 文档容易被忽略
- 建立原型的许多工作会被浪费掉
- 项目难以规划和管理
阶段化开发
缩短循环周期,使系统一部分一部分地交付,从而在系统其余部分正在开发的同时,用户已经获得了一部分功能
增量开发
需求文档中指定的系统按功能划分为子系统,定义发布时首先定义一个小的功能子系统,然后在每一个新的发布中增加新功能
迭代开发
一开始就提交一个完整的系统,然后在每一个新的发布中改变每个子系统的功能
增量+迭代结合
一个新发布的版本可能包含新功能,并对已有功能做了改进
螺旋模型
把开发活动和风险管理结合起来,以将风险减到最小并控制风险
每次迭代有四个任务,依次是(四个象限):计划、目标/可选方案、风险评估、 开发与测试
共有四次迭代,依次是(每个象限的四重循环):操作概念、软件需求、软件设计、开发与测试
敏捷方法
4条原则
- 个人和交互的价值 > 过程和工具
- 生产运行的软件 > 编写各种文档
- 与客户合作 > 合同谈判
- 响应变化 > 遵循计划
总体目标
尽可能早地、持续地交付有价值的软件
极限编程 XP
交流、简单性、勇气、反馈
水晶法 Crystal
每一个不同的项目都需要一套不同的策略、约定和方法论
并列争球法 Scrum
使用迭代的方法,把每30天一次的迭代成为一个冲刺sprint,并按需求的优先级别来实现产品
自适应软件开发 ASD
静态建模:Lai表示法
该模型范式中可由人员完成角色,由资源完成活动,最后导致软件工件/制品的产生。过程模型可以用角色、活动、加工项(工件)来显示彼此之间的关系,用状态表显示每个加工项(工件)在特定时间的完成情况
动态建模:系统动力学
推演过程,观察到资源流是如何通过活动成为输出的
第三章 计划和管理项目
活动图计算关键路径 CPM
对于每个活动,列出它的前驱,并计算最早开始时间、最晚开始时间和时差,然后确定出关键路径
活动图上的结点为里程碑,边为活动
正推求最早开始时间
倒推求最晚开始时间
关键路径上的活动
P97 习题2,3 详解
CPM 条状图
水平条表示每个活动的工期,星号组成的那些条表明它是关键路径,由虚线和 F 描述的活动不在关键路径上,F 表示时差或浮动时间。
甘特图 Gantt
通过活动列表和时间刻度表示出特定项目的顺序和持续时间,用颜色或图标来指明完成的程度
项目人员
项目组织
结构化较强的团队:
按时完成任务,但工作比较循规蹈矩,项目普通但是功能完备。适合人员较多,项目稳定性和一致性高,使用较正规的结构。
结构化较弱的团队:
不能按时完成任务但是创造性强,涉及大量的不确定性因素时采用较为民主的方法和相关的团队结构
主程序员负责制小组 chief programmer team
由一个人总体负责系统的设计和开发,其他的小组成员向该主程序员汇报,主程序员对每一个决定由最终决策权
优点:
- 通过让主程序员负责所有决策,项目过程中需要的交流量最小化
- 迅速做出决定,效率高
缺点:
- 创造性低
- 对主程序员要求高,个人主观性强
忘我方法 egoless approach
每个人平等地担负责任,而且过程与个人是分开的,批评是针对产品和结果的,并不涉及个人,结构是民主式的,小组成员投票产生决策
工作量估算
专家判断
缺点:专家判断不仅受到差异性和主观性的影响,还受到对当前数据依赖性的影响。大部分专家判断技术过于简单化,没有将大量可能影响项目所需工作量的因素考虑在内
类推法
做出3种预测:一个悲观的预测$(x)$,一个乐观的预测$(y)$,最可能的猜测$(z)$
通过公式$(x+4y+z)/6$计算这些数的beta概率分布的平均值
Delphi技术
专家秘密地进行个人预测,然后计算平均估算并提交给专家组,直到没有专家修正为止
Wolverton模型
软件成本23矩阵:老问题O/新问题N 容易的E/适中的M/困难的H
把将要实现的软件系统划分成模块,然后根据代码行估算每一个模块的规模,使用矩阵计算每一个模块的成本,然后把所有模块的成本求和
算法方法
其中 $S$ 是估算的系统规模,而 $a, b, c$ 是常量,$X$ 是从 $x_1$ 到 $x_n$ 的一个成本因素的向量,$m$ 是基于这些因素的一个调整因子
Walston and Felix 1977
P98 习题6
COCOMO 模型
【阶段1】
项目通常构建原型以解决包含用户界面、软件和系统交互、性能和技术成熟性等方面在内的高风险问题
使用应用点AP进行规模测量
① 计算应用中将要包含的屏幕、报告和第三代语言的构件数目
② 将每个应用元素分类为简单、适中或难3个级别
③ 把加权的报告或屏幕求和,以得到一个单独的应用点的数。如果对象种有 $r\%$ 将从以前的项目中复用,则新的应用点的数目可以计算为 $新应用点=应用点\times(100-r)/100$
④ 根据开发人员的经验和能力、CASE成熟度和能力,使用一个称为生产率比率的调整因子
⑤ 所需的人月数是新的应用点数除以生产率因子
【阶段2】
早期设计阶段,已经决定将项目开发向前推进,但设计人员必须研究几种可选的体系结构和操作的概念
使用功能点FP进行规模测量
【阶段3】
后体系结构阶段,开发已经开始,且已经知道相当多的信息
根据功能点或代码行来进行规模估算,而且可以较为轻松地估算很多成本因素
风险管理
风险影响:与风险有关的损失
风险概率:从0到1对风险进行的测量
当风险概率为1时,则称该风险为问题
风险暴露
即数学期望,用于量化风险所造成的影响
风险管理活动
降低风险的3种策略
- 避免风险:改变性能或功能需求
- 转移风险:把风险分配到其他系统中,或者购买保险以便在风险成为事实时弥补经济上的损失
- 假设风险会发生,接受并用项目资源控制风险
风险杠杆
决策如何降低风险时,考虑降低风险的成本,如果杠杆值不够高,那么要寻求代价更低或更有效的风险降低技术
在某些情况下可以选取一种开发过程来帮助我们降低风险
例如,原型化可以改善对需求和设计的理解
Boehm的十大风险事项
风险 推荐的风险管理技术 人员短缺 配备最强能力的人员;合适地安排工作;团队建设;增强士气;交叉培训;预先安排关键人员 不现实的进度和预算 详细的、多源的成本和进度估算;根据成本进行设计;增量开发;软件复用;精简需求 开发错误的软件功能 组织分析;人物分析;明确表示操作概念;用户调查;原型化;早期用户手册 开发错误的用户界面 原型化;场景;任务分析 华丽的计划 精简需求;原型化;成本-收益分析;根据成本进行设计 持续的需求变化 提高变化阈值;信息隐藏;增量开发 外部执行的任务未达到要求 引用检查;对审核先给予奖励;奖惩合同;优胜劣汰的设计或原型化;团队建设 外部提供的构件达不到要求 基准;审查;引用检查;兼容性分析 实时性能达不到要求 模拟;基准;建模;原型化;使用仪器;调优 超出计算机科学的能力 技术分析;成本-收益分析;原型化;引用检查
第四章 获取需求
需求过程
① 原始需求获取:客户给出的需求
② 问题分析:理解需求并通过建模或模型化方式进行描述
③ 规格说明:利用符号描述系统将定义规范化表示
④ 需求确认:检查规格说明是否与客户需求匹配
⑤ 形成软件需求规格说明书SRS
敏捷需求建模
适用范围:小团队,不确定的需求
方法:递增地收集和实现需求,增量式开发
“重量级”过程
适用范围:大团队,确定的需求
特点:开发人员将编码推迟到已经对需求进行了建模和分析,详细的设计已经完成,其中每一步都需要模型,且模型之间是相关的、相互配合的,以便于使设计完全实现需求
需求引发的手段
风险承担者
委托人(要为开发的软件支付费用的人)
客户(软件开发之后购买软件的人)
用户(熟悉当前系统并将使用最终系统的人)
领域专家(对软件必须自动化的问题很熟悉的人)
市场研究人员(进行调查来确定未来趋势和潜在客户需求的人)
律师或审计人员(对政府、安全性以及法律的需求熟悉的人)
软件工程师或其他技术专家
与风险承担者进行会谈
评审可用文档
观察当前系统
做用户的学徒,在用户执行任务的时候详细学习
以小组的方式与用户和风险承担者进行交谈,以便相互启发
使用特定领域的策略,确保风险承担者考虑与特殊情形相关的特定类型的需求
与当前的和潜在的用户集体讨论如何改进打算要构建的产品
需求的类型
功能需求:描述系统内部功能或系统与外部功能的交互作用,涉及系统对输入的反应、实体状态变化、输出结果
质量需求(非功能需求):描述软件解决方案必须拥有的质量特性,如性能、易使用性、高可靠性或低维护代价
设计约束:已经做出的设计决策或限制问题解决方案集的设计决策,如平台或构件接口的选择
过程约束:对用于构建系统的技术和资源的限制
功能需求定义问题解决方案空间的边界,质量需求、设计约束以及过程约束通过将可接受的、喜欢的解决方案与无用的产品加以区分,进一步限制了解决方案空间
解决冲突
请求客户对需求进行优先级划分
(必需的)绝对要满足的的需求
(值得要的)非常值得要的但并非必需的需求
(可选的)可要可不要的需求
两种需求文档
需求定义:客户想要的每一件事情的完整列表
面向业务相关的人员,例如委托人、客户、用户
需求规格说明:将需求重新陈述为关于要构建的系统将如何运转的规格说明
面向技术性人员,例如设计人员、测试人员、项目经理
区别:需求定义可以处于环境域的任何地方,需求规格说明仅仅限制在环境域和系统域的交集处(共享的接口)
在将需求细化为规格说明时,没有丢失或改变信息
在定义文档中的每一条需求与规格说明文档的需求之间必须有直接的对应关系
需求的特性
正确的
一致的
无二义性的
完备的
可行的
相关的
可测试的
可跟踪的
建模表示法
7种基本的表示法范型 + 最适合的问题类型和描述 + 具体例子
E-R图
一种表示概念模型的图形表示法范型
实体表示为矩形,代表具有共同性质和行为的现实世界对象构成的集合
属性是实体上的注释,描述实体相关的数据或性质
联系表示为两个实体之间的边,边的中间有一个菱形,说明联系的类型
主要说明实体之间是如何联系的,而没有关于实体行为的任何信息
适合在需求过程的早期用于建模问题,因为它们提供要解决的问题的总体概况,而且当问题的需求发生变化时该视图是相对稳定的
例子:UML类图
每一个方框是一个类,表示一组相似类型的实体
一个类具有名称、属性集和类的属性上的操作集
类范围属性用带下划线的属性表示,是被类的所有实例共享的数据值
类范围操作用带下划线的操作表示,作用于一个新的实例或整个实例集
两个类之间的连线为关联,表示类的实体之间的联系
一端带有空心菱形的关联为聚合关联,表示 A <—has a— B
一端带有实心菱形的聚合为组装关联,表示 A <—由— B 组成
一端带有三角形的关联为泛化关联,表示 A <—是— B 的父类,一个子类继承其父类的所有属性、操作和关联
事件踪迹
是关于现实世界实体之间交换的事件序列的图形描述
每一条竖线表示不同实体的时间线,其名字出现在线的顶部
每一条水平线表示两个实体之间的一个事件或交互
时间按从顶到下的踪迹进展
广泛应用于开发人员和客户中,因为除了计时问题,事件踪迹的语义相对精简,还简单、易于理解。但对于文档化系统的行为并不是非常有效的,因为场景数目会难以处理
最好用在项目的开始以对关键需求达成共识,并帮助开发人员识别正在建模的问题中的重要实体
例子:消息时序图 MSC
只用于描述关键的场景,而不是说明整个问题
状态机
描述系统与其环境之间的所有对话,用于在单个模型中表示一组事件踪迹
每一个节点称为状态,表示存在于事件发生之间的一个稳定的条件集合
每一个边称为转移,表示由于一个事件的发生而产生的行为或条件的变化
每一个转移都标记有触发事件,还可能有输出事件,输出时间是在转移发生时产生的
特别适合的建模模型是:随着系统的执行过程,对于同样的输入,系统响应是如何变化的
例子: UML状态图
精细地将问题的动态行为模块化,标识为单个类的对象的行为,这种模块化使得难以了解对象之间是如何交互的
圆角矩形表示状态
实心圆表示起点
内部包含实心圆的圆表示终点
一个超状态可能实际上包含多个并发的子状态机,由虚线分开
状态转移标记的语法为:event(args) [condition] /action* ^Object.event(args)*
触发事件是一个可能携带参数的消息
激活条件由方括号括起来,是关于对象属性值的谓词
如果一个转移发生,其动作表示对象属性的赋值
如果该转移发生,他可能生成任意多个输出事件
例子:Petri网
用于建模并发活动以及它们之间的交互
圆圈称为位置,表示活动或条件
条表示变迁
有向的箭头称为弧,将变迁与其输入位置和输出位置连接起来
位置中放置的是令牌,作为变迁的启动条件。当变迁被触发时,清除每一个输入位置中的令牌,并将令牌插入每一个输出位置
为每一条弧分配一个权重,指出在变迁触发的时候令牌的变化数目
如果变迁的每一个输入位置包含足够的令牌,则一个变迁是可激活的
并发和同步的特征对于建模发生顺序不重要的时间特别有用
数据流图 DFD
建模功能以及从一个功能到另一个功能的数据流
一个泡泡表示一个加工或功能,它转换数据
箭头表示数据流,进入泡泡的箭头表示其功能的输入,从泡泡出去的箭头表示其功能的输出
数据存储是一个正式的库或信息库,表示为两个平行的条
数据源或数据接收器表示为矩形,称为参与者,提供输入数据或接受输出结果的实体
优点:提供了两种直观模型,一种是关于被提议系统的高层功能的,一种是各种加工之间的数据依赖关系
缺点:对于不太熟悉正在建模问题的软件开发人员而言数据流图是更加含糊不清的
最好由熟悉正在建模的应用领域的用户使用,并且最好作为问题的早期模型使用
例子:UML用例图
根据系统和系统的环境之间的交互,描述可观察到的、用户发起的功能
大的方框表示系统的边界
方框外的小人表示参与者,包括人或系统
方框之内的椭圆是用例,表示必需的主要功能及其变种
参与者与用例之间的线表明参与者参与了该用例
如果存在一个若干用例共有的步骤序列,则将该序列抽取出来形成一个子用例,以被基用例调用 基用例 --<<include>>--> 子用例
一个用例还可以附加上一个扩充的子用例,在该用例的后面增加功能 基用例 <--<<extend>>-- 扩充子用例
函数和关系
基于数学的规格说明和设计技术称为形式化方法,可以自动化地检查很多形式化规格说明的一致性、完备性、非确定性、可达状态以及类型正确性
将需求或软件行为建模为一组数学函数或关系,当组合在一起的时候,将系统输入映射到系统输出
一些函数说明系统的执行状态,一些函数说明输出
当一个输入值映射到多个输出值时,使用关系而不使用函数
函数规格说明有助于系统地、直观地测试一致性和完备性
例子:判定表
是函数规格说明的表格式表示,将事件和条件映射到适当的反应或动作上
所有可能的输入事件、条件、动作都列在表的左边;输入事件和条件列在水平线的上面,动作列在水平线的下面
每一个列表示将一组条件映射到其对应结果的规则
T
表示该行的输入条件为真;F
表示该行的输入条件为假;-
表示条件的值无关紧要
X
表示只要其对应的输入条件成立,该行的动作就应该执行
优点:很容易地检查是否考虑了条件的每一个组合以确定规格说明是否完备;通过识别同一输入条件地多个实例以及删除任何冲突的输出进行一致性检查;搜索表中的模式以了解个别输入条件与个别动作之间相互关联的程度
例子:Parnas表
使用行和列将函数的定义分割为不同的情况,表的每一个条目要么指定部分地识别某些情况的一个输入条件,要么指定某些情况的输出值。与判定表不同的是,Parnas表的输入和输出是纯数学表达式
要注意如何对行和列的头进行组织,以涵盖所有可能的条件组合
与状态机模型相比,这种模型结构的优点是,每一个输出变量的定义都局部化在一个不同的表中,而不是散布在模型中
逻辑
描述性的表示法更适合于表达全局性质或约束,如E-R图、逻辑
一阶逻辑
包含类型变量、常量、函数、谓词($>, <, =, \wedge, \vee , \neg, \Rightarrow, \Leftrightarrow$)、量词($\exists, \forall$)
时态逻辑
引入额外的逻辑连接符用以约束变量是如何随着时间的变化改变其值的
例子:对象约束语言OCL
专门为表述对象模型(如ER图)上的约束而设计的一种语言
以UML注释的形式出现在UML图中,或在支持文档中列举
能增强UML的很多模型:可以表示类图中的不变量、前置条件以及后置条件,状态图中的不变量、状态转移条件,消息时序图中事件的条件
例子:Z
将集合论的变量定义组织到一个问题的完整的抽象数据类型模型当中,并使用逻辑来表示每一个操作的前置条件和后置条件
Z利用抽象方法将规格说明分解为可管理规模的模块,称为模式
代数规格说明
通过指定操作对之间的相互作用,而不是建模单个操作,来指定操作的行为
但对于一个操作集,构造一个完备的、一致的、正确的公理集是很难的
例子:SDL数据
用于创建规格说明和描述语言中的用户定义数据类型以及参数化的数据类型
规格说明和描述语言SDL
SDL包括3个主要的图,外加定义复杂数据类型的代数规格说明
- SDL系统图(DFD)
- SDL方框图(DFD)
- SDL进程图(状态机)
快速原型化
抛弃型原型 throwaway
仅用于了解问题、快速解决问题,用完抛弃
演化型原型 evolutionary
不仅用于了解问题、解决问题,还要演变为最终产品
探索需求问题的两种方式 —— 建模 or 原型化?
取决于问题是什么,用模型表示更合适还是用软件表示更合适,构建模型或构建软件哪个更快
用原型更容易回答关于用户界面的问题;用模型更容易回答关于事件发生顺序这样的约束问题,或者关于活动的同步问题
第五章 设计体系结构
设计过程
① 建模:根据需求描述的系统的关键特性尝试可能的分解
② 分析:分析初步的体系结构,主要关注系统级别的决策
③ 文档化:确定各个不同的模型视图
④ 评审:检查体系结构是否满足需求
⑤ 形成软件体系结构文档 SAD
体系结构风格
管道和过滤器
优点:
- 简单易懂
- 可复用
- 可扩展性
- 并发性
- 便于系统分析
缺点:
- 批处理方式不适合交互式应用系统
- 没有通用的数据传输标准,每个过滤器都要解析和反解析数据,阻碍系统性能
- 难以进行错误处理
客户-服务器
优点:
- 有利于分布式数据的组织和管理
- 支持构件重用
- 构件之间彼此独立、充分隔离,转移计算机进程可以提高系统性能
- 提高系统模块化
- 在进程分配活动上赋予设计人员更大的灵活性
缺点:
- 数据安全性不高
- 维护成本高
- 开发成本高
对等网络 P2P
每个构件本身既是客户端又是服务器 文件共享网络
优点:
- 可扩展性,用户的加入增加了系统容量
- 数据被很多同级构件复制并且分布其间,对于构件和网络故障容错性高
- 非中心离散化避免出现服务器性能瓶颈
- 有效利用网络中的闲置资源进行计算、存储
- 数据传播直接在节点之间进行,速度快
缺点:
- 无法确保传输数据的安全性和质量
- 版权问题、管理困难、垃圾信息、占用带宽、病毒
- 脆弱性,易被黑客攻击
发布-订阅
构件之间通过对事件的广播和反应实现交互 隐含调用
优点:
- 为系统演化和可定制性提供了强有力的支持
- 可复用
- 低耦合,发布者只发布然后等待反应,订阅者只对事件做出反应
缺点:
- 不能独立地测试
- 消耗一定的时间和内存
- 过度使用会导致程序难以跟踪维护
信息库
由中心数据存储以及与其相关联的访问构件组成
两种类型构件的交互:访问数据的构件是主动/被动的——传统数据库/黑板类型
优点:
- 便于多用户共享大量数据
- 便于将构件作为知识源添加到系统中去
缺点:
- 设计难度大,需要同步机制和加锁机制保证数据的完整性和一致性
- 不同知识源间共享的数据结构要达成一致
分层
将系统的软件单元按层次化组织,每一层为它的上层提供服务,同时作为下层的客户
优点:
- 将系统分解从而完成复杂的业务逻辑
- 开发人员可以只关注其中某一层
- 可移植性
- 可维护性
- 降低层与层之间的依赖
- 有利于标准化
缺点:
- 层次之间频繁的调用和数据传输降低了系统性能
- 不是所有系统都能按层次划分
- 多层结构难以调试
组合体系结构风格
满足质量属性的策略
可修改性
使受直接影响的软件单元数量最少,将设计中的预期改变集中在一起:
- 预测预期改变
- 保持高内聚性
- 足够高的通用性
使受间接影响的软件单元数量最少,减少单元之间的依赖关系:
- 降低耦合度
- 单元间只通过接口交互
- 多重接口
性能
包括响应时间、吞吐量、负载
- 增加计算资源
- 提高资源利用率(增加软件并行程度)
- 有效管理资源分配
- 降低对资源的需求
安全性
高免疫力:能阻挡攻击企图
- 在设计中保证包含所有安全性特征
- 将可能被攻击者利用的安全性弱点最小化
高弹性:能快速容易地从攻击中恢复
- 把功能分段
- 使系统能在一小段时间里快速恢复功能和性能
可靠性
与软件本身内部是否有错误有关
主动故障检测
周期性检查
使用某种形式的冗余 n版本编程
故障恢复
- 撤销事务
- 检验点/回退
- 备份
- 服务降级
- 修正和继续
- 报告
健壮性
与软件容忍错误或外部环境异常时的表现有关
- 互相怀疑
易使用性
- 用户界面放置于自己的软件单元中方便为不同用户定制
- 能够检测和响应任何预期的用户输入
- 维护一个环境模型支持系统发起的活动要求
商业目标
- 购买与开发
- 最初的开发成本与维护成本
- 新的技术与已知的技术
故障树分析
- 给割集树的顶点分配节点,使得该节点与故障树顶部的第一个逻辑门相对应
- 自顶向下地进行,按以下步骤扩展割集树
- 扩展或门节点得到两个子节点,分别是或门的两个子节点
- 扩展与门节点得到一个合成的子节点,由与门的两个子节点组合而成
- 扩展组合节点中的一个逻辑门产生子节点,并把该组合节点中的其他逻辑门传播到各子节点中去
- 持续进行,直到所有的子节点都是基本事件节点或者基本事件的合成节点
KWIC的不同设计方案对比
信息库
数据抽象
隐含调用
管道和过滤器
成本效益分析
计算效益
计算投资回报 ROI=效益/成本
计算投资回收期
体系结构设计评审
确认:设计是否符合客户指定的需求
验证:设计是否遵循了良好的设计原则,且该设计文档是否适合用户的需要
主动设计评审:评审人员通过在SAD中寻找信息来回答相关问题
被动评审过程:评审人员在阅读文档时发现问题
第六章 设计模块
设计原则
模块化
高内聚低耦合
耦合度
内容耦合:A模块实际上修改了B模块,B模块完全依赖于A模块
公共耦合:不同模块可以从公共数据存储区来访问和修改数据
控制耦合:一个模块通过传递参数或返回代码来控制另一个模块的活动
标记耦合:使用一个复杂的数据结构进行模块间传递消息,并且传递的是该数据结构本身
数据耦合:模块间传递的是数据值,是最受欢迎的耦合
非耦合:模块相互之间没有信息传递,但是不太现实
内聚度
- 巧合内聚:模块各部分不相关,只为方便或偶然性原因放入同一模块
逻辑内聚:模块中各部分只通过代码的逻辑结构相关联
时态内聚:数据和功能仅仅因在一个任务中同时被使用而形成联系
过程内聚:功能组合在一起只是为了确保确定的顺序
通信内聚:各部分访问和操作同一数据集
- 功能内聚:模块中包含所有必需元素,且每个处理元素对功能都是必须的,每个模块执行且只执行设计的功能
- 信息内聚:在功能内聚的基础上,进行数据抽象化和基于对象的设计
接口
信息隐藏
增量式开发
使用图
创建有高扇入、低扇出的软件单元
使用夹层法消除循环
抽象
通用性
面向对象的设计 OO
继承和组合
继承:通过扩展和重载现有类的行为来创建新的类
- 子类对象从父类继承的方法在运行时不会再改变,具有更小的灵活性
- 理解和预测通过继承方式构造的类会更容易
- 通过选择性地覆载被继承的定义,可以改变和特化继承方法的行为
组合:通过组合简单的类来形成一个新类
- 保持被复用代码的封装性
允许动态替换对象构件
对象组合引入了一层间接性,会影响程序运行时性能
可替换性
子类必须保持其父类的行为,这样客户端代码才能把他的实例也当成其父类的实例来同等对待
利斯科夫替换原则的主要用途是确定在什么时候一个对象可以安全地被另一个对象所替换
德米特法则
“不要和陌生人说话”通过把组合类中作用在类构件上的每个方法都包含进来降低它们的依赖程度,使组合类的客户代码仅仅需要知道组合本身,而不需要知道组合的构件
依赖倒置
消除类形成的依赖循环
① 创建用户可以依赖的接口
② 将客户类和接口打包成新的客户模块
③ 为服务器类创建包装类,实现第一步中创建的接口
过程中的UML
需求:用例图、活动图、领域模型
体系结构:构件图、部署图
设计:类图、对象图;顺序图、通信图;活动图、状态图;包图
类图
类作用域的:操作方法名
公共的:+
私有的:-
受保护的:#
设计模式
模板方法模式
当多个子类对同样的方法有着相似但不是完全相同的实现时,将重复的代码结构放在抽象类中,让子类继承它
优点:
- 封装不变部分,扩展可变部分
- 提取公共代码便于维护
- 行为由父类控制,子类实现
缺点:
- 每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大
工厂方法模式
当在不同条件下创建不同实例时,创建一个抽象类来定义一个抽象的构造函数(工厂方法),然后子类覆载工厂方法来构造特定的对象
优点:
- 调用者创建一个对象只需知道名称
- 扩展性高
- 屏蔽产品的具体实现,调用者只关心产品接口
缺点:
- 每次增加一个产品都需要增加一个具体类和对象实现工厂,增加了系统的复杂度,同时也增加了系统具体类的依赖
策略模式
当有多种算法可用,但直到运行时才知道最好的时,将每个算法都封装为单独的对象,任意替换
优点:
- 算法可以自由切换
- 避免使用多重条件判断
- 扩展性高
缺点:
- 策略类会增多
- 所有策略类都需要对外暴露
装饰者模式
当要动态扩展对象的功能时,装饰基类充当抽象角色,装饰类扩展基类引用并继承它所装饰的对象
优点:
- 装饰类和被装饰类可以独立发展,不会相互耦合
- 装饰模式是继承的一个替代模式
- 装饰模式可以动态扩展一个实现类的功能
缺点:
- 多层装饰比较复杂
观察者模式
是发布-订阅风格的应用。一个对象的状态发生改变,进行广播,所有的依赖对象都将得到通知
优点:
- 观察者和被观察者是抽象耦合的
- 建立了一套触发机制
缺点:
- 如果一个被观察者对象有很多的直接和间接的观察者的话,广播会花费很多时间
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃
- 没有相应的机制让观察者知道目标对象是怎么发生变化的,而仅知道观察目标发生了变化
组合模式
表示树形结构时,树枝和叶子间实现统一接口,树枝内部组合该接口
优点:
- 客户端模块只和新接口交互,调用简单
- 组合对象的类结构改变不会对客户端产生影响
- 降低了客户端模块与组合对象之间的耦合度
缺点:
- 违背利斯科夫替换原则,子类会继承没有意义的操作
- 只强调组合结点的统一性,而忽略了安全性
- 增加一个新的操作就需要向组合对象的每个类增加一个新方法
访问者模式
将数据结构与数据操作分离,使用访问者模式将每个操作实现为抽象类Visitor的一个单独子类,并且子类都拥有将这些操作应用于每个构件类型的方法accept()
优点:
- 操作更加内聚
- 不触及组合对象代码的情况下增加新操作
缺点:
- 违反迪米特原则,具体元素对访问者公布细节
模式与框架
模式是抽象体系结构元素的模板,可用来指导生成设计
框架是针对特定应用领域的大规模的可复用设计
第七章 编写程序
编程标准
最关键的是需要在程序设计构件和程序代码构件之间建立直接的对应关系,设计低耦合、高内聚、定义明确的接口
对自身的作用
- 帮助自己组织想法避免错误
- 一些过程包括编写代码文档的方法,使得它更清晰且易于遵循
- 有助于将设计转化成代码,维护设计构件和代码构件的一致性
对他人的作用
- 易于维护
- 易于测试
- 易于重用
指导原则
控制结构
代码重组 模块化 通用性 注释体现构件耦合 内聚度
算法
选择算法时要在执行时间、设计质量、标准和客户需求之间平衡考虑
数据结构
保持程序简单 用数据结构来决定程序结构
通用策略
局部化输入和输出
包含伪代码
改正和重写,而不是打补丁
复用
文档
内部文档
头注释块HCB
其他程序注释
有意义的变量名和语句标记
安排格式以增强理解
文档化数据
外部文档
描述问题、算法、数据
编程过程
理解问题 - 制定计划 - 执行计划 - 回顾
极限编程
一种轻量级的软件开发方法,属于敏捷开发方法。它将复杂的开发过程分解为一个个相对比较简单的小周期,通过交流、反馈等方法,开发人员和客户可以非常清楚开发进度、变化、待解决的问题和潜在的困难等,并根据实际情况及时地调整开发过程。
结对编程
结对编程属于主要的敏捷开发方法,开发方式是两个程序员共同开发程序,一个负责编写程序,另一个负责复审和测试,两个人定期交换角色
优点:提高生产率和质量(但证据不充分)
缺点:会抑制问题求解的基本步骤,扰乱对问题的关注
第八章 测试程序
故障类型
算法故障:由于处理步骤中的某些错误,构件对于给定的输入没有产生正确的输出
语法故障:编程语言语法出错
计算故障 精度故障:个公式的实现是错误的,或者计算结果没有达到要求的精度
文档故障:文档与程序不一致
压力故障 过载故障:对数据结构的使用超出了承载能力
能力故障 边界故障:系统活动到达指定的极限时,性能会变得不可接受
计时故障 协调故障:几个同时执行或按仔细定义顺序执行的进程之间协调不当
吞吐量故障 性能故障:系统不能以需求规定的速度执行
恢复故障:当系统失效后不能表现得像设计人员希望的或客户要求的那样
硬件和系统软件故障:提供的硬件或者系统软件实际上并没有按照文档中的操作条件或步骤运作
标准和过程故障:代码没有遵循组织机构的标准和过程
测试步骤
模块测试、构件测试、单元测试
将每个程序构件与系统中的其他构件隔离,对其本身进行测试
集成测试
验证系统构件是否能够按照系统和程序设计规格说明中描述的那样共同工作
功能测试
对系统进行评估,以确定集成的系统确实执行了需求规格说明中描述的功能。其结果是一个可运转的系统
性能测试
测试系统的软硬件性能是否符合需求规格说明文档。其结果是一个确认的系统
验收测试
确定系统是按照用户的期望运转的
安装测试
确保系统在实际环境中按照应有的方式运转
黑盒测试
将测试的对象看作是一个密闭的黑盒,向闭盒提供输入的数据,并记录产生的输出。测试目标是确保针对每种输入,观察到的输出与预期的输出相匹配。黑盒测试参考的文档是系统设计和程序设计阶段的文档。
优点:免于受强加给测试对象内部结构和逻辑的约束,更偏向于功能性的测试。
缺点:
以SRS 为依据,有一定的盲目性和不确定性,不可能揭示所有的错误。
没办法总是使用这种方式进行完备的测试。
不容易找到具有代表性的测试用例证明所有情况
白盒测试
将测试对象看作一个白盒,然后根据测试对象的结构用不同的方式进行测试。
优点:可以测试一个模块的细节
缺点:
以模块内部逻辑为依据,当内部逻辑过于复杂时,不能给出合适的测试用例
对于大量递归、循环和分支的构件,测试完所有的分支是不现实的
实际测试方法依赖诸多因素
- 可能的逻辑路径数目
- 输入数据的性质
- 涉及计算量
- 算法复杂性
单元测试
代码评审
代码走查相对不正式
代码审查相对正式,事先准备关注问题清单,依据清单比对代码和文档的一致性
好处:一个故障在开发过程中发现的越早,它就越容易纠正,所造成的损失也就越小
测试程序构件
测试点或测试用例:用于测试程序的输入数据的一个特定选择。测试是测试用例的有限集合
测试的完全性:证明测试数据展现了所有可能的行为
- 语句测试:构件中的每条语句至少执行一次
- 分支测试:对代码中的每个判断点,每个分支至少选择一次
- 路径测试:通过代码的每一条不同路径至少执行一次
集成测试
自底向上
每一个处于系统层次中最底层的构件先被单独测试,接着测试调用了前面已测试构件的构件
优点:适合面向对象的程序;编写构件驱动程序很简单
缺点:顶层构件通常是最重要的,但是却是最后测试的
自顶向下
顶层构件先独立进行测试,然后将将被测构件调用的所有构件组合起来,作为一个更大的单元进行测试
优点:功能性的设计故障或主要问题可以在测试的早期进行处理
缺点:桩不容易编写,桩的正确性影响测试的有效性;可能需要大量的桩
一次性集成
先测试每一个构件,然后将所有的构件一次性的集成。只适用于小型系统
缺点:同时需要桩和驱动程序;很难发现失效原因;很难将接口故障和其他故障区分开
三明治集成
将系统分成三层,目标层处于中间,在顶层采用自顶向下的方式集成,在较低层采用自底向上的方式集成。测试集中于目标层
改进的:允许在较上层的构件和其他构件合并之前先对它们进行测试,保证每个模块得到单独测试
面向对象测试和传统测试之间的区别
测试用例的充分性
面向对象趋向于小粒度,其单元测试较为容易,但是集成测试涉及面变得更加广泛
需求分析和验证、测试用例生成、源码分析和覆盖分析
故障播种
测试小组1 检测到故障 $x$ 个
测试小组2 检测到故障 $y$ 个
两个小组都检测到的故障有 $q$ 个
程序中的故障总数的估算值 $n=\frac{q}{E_1E_2}$
可信度
假定要在一个程序中播种 $S$ 个故障,并且断言该程序中实际只有 $N$ 个故障,$n$ 表示测试过程中发现的实际的故障数,可信度为
Richards对其进行修改,可以用检测到的播种故障数 $s$ 估算可信度级别,而不管是否已经找出了所有故障
当 $N=0$ 时,$C=\frac{s}{S+1}$
第九章 测试系统
软件故障根源
需求分析:不正确、遗漏或者不清晰的需求
系统设计:对需求设计的误读,不正确或不清晰的设计规格说明
程序设计:对系统设计的误读,不正确或不清晰的设计规格说明
程序实现:对程序设计的误读,不正确的文档,不正确的语法语义
单元/集成测试:不完全的测试过程,改正已有故障时引入新故障
系统测试:不完全的测试过程,改正已有故障时引入新故障
维护:需求变化,错误的用户文档,负面的人为因素,改正已有故障时引入新故障
测试过程
① 功能测试:根据SRS测试系统功能
② 性能测试:将集成的构件与非功能系统需求进行比较 得到经验证的、确认的系统
③ 验收测试:客户根据需求测试 得到验收过的系统
④ 安装测试:在用户环境下进行测试 得到使用中的系统
集成计划定义要测试的子系统,并描述如何、何处、何时和由谁进行测试
一个子系统为一次旋转spin
配置管理
版本:针对特定系统的特定配置
发布:针对旧版本的改进版本
回归测试用于识别在改正当前故障的同时可能引入的新故障
- 插入新代码
- 测试被新代码影响的功能
- 测试版本m的基本功能,验证正常工作
- 继续版本m+1的功能测试
控制版本和发布的3种方式:
delta 差别文件描述如何将主版本转换到不同版本的编辑命令
优点:对公共功能的改变只需改变主版本;占存储空间小
缺点:若主版本丢失或损坏则所有版本丢失;将每一个变种表示为从主版本的转换是很困难的
为每一个不同的版本或发布保留单独文件
条件编译 单个代码构件代表所有的版本,让编译器决定哪些语句适用于哪些版本
测试小组
专业测试人员:集中于测试开发、方法和过程
分析员:以需求创建者的立场参与测试
系统设计人员:了解系统运作,可以使测试工作更有目的性
配置管理代表:出现失效或变化请求时安排变动,使变动反映在文档、需求、设计、代
码或者其他开发制品中
用户:对所发布的软件进行评估
因果图
对需求的语义进行检查,输入称为原因,输出和转换称为结果,反应这些关系的布尔逻辑图
验证参数还会产生中间节点
判定表产生测试用例
原因被调用或真(I) 原因被禁止或假(S) 不关心(X)
特定的结果出现(P) 未出现(A)
性能测试
性能测试所针对的是非功能需求。它需要确保这个系统的可靠性、可用性与可维护性。
压力测试(短时间内加载极限负荷,验证系统能力)
容量测试(验证系统处理巨量数据的能力)
配置测试(测试各种软硬件配置)
兼容性测试(如果它与其他系统交互时)
回归测试(如果这个系统要替代一个现有系统时)
安全性测试(确保安全性需求得到满足)
计时测试(评估涉及对用户的响应时间和一个功能的执行时间的需求)
环境测试(考察系统在安装场所的执行能力)
质量测试(评估系统的可靠性、可维护性、可用性)
恢复测试(强调系统对出现故障或丢失数据、电源、设备或服务时的反应)
维护测试(核实诊断辅助工具是否存在能否正常运行)
文档测试(确保已经编写了必需文档)
人为因素测试/可使用性测试(检查涉及系统用户界面的需求)
可靠性、可用性和可维护性
平均无故障时间MTTF 平均修复时间MTTR
平均是小间隔时间MTBF=MTTF+MTTR
可靠性:一个系统对于给定时间间隔内、在给定条件下无失效运作的概率(0~1)
R=MTTF/(1+MTTF)
可用性:在给定的时间点上,一个系统能够按照规格说明正确运作的概率(0/1)
A=MTBF/(1+MTBF)
可维护性:在给定的使用条件下,在规定的时间间隔内,使用规定的过程和资源完成维护活动的概率(0~1)
M=1/(1+MTTR)
验收测试
基准测试:由用户准备典型测试用例,在实际安装后的系统运作并由用户对系统执行情况进行评估
试验性测试:假设系统已经永久安装,执行系统,依赖系统的日常工作进行测试,相对基
准测试不是非常的正式与结构化
- $\alpha$测试:在向客户发布一个系统之前,先让来自自己组织机构或公司的用户来测试这个系统
- $\beta$测试:客户的试验
并行测试:新系统与先前版本并行运转,使用户进行比较和对照
本文作者: 31
本文链接: http://uuunni.github.io/2022/06/17/SEreview/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!