软件工程概论
软件=程序+数据+文档(逻辑实体)
程序:按实现设计的功能和性能要求执行的指令序列
数据:使程序正常操作信息的数据结构
文档:与程序开发、维护有关的图文材料
软件危机:计算机软件在开发和维护过程中所遇到的一系列严重问题1,如何开发软件;2,如何维护软件。
原因:
- 软件本身的特点
- 软件的逻辑性
- 程序的复杂性、规模庞大
- 软件开发和维护方法不正确
- 忽略定义时期的工作,特别是需求分析
- 认为软件开发就是写程序并使之运行
- 轻视维护
软件工程
定义:
- 把系统的、规范的、可度量的途径应用于软件开发、运行和维护过程,也就是把工程应用于软件
- 研究1,中提到的途径
关注大型程序,控制复杂性,经常面对变化,开发效率重要,和谐合作是关键,有效支持用户,一种文化背景的人像另一种文化背景的人提供服务
七条原理:
- 用分阶段的生命周期计划严格管理
- 坚持阶段评审
- 严格的产品控制
- 现代化程序设计技术
- 结果应能清楚地审查
- 开发人员少而精
- 承认不断改进软件工程实践的重要性
软件工程方法学三要素:方法、工具和过程
传统方法学:生命周期法/结构化范型
面向对象方法学:对象+类+继承+消息通信
三大角色:
- 客户(Customer):花钱开发软件的公司、组织或个人
- 开发者(Developer):为客户构建软件的公司、组织或个人
- 用户(User):最终使用软件的人
软件生命周期:
- 软件定义
- 问题定义
- 可行性研究
- 需求分析
- 软件开发
- 总体设计
- 详细设计
- 编码和单元测试
- 综合测试
- 运行维护
- 适应性维护
- 改正性维护
- 完善性维护
- 预防性维护
软件过程
软件过程可以理解成一个黑盒,输入需求,输出系统
瀑布模型:
阶段间具有顺序性和依赖性
推迟实现
质量保证:每个阶段都必须完成规定的文档,并对文档评审
实质是一种文档驱动的模型
优点:
- 可强迫开发者采用规范的方法
- 严格规定每阶段必须提交文档
- 每阶段的产品都经过质量保证小组验证
缺点:
- 用户大多数情况下并不能直接提供完整需求
- 仅通过纸面上的规格说明很难完整的认识软件
- 软件开发过程被强行线性化
- 可运行的软件版本在后期才会得到,一旦出问题,代价巨大
V模型:
本质是把瀑布模型中隐含的迭代过程明确出来
改进了的瀑布模型,强调测试和分析设计之间的关联
- 单元、集成测试校验程序设计
- 系统测试校验系统设计
- 验收测试确认需求
是活动驱动的
过于简单、理想化的抽象,对需求变化适应性差
原型/快速原型模型:
快速建立起一个可以在计算机上运行的程序,往往是最终产品的一个子集
优点:
- 不带反馈环,基本线性进行
- 原型已与客户验证
- 开发人员在建立原型时已经学到很多
- 利用原型有助于增强客户和开发者对需求的定义、确认
- 可以结合瀑布模型,两者互补性强
问题:
- 原型不可能面面俱到
- 客户:不可把原型当作正式运行的软件
- 开发者:同上
- 原型只是模型而已
阶段式开发(演化模型):
渐增式开发:
- 增量模型把软件作为一系列增量构建来设计、编码、集成和测试
- 每个构建由多个相互作用的模块构成,完成特定的功能
- 第一个构建实现基本需求、提供核心功能
迭代式开发(螺旋模型):
优点:
- 是对瀑布模型的发展,由客户对阶段性结果做出评审,对保证软件质量十分有利
- 引入风险分析,测试活动的确定性增强
- 最外层代表维护,维护与开发同样的重视
缺点:
- 适合内部开发,否则风险分析要在合同前完成或者客户理解(分析之后可能会取消)
- 只适合大项目,风险分析占比过大
- 对风险分析能力要求高,否则会退化为瀑布模型或更糟
喷泉模型:
喷泉体现了面向对象软件开发过程迭代和无缝的特性(相邻两个过程可能有重合)
详见面向对象章节
RUP
最佳实践:迭代式开发,管理需求,使用基于构件的体系结构,可视化建模,验证软件质量,控制软件变更
工作阶段
Inception(先启):生命周期目标里程碑
建立业务模型,定义最终产品视图,确定项目的范围
Elaboration(精化):生命周期架构里程碑
设计并确定系统的体系结构,制定项目计划,确定资源需求
Construction(构建):初始可操作性能里程碑
开发所有构件和程序,集成为客户需要的产品,测试所有功能
Transition(移交):产品发布里程碑
把开发出的产品提交给用户使用
优点:
- 用例驱动、以架构为中心、迭代和增量;
- 具有二维迭代性,有利于降低风险、适应需求变化;
- 是可配置的,具有通用性;
缺点:
- 在理想的项目开发环境下软件过程的一种完美模式;
- 未给出具体的剪裁、扩充等配置实施的方法准则。
敏捷过程:
适应变化而非预测变化
以人为中心
可以工作的软件胜过面面俱到的文档
微软过程
类似于螺旋模型的迭代
每个周期分为:构思、计划、开发、稳定、部署五个阶段,各阶段结束于一个重要的里程碑
可行性研究
目的:最小的代价,尽可能短的时间,确定问题能否解决
实质:压缩、简化的系统分析设计过程
根本任务:对日后行动方针提出建议
两个重点:技术可行性,经济可行性
- 技术可行性:使用现有的技术能实现这个系统吗?
- 经济可行性:这个系统的经济效益能超过他的开发成本吗?
成本:总成本的0.05-0.1
成本效益分析:
估计的成本:e=(a+4m+b)/6
其中,a是最乐观估计的成本,b是最悲观估计的成本,m是一般估计的成本
货币的时间价值:
i—年利率
P—现在存入的钱数
n—年数
n年后的收入$F=P(1+i)^n$
反之,若n年后收入为F,则现在这些钱的价值为$P=\frac{F}{(1+i)^n}$
纯收入:整个生存周期内的收益的现在值与投资之差
投资回收率大于存入银行的年利率,才会考虑开发
投资回收率$i$的计算公式为:
$P=F_1/(1+i)1+F_2/(1+i)2+...+F_n/(1+i)^n$
其中,$F_1,F_2,..,F_n$表示每年的收入。
需求分析——软件定义的最后一个阶段
需求分析的工作
需求分析要求系统必须确定完成哪些工作,及对目标系统提出完整、准确、清晰、具体的需求
需求分析的任务:
必须理解并描述问题的信息域,因此应该建立数据模型
必须定义软件应完成的功能,因此要建立功能模型
必须描述作为外部事件结果的软件行为,因此要建立行为模型
必须对描述信息、功能和行为的模型进行分解,用层次的方式展示细节
需求按必要程度分为:
- 必须满足的需求
- 很想满足但不是必要的需求
- 可以满足也可以取消的需求
需求分析为什么重要
五点事实:
- 软件生命周期中,一个错误发现的越晚,修复的费用就越高
- 许多错误是潜伏的,并且在产生很久后才被检测出来
- 需求过程中会产生很多错误
- 需求阶段代表性的错误为疏忽、不一致性和二义性
- 需求错误是可以被检测出来的
四点结论:
- 需求过程中会产生很多错误(事实3,4)
- 许多错误没有被早期发现(事实2)
- 这些错误是能被及时检测出来的(事实5)
- 如果没有即使检测出来错误,软件费用会直线上升(事实1)
两种需求:
- 功能需求:系统必须支持的功能和过程
- 非功能需求:操作环境和性能目标的系统需求
区别:功能性需求描述系统应该做什么,非功能性需求描述为如何实现功能性需求设定约束
两种需求文档:
需求定义文档:需求的描述
需求规约:软件需求规格说明
需求确认
目标是检查被定义的需求集合,并发现需求中可能存在的问题。最终要确保开发者与客户间没有误解、需求定义与规约间保持一致。
结构化分析方法
传统的结构化分析方法是一种面向数据流进行需求分析的方法
具体而言,传统的结构化分析方法就是用抽象的概念,按照软件内部数据传递、变换的关系,自顶向下逐层分解,直到找到满足功能要求的所有可实现的软件为止
结构化分析方法从三个方面建模:
- 数据建模——实体关系图
- 功能建模——数据流图
- 行为建模——状态转换图
状态转换图:
活动表的语法格式如下: 事件名(参数表)/动作表达式
事件表达式的语法如下: 事件说明[守卫条件]/动作表达式
其中,事件说明的语法如下: 事件名(参数表)
数字电路中的时序逻辑电路图就涉及了状态转换图的画法,操作系统中就绪态、挂起态和运行态的转换也可用状态图表示
数据流图
描述数据在系统中如何被传送或变换,以及描述如何对数据流进行变换的功能
数据流图所使用的符号:
源点、终点:系统之外的实体,是为了帮助理解系统接口而引入的
加工/变换:对数据进行处理的单元。要编号和起合理的名字
数据流:一组数据项组成
文件:暂存数据。读文件:数据从文件流向加工;写文件:数据从加工流向文件;又读又写:数据流向是双向的。
特殊符号:*:表示且;$\oplus$:表示异或
层次化的数据流图
为了表达详细的加工情况,将采用层次结构的数据流图。具体来说,采用自顶向下逐层构建数据流图的方式
-
首先构建顶层数据流图(基本系统模型):
只含有一个代表软件系统整体处理功能的转换 -
画出系统的内部(系统功能级数据流图):将顶层中的处理分解为若干多个处理
-
画处理的内部:把每个处理看成一个小的系统,用第2步的方法画出每个处理的数据流图子图
-
重复3,直到尚未分解的处理都足够简单
编号原则:
每个处理的子处理编号由父处理加细得到。例如:3号处理的子处理依次编号为3.1,3.2等
父图与子图的平衡问题:
子图的输入/输出数据流必须与父图的输入/输出数据流一致,不得添加或减少(然而,如果父图中的数据流可以被加细为多个子图中的数据流,也认为是平衡的)
局部文件问题:
文件(数据存储)总是局部于分层数据流图的某一层或某几层,所以数据流图中引入的文件都是局部文件。
命名问题:
首先为数据流命名:名字要代表整个数据流的内容,要具体有含义,如果命名困难,则说明应当继续分解
然后为与数据流关联的处理命名:规则与数据流命名类似,当一个处理命名时要使用两个或多个动词时,将该处理继续分解。
数据字典
数据字典相当于对数据流图的解释和注释,注明了每个加工的含义。
数据字典与数据流图共同构成逻辑模型
数据字典实现对下述四类元素的定义:
- 数据流
- 数据流分量(数据元素)
- 数据存储
- 处理
其他图形工具:Warnier图,IPO图等
总体设计
软件的设计原则包括:模块化、抽象、局部化、信息隐藏
软件设计的本质:
- 是软件开发中承前启后的工作,是下一步编程的依据
- 是软件开发形成质量的地方
- 是将需求转换为完整产品或系统的唯一方法
软件设计=概要设计+详细设计
软件设计过程:
- 设想供选择的方案
- 选择合理的方案
- 推荐最佳方案
- 功能分解
- 设计软件结构
- 设计数据库
- 制定测试计划
- 书写文档
- 审查和复审
设计原理
模块:可单独命名和编制的部分
模块化:程序划分为独立命名且可独立访问的模块,每个模块完成一个子功能,把这些模块集成起来构成一个整体,可以完成指定的功能满足用户的需求
我们一般认为将大问题分解成多个模块可以显著地降低问题的复杂度。然而,随着模块数目增加,设计模块间接口所需要的工作量也将增加,故模块并非越多越好。
逐步求精策略:为了能集中精力解决主要问题而尽量推迟对问题细节的考虑
逐步求精可视为一种自顶向下的设计策略。
信息隐藏:模块应设计成其中包含的信息对不需要这些信息的其他模块来说是不可访问的。
局部化:把逻辑关系密切的软件元素物理的放置得彼此接近。
耦合:衡量不同模块之间彼此依赖的紧密程度
内聚:衡量一个模块内部各自元素彼此结合紧密程度
耦合取决于:
- 接口的复杂程度
- 进入或访问一个模块的点
- 通过接口的数据
耦合
- 非直接耦合:两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。——独立性最强
- 数据耦合:一个模块访问另一个模块的时候,彼此之间通过简单的数据参数来交换输入、输出信息。
- 标记耦合:一组模块(模块间)通过参数表传递记录信息,就是标记耦合。这个记录是某种数据结构,例如数组、链表,将其完整的传递,实际就是传递了一个地址。
- 控制耦合:如果一个模块能够通过传送开关、标志、名字等控制信息,明显地控制选择另一模块的功能,就是控制耦合。
- 外部耦合:一组模块都访问同一全局简单变量而不是同一全局数据结构(更严重),且不是通过参数表传递该全局变量得信息,则称之为外部耦合。
- 公共耦合:若一组模块都访问同一个公共数据环境,则它们之间的耦合被称为公共耦合。公共数据环境包括全局数据结构、共享的通信区、内存的公共覆盖区等。
- 内容耦合:
- 模块直接访问另一个模块的内部数据
- 模块不通过正常入口转到另一个模块内部
- 两个模块有一部分代码重叠
- 一个模块有多个入口
注:《软件工程导论》一书中提到的公共耦合包括对公共简单数据的访问,分为了以下两种情况
- 一个模块只向公共区域发送数据,另一模块只从该区域接收数据。此情况为数据耦合
- 两个模块都与公共区域有数据的双向交流,这种情况耦合性比数据耦合强,比控制耦合弱。然而PPT中认为两个模块访问公共的全局简单变量是外部耦合,要强于控制耦合。
内聚
一个模块内部各元素彼此结合得越紧密,内聚度就越高,模块独立性就越强。
- 偶然内聚:模块内各个部分没有联系,即使有联系,这种联系也很松散,这种模块被称为偶然内聚模块。
- 逻辑内聚:这种模块把几种相关的功能组合在一起,每次调用时,由传送给模块的判定参数来确定该模块应执行哪一种功能。——不是执行一种功能,而是执行多种功能中的一种,因此不易修改。
- 时间内聚:这种模块大多为功能模块,但模块的各个功能的执行与时间有关,通常要求所有的功能必须在同一时间段内执行,例如初始化模块和终止模块。
- 过程内聚:使用流程图设计程序时,把流程图中的循环部分、判定部分、计算部分分成三个模块,这三个模块都是过程内聚模块。
- 通信内聚:如果一个模块的各功能部分使用了相同的输入数据,或产生了相同的输出数据,则成为通信内聚模块。通常通信内聚是使用数据流图定义的。内聚度较高,但是破坏了功能独立性。
- 顺序内聚:一个模块内的处理元素和同一个功能密切相关,且这些处理必须顺序执行,通常一个处理元素的输出数据作为下一个处理元素的输入数据。
- 功能内聚:一个模块中各个部分都是完成某一具体功能必不可少的组成部分,换言之:该模块中所有的部分都是为了完成一项具体功能而协同工作,紧密联系,不可分割的。
启发式规则:
- 改进软件结构提高模块的独立性
- 通过软件分解合并,降低耦合提高内聚
- 模块功能的完善化
- 消除重复功能,改善软件结构
- 软件规模应该适中
- 深度、宽度、扇入和扇出都应该适当
深度:软件结构中控制的层数
宽度:【软件结构同一层中模块总数】的最大值。一般来说,宽度越宽,系统越复杂。
扇入:一个模块有多少个上级模块直接调用
扇出:一个模块直接控制调用的模块数目
——目标:避免扁平,追求椭圆结构
一个理想的软件结构应该上层扇出多,中层扇出少——这样有利于增多中层模块而减少下层模块,实现椭圆结构 - 模块的作用域应在控制域内——控制域>作用域
作用域:受该模块内一个判定影响的所有模块的集合
控制域:模块本身和所有【直接或间接从属于他】的模块的集合 - 力求降低接口的复杂度
- 设计单出口单入口的模块
- 模块功能应该可以预测,避免对模块施加过多限制:假定模块是一个黑盒,使用同样的的输入预期能得到同样的输出
图形工具:
- 层次图
层次图的一个矩形代表一个模块,方框间的连线代表调用关系
下图是层次图的一个典型例子:
- 顶层方框代表正文加工系统的主控模块
- 第二层的每个模块控制完成正文加工的一个主要功能
- 第三层代表第二层一个模块可以控制调用的所有功能
- HIPO图:H(层次图)+I(输入)+P(处理)+O(输出)。层次图中每一个方框都有一个IPO表对应
- 结构图:
- 不能再分的底层模块称为原子模块
- 一个软件系统的所有实际加工都是原子模块完成,则成为完全因子分解系统。完全因子分解是所有设计的目标。
结构化设计方法(面向数据流的设计方法):
把信息流映射成软件结构
信息流:变换流or事务流
- 变换流:信息沿输入流进入系统,通过变换中心,经加工处理后沿输出流离开软件系统,则称之为变换流。
组成部分:输入,变换中心和输出
事务流:数据沿通路到达事务中心,后根据输入数据类型从若干动作序列中选择一个来执行。
变换分析:- 复查基本数据类型
- 复查并精化数据流图
- 确定数据流图具有变换特性还是事务特性
- 确定输入输出流的边界,孤立出变换中心
- 完成“第一级分解”
1. 分解出模块Cm,Ca,Ct和Ce
2. Cm:协调Ca、Ct、Ce功能
3. Ca:输入信息处理
4. Ct:变换中心模块
5. Ce:输出信息处理模块 - 完成“第二级分解”
把数据流图中每个处理映射成软件结构中一个适当的模块,并为每个模块写一个简要说明。 - 使用设计度量和启发式规则对第一次分割得到的软件结构进一步精化
例题:把下面的数据流图用SD方法映射成软件结构图,要求写出映射步骤和并画出软件结构图,并在图中标出模块调用时的参数和返回值。(注意对变换中心出现的数据输入的处理)
第一步:复查基本数据类型
第二步:复查并精化数据流图
第三步:确定数据流图具有变化特性还是事务特性:该图由三个部分组成,可以认为具有变换特性
第四步:确定输入输出流的边界。孤立出变换中心
第五步:完成第一级分解
第六步:完成第二级分解
第七步:使用设计度量和启发式规则对第一次分割得到的软件结构进一步精化
详细设计
目标:如何具体地实现所要求的系统
包括:接口设计、过程设计
- 接口设计:
- 软件构件之间的接口
- 模块和其他非人的消息生产者和消费者的接口
- 人和计算机间的接口,人机界面
- 过程设计:编码之前要对采用的算法的逻辑关系分析,设计出全部必要的细节,给予清晰的表达,使之成为编码依据。——结构化程序设计技术设详细设计的逻辑基础。
结构化程序设计:
- 只用选择、顺序和循环三种基本的控制结构连接
- 只有一个出口和一个入口
- 少用,最好只在检出错误时使用goto语句,应该总是前向goto
过程设计工具:
- 程序流程图
- 不是自顶向下的好工具,过早地考虑了细节而非全局结构
- 用箭头代表控制流,因此程序员可以不受约束控制,随意转移控制
- 不易表示数据结构
- 盒图/N-S图
- 功能域明确
- 不可能随意转移控制
- 确定数据的作用域
- 方便表示嵌套关系和模块的层次结构
- PAD图
- 使用结构化的PAD符号设计出的程序必然是结构化程序
- 描绘的程序结构清晰、容易记忆
- 方便转化为高级程序语言
- 既可以表示程序逻辑,又能用于描绘数据结构
- 支持自顶向下、逐步求精
- 判定表
以课本上的例子为例:由横竖两条双实线构成的坐标轴左上方代表条件,左下方是所有可能的动作,右上方是某个具体的实例符合哪些条件,右下方是该实例要执行的动作。
如:2号满足“国内乘客”和“头等舱”两个条件,因此执行(W-30)*4的动作。 - 判定树
判定树就是将判定表根据判定表中的所有条件做成分支。从根节点开始一路判断,最后的叶子节点就是要执行的动作。 - PDL语言
- 也被称为伪代码,具有严格的关键字外部结构用于定义控制结构和数据结构。同时又有着灵活的内部语法,可以适应各种工程需要
- 可以直接作为注释插入程序片段中,可以使用普通的文字编辑系统编辑,有软件可以自动由PDL生成程序代码
- 但不如图形界面清晰,且在复杂的条件组合和动作对应关系上不如判定表清晰。
人机界面——计算机产品最重要的元素
人机界面设计的黄金规则:
- 赋予用户控制权
- 减少用户记忆负担
- 保持界面一致
人机界面的设计过程
- 用户、任务和环境分析及建模
- 界面设计活动
系统响应时间:用户完成某个控制动作(如回车或点击鼠标),到软件给出预期响应之间的时间。
用户帮助设施
出错信息处理
命令交互
一般交互指南:
- 保持一致性
- 提供有意义的反馈
- 执行有较大破坏性的动作之前要求用户确认
- 允许取消绝大多数操作
- 减少在两次操作之间必须记忆的信息量
- 提高对话、移动和思考的效率
- 允许犯错误
- 按功能对动作分类,并据此设计屏幕布局
- 提供对用户工作内容敏感的帮助设施
- 用简单动词或动词短语作为命令名
信息显示指南
- 只显示与当前工作有关的信息
- 不要用数据淹没用户
- 使用一致的标记、标准的缩写和可预知的颜色
- 允许用户保持可视化的语境
- 产生有意义的出错信息
- 使用大小写、缩进和文本分组以帮助理解
- 使用窗口分隔不用类型的信息
- 使用“模拟”显示方式表示信息
- 高效率地使用显示屏
数据输入指南
- 尽量减少用户的输入动作
- 保持信息显示和数据输入之间的一致性
- 允许用户自定义输入
- 交互应该是灵活的,并且可以调整为用户最喜欢的输入方式
- 使在当前动作语境中不适用的命令起不了作用
- 让用户控制交互流
- 对所有输入动作都提供帮助
- 消除冗余的输入
程序复杂性的度量
程序的复杂程度乘以适当的参数可以估算软件中地错误数量以及软件开发需要的工作量
结果可以比较两个算法的优劣
可以作为模块规模的精确限度
McCabe方法
控制流图:仅仅描述程序的控制流程,完全不表现对数据的具体操作及分支和循环的具体条件
注意:流图的选择语句都是单一的,遇到多重选择问题要将多个选择条件转换为多个节点
- 节点(N):用圆表示,代表一条或多条语句
- 边(E):用箭头表示,一边必须终止于一个节点
- 区域(V):由边和结点围成的面积.特别的,开区域也算作一个区域
环形复杂度
- V(G)=V,环形复杂度等于区域数量
- V(G)=E-N+2
- V(G)=P+1,P是流图中判断的数目
- 在标准的流图中,一个判断节点代表一个判断
- 在某些非标准流图中,可能一个节点可以引出n条路径,这代表着存在n-1个判断
实现
编码
好的程序的代码逻辑简明清晰、易读易懂
- 程序的内部文档
- 数据说明
- 语句构造
- 输入/输出方法
- 效率问题
测试
- 测试是程序的执行过程,目的在于发现错误
- 一个好的测试用例在于能发现至今未发现的错误
- 一个成功的测试用例在于发现了至今未发现的错误
黑盒测试:把测试对象看作一个黑盒,测试人完全不考虑程序内部逻辑和内部特性,只依据程序的需求规格说明书,检查程序的功能是否符合他的功能说明。——功能测试/数据驱动测试
黑盒测试不可能用所有的输入输出条件来确定测试数据
白盒测试:把测试对象看作一个透明的盒子,它允许测试人员利用程序内部的逻辑结构及有关信息,设计或选择测试用例,对程序所有的逻辑路径进行测试。——结构测试、玻璃盒测试或逻辑驱动测试
模块测验(单元测验)
子系统测验(集成测验)
系统测验(集成测验)
验收测试(确认测试)
平行运行:同时运行开发出的新版本和被他取代的旧版本,比较两个系统的处理结果
单元测试
测试重点
- 模块接口
- 局部数据结构
- 重要的执行通路
- 出错处理通路
- 边界条件
代码审查:可以审查出30%-70%的逻辑错误和编码错误
审查小组人员:
组长
程序的设计者
程序的编写者
程序的测试者
代码审查的优越性:一次审查会可以发现许多错误,减少系统验证的工作量
需要编写驱动程序和存根程序
- 驱动程序:接收测试数据,把这些数据传送给被测试的模块,印出有关结果
- 存根程序:代替被测试模块所调用的子模块,“虚拟子程序”/“桩模块”:做最少量的数据操作,把控制归还给调用它的模块
集成测试
- 非渐增式测试:先分别测试每个模块,再一次性把所有模块设计要求放在一起结合成所要的程序
- 渐增式测试:把下一个要测试的模块同已测试好的那些模块结合起来进行测试,以此类推,每次增加一个模块。这种方法实质上是同时完成单元测试和集成测试。
具体分类:
-
一次性集成:当所有的组件都单独测试完毕之后,将他们一次性混合起来组成最终的系统,查看其是否能运行成功。(类似于非渐增式测试)
- 需要大量编写驱动程序和存根程序
- 所有组件一次进行合并,很难找出所有错误的原因
- 不容易区分接口错误与其他类型的错误
-
自顶向下集成:从主模块开始,沿着程序的控制层向下移动,逐渐把各个模块结合起来。在把【附属于主模块的那些模块】装载到程序结构中时,使用DFS或BFS策略。
- 能够早期对主要的控制或关键的抉择进行检验
- 不需要驱动程序
- 选择DFS时,可以在早期实验一个完整的功能并验证此功能
- 需要编写存根程序
- 为了充分测试高层,可能需要低层的处理
- 可能需要很多存根程序
-
自底向上集成:从原子模块开始组装和测试,不需要存根程序。当底层有许多组件是有多种用途的公共例程而经常被其他组件调用时、当设计是面向对象的或者当系统由大量孤立的复用的组件时,自底向上集成很有用。
- 不需要写存根程序
- 测试驱动程序数目较少
- 底层往往承担着主要的计算和输出,更容易出错,该方法能早发现这类错误
- 底层模块可以并行测试
- 对顶层测试较晚,会推迟主要错误的发现
- 程序最后一个模块加入时,才有具体整体形象
-
三明治集成:将自顶向下和自底向上结合起来,选取某一层作为基准层,选取不同的基准层,整个集成测试会有很大不同。
- 允许在测试的早期进行集成测试
- 结合了自顶向下和自底向上测试的优点,在测试的最开始就对控制和公用程序进行测试
- 在集成之前没有彻底地测试单独的组件
回归测试
在集成测试过程中,每引进一个新的模块,程序就发生了变化.
重新执行已经做过的测试的某个子集,以保证由于调试或其他原因引起的变化,不会导致非预期的软件行为或额外的错误。
回归测试集包括以下三类测试用例:
- 检测软件全部功能的代表性测试用例
- 专门针对可能受修改的软件功能的附加测试
- 针对被修改过的软件成分的测试
确认测试
确认测试有时也叫验收测试,目标是验证软件的有效性
软件有效性:像预期那样运行
确认测试以用户为主来进行
确认:为了保证软件确实满足了用户需求而进行的一系列活动——you built a right thing
验证:为了保证软件正确地实现了某个特定的要求地一系列活动——you built it right
Alpha测试:用户在开发者的场景下,在开发者的指导下进行测试
Beta测试:开发者不在现场,用户在一个或多个客户现场进行测试
白盒测试
- 语句覆盖:设计若干测试用例,运行被测程序,使得每一可执行语句至少被执行一次
- 判定覆盖/分支覆盖:语句覆盖的基础上,判定的每个分支都要执行一次
如:在上图中,语句覆盖不用执行a判定下的F路径,因为该路径没有语句。然而,判定覆盖需要执行该语句
- 条件覆盖:每个判定中的每个条件域至少要执行一次
选取测试数据使得
在a点有下述各种结果出现:
A>1,A≤1,B=0,B≠0
在b点有下述各种结果出现:
A=2,A≠2,X>1,X≤1
只需要使用下面两组测试数据就可以达到上述覆盖标准:
- A=2,B=0,X=4(满足A>l,B=0,A=2和X>l的条件,执行路径sacbed)
- A=l,B=1,X=1(满足A≤1,B≠0,A≠2和X≤1的条件,执行路径sabd)
如此可见,两个条件仅需分别满足对两个判定的各自判定域满足都覆盖,即可满足条件覆盖,不对两者同时满足或同时不满足做要求。由是可知,满足条件覆盖不一定满足判定覆盖。
- 判定条件覆盖:选取足够多的测试用例,使得所有判定和条件域都被执行一次。
- 条件组合覆盖:满足上述所有标准,所有条件的组合各执行一次,比判定条件覆盖更强,覆盖了更多的组合。但并不能保证所有的路径都被执行到。
- 点覆盖:流图的每个节点都被至少经过一次,判断标准与语句覆盖是相同的。
- 边覆盖:程序中的每条边都执行一次,判断标准与判定覆盖一致。
- 路径覆盖:程序中的每条路径至少执行一次
仍然以上图为例
A、B、X的所有可能取值为:
A>1,B=0,A=2,x>1
(TT)A>1,B=0,A≠2,x>1
(TT)A>1,B=0,A=2,x≤1
(TT)A>1,B=0,A≠2,x≤1
(TF)A>1,B≠0,A=2,x>1
(FT)A>1,B≠0,A≠2,x>1
(FT)A>1,B≠0,A=2,x≤1
(FT)A>1,B≠0,A≠2,x≤1
(FF)A≤1,B=0,A≠2,x>1
(FT)A≤1,B=0,A≠2,x≤1
(FF)A≤1,B≠0,A≠2,x>1
(FT)A≤1,B≠0,A≠2,x≤1
(FF)
- 若要满足路径覆盖/点覆盖,只需要取值1. 就可以实现
- 若要满足判定覆盖/边覆盖,需要取值4.和5.就可以实现,但该组合不满足条件覆盖
- 若要满足条件覆盖,则需要取值3.和11.就可以实现,但该组合不满足判定覆盖
- 若要满足判定条件覆盖,则取值7.和9.就可以实现
- 若要满足条件组合覆盖,则取值1.(TT)、7.(FT)、9.(FT)和12.(FF)就能实现(条件组合覆盖要求每个判定分别实现所有组合),但该组合不满足路径覆盖
- 若要满足路径覆盖,则要取值1.(TT)、5.(FT)、4.(TF)和12.(FF)
控制结构测试
基本路径测试:
- 根据过程设计画出流图
- 计算流图环形复杂度
- 确定线性独立路径的基本集合
- 独立路径指至少引入程序的一个新处理语句集合或一个新条件的路径,用流图术语描述,包含至少一条定义该路径之前不曾用过的边
- 环形复杂度=独立路径数量
- 设计可强制执行每一条独立路径的测试用例
注:某些独立测试并不能依靠程序正常执行,需要使用驱动程序或放在更大的程序中执行
黑盒测试
- 等价划分:将所有可能的输入数据划分为若干类,每一类导出一个测试用例,一个理想的测试用例可以发现一类错误。
确定测试用例:- 设计一个新的测试用例,使得尽可能多的覆盖并未被覆盖的有效等价类,重复直到每个有效等价类被覆盖。
- 设计一个新的测试用例,使得仅覆盖一个尚未被覆盖的无效等价类,重复直到所有的无效等价类被覆盖。
- 边界值分析:大量的错误发生在边界上而不是输入范围的内部。
- 错误推测法:依靠经验和直觉推测程序中可能会存在的各种错误,从而有针对性地编写检查错误的例子。
综合策略:
- 任何时候都必须使用边界值分析方法
- 必要时使用等价划分增加测试用例
- 用错误推断法增加用例
黑盒与白盒对比:
- 白盒只考虑测试软件产品;黑盒只考虑需求规约
- 黑盒会发现遗漏的缺陷:规格的哪些部分没有被完成;白盒会发现提交的缺陷:提出哪些实现是错误的
- 白盒的成本远高于黑盒,因为测试前先有源码
- 一个白盒的失败会导致所有的黑盒测试被重复执行
软件可靠性:
软件可靠性(R):在程序给定的时间间隔内,按照规格说明书成功运行的概率
软件可用性(A):在程序给定的时间点,按照规格说明书成功运行的概率
R(250)=0.95:100个相同系统中,有95个无故障的运行了250小时,5个在此期间发生了故障。
A(250)=0.95:在运行的第250个小时,有95正在正常运行,有5个处于故障待处理状态。
稳定可用性:
一段时间内,软件故障停机的时间为$T_{down}$,正常运行的时间为$T_{up}$,则系统的稳态可用性为:
$A_{ss}=\frac{T_{up}}{T_{up}+T_{down}}$
引入平均无故障时间MTTF和平均维修时间MTTR
上式可变为:
$A_{ss}=\frac{MTTF}{MTTF+MTTR}$
估算MTTF使用以下符号表示有关的量:
$E_T$——测试之前程序中错误总数
$I_T$——程序长度(机器指令总数)
τ——测试(包括调试)时间
$E_d(τ)$——在0至τ期间发现的错误数
$E_c(τ)$——在0至τ期间改正的错误数
我们认为:
- 在相似的程序中,单位长度内错误数($\frac{E_T}{I_T}$)为常数
- 失效率正比于软件中隐藏的错误数,MTTF反比于隐藏的错误数
- 每发现一个错误都立即被改进,即$E_d(τ)$= $E_c(τ)$
假定隐藏的错误数为$H_T$
根据2.有:
$MTTF*H_T=k$
且:
$H_T=E_T-E_d(\tau)$
根据1. 将上式左右同时除以$I_T$,有:
$\frac{H_T}{I_T}=\frac{E_T}{I_T}-\frac{E_c(\tau)}{I_T}$
我们令:
$K=\frac{I_T}{k}$
则平均无故障时间为:
$MTTF=\frac{1}{K(\frac{E_T}{I_T}-\frac{E_c(\tau)}{I_T})}$
即:
$E_c=E_T-\frac{I_T}{K\times M\ T\ T\ F}$
估计错误的方法:
- 植入错误法:测试之前随机的植入一些错误,测试之后统计发现的原有的错误和植入的错误的比例,计算原有的错误总数。
- 分别测试法:随机把一部分原有的错误加上标记,比较测试后带标记的错误和不带标记的错误的比例,估算原有错误总数。(标记重捕法)
注:为了随机的标记错误,分别安排两组测试人员,同时对一个程序的两个副本进行测试,一个组测试的结果认作标记,另一个组的结果中与标记重合的部分,就认为是重捕到的标记。
计算方法:总错误数=(乙找到的错误数)×[(重合的错误数)/(甲找到的错误数)]
调试
调试是在成功测试之后才开始的工作。任务是进一步诊断和改进程序中潜在的错误
调试方法:
- 蛮干法
- 通过内存全部打印来调试
- 在程序内部特定位置设置打印语句
- 自动调试工具
- 回溯法:一旦发生错误,先确定最先发生“症状“的位置,然后人工沿控制流程回追错误产生位置
- 原因排除法
- 对分查找法:在几个关键节点输入变量的正确值,观察结果正确性,正确则问题发生在节点前,反之则发生在节点后。
- 归纳法
- 演绎法
软件维护
软件维护:交付之后,为了修改错误或增加新需求而修改软件
- 结构化维护:有完整的软件配置,维护整体质量高
- 非结构化维护:缺少相关文档,维护代价巨大
维护存在诸如代码理解苦难,文档缺乏,开发者并未仔细说明软件,未对修改开放,并不是一项吸引人的工作等问题。
维护过程
软件的可维护性=可理解性+可测试性+可修改性+可重用性+可移植性
文档的要求:
- 描述如何使用该系统
- 必须描述如何安装和管理该系统
- 必须描述系统需求和设计
- 必须描述系统实现和测试
各类文档必须如实反映软件的当前状态
可维护性复审:需求分析复审,设计复审,代码复审,设计和编码,配置复审,在完成每项维护工作后,都应该对软件维护本身进行复审
软件再工程过程
库存目录分析
应当仔细分析库存目录,按照业务重要程度,寿命,当前可维护性,预期修改次数等指标,把库中应用系统排序,从中找到再工程候选者,然后明智分配再工程资源
以下程序可能成为预防性维护对象
- 预定使用多年的程序
- 当前正在成功使用的程序
- 最近的将来可能要做重大修改或增强的程序
文档重构
- 稳定不变的程序:保持现状
- 需要更新文档但资源有限:使用时建文档
- 关键应用+需要重构全部文档:文档工作减小到必需的最小值
逆向工程
分析程序以便在【比源代码更高的抽象层上】创建【程序的某种表示】的过程
恢复设计结果的过程
代码重构
重构难以理解、测试和维护的个体模块的代码
数据重构
对数据体系结构做适应性增强
当数据结构较差时,应该对数据进行再工程
正向工程
改变或重构现有系统,提高整体质量
软件项目管理
是否需要管理,是区别专业开发和业余编程的重要区别之一。
- 项目(project):为了创造独特的产品,实现独特的服务,达成独特的结果的暂时性努力。
- 暂时的
- 实现目标之后就完成了
- 无法实现的话,也算是结束了/取消了
- 运营(Operation):连续的,没有起止日期,往往是重复同一工作程序
软件成本
软件成本也需要定期的修正维护
对于大多数项目,工作量是软件成本最大的一块,同时也是最不确定的一块
软件工作量估算
工作量的估算首先从软件规模估算开始
- 代码行技术:依据以往的产品,估计一个功能要多少行
- 依赖开发语言
- 跨组织由于标准不同,因此不能类比
- 用源程序估计整个项目不合理
- 语言效率高,则估算的生产率偏低.原因:代码效率越高,写出来的代码行数就越少,由此得到的经验指导下生产率就会很低
- 功能点(FP)技术:依据功能数量,通过对软件信息域特性和软件复杂性评估软件规模
- 使用输入数、输出数、查询数、主文件数和外部接口数加权求和可以计算出未经调整的功能点计数(UFP)
上图中每部分取值为0-5,0表示该部分对系统无影响,5表示该部分对系统很重要,TFC=0.65+0.01(SUM(Fi)),TFC取值在0.65到1.35之间- FP=UFP×TFC
进度计划
工作分解结构(WBS):
- 是以可交付成果为导向的对项目成分的分组
- 自顶向下逐层构建,可以是图片或者文字
- 最高层是项目本身,此后是可交付成果及进一步分解的可交付成果,然后是创建这些成果的活动
Gantt图:
例子:假设有一座陈旧的矩形木板房需要重新油漆。这项工作必须分3步完成: 首先刮掉旧漆,然后刷上新漆,最后清除溅在窗户上的油漆。假设一共分配了15名工人去完成这项工作,然而工具却很有限: 只有5把刮旧漆用的刮板,5把刷漆用的刷子,5把清除溅在窗户上的油漆用的小刮刀。
甘特图画法:
优点:
形象的描绘任务的分解情况和子任务开始结束时间
缺点:
- 不能显示的描述各项作业之间的依赖关系
- 进度计划的关键部分不明确
- 有潜力的部分和潜力的大小不明确,可能会造成潜力的浪费
工程网络
- 描述任务的分解情况
- 注明作业的开始/结束时间
- 显示的描述作业之间的依赖关系
- 要求绘制者理解项目中哪些地方可以并行
- 活动(Activity):项目的一部分,要耗费一段时间,有开始和结束,用箭头表示
- 里程碑(Milestone):是某个活动完成的标志,是一个特定的时间点,用圆圈表示
活动的参数:
- 前置条件(Precursor):活动开始前必须发生的事件
- 持续时间(Duration):完成活动所需的时间
- 最终期限(Due Date):日期,活动必须在此之前完成
- 结束点(Endpoint):通常是活动对应的里程碑/可交付的成果
- 事件的最早时刻(EET)该事件可以发生的最早时间
- 事件的最迟时刻(LET)不影响竣工的前提下,最晚可以发生的时间
可见,工程网络类似于数据结构中的活动图
关键路径:持续时间最长,各活动机动时间为0,最早和最迟时刻相同
人员组织
- 项目干系人:有既得利益者
- 关键项目干系人:能够促成和破坏项目的成功
- 客户:负责说明开发软件的需求和其他风险的承担
- 用户:最终使用软件的人
民主制程序员组:
小组成员平等,两两之间存在通信信道,规模小(2-8人),组织方式非正式
主程序员组:
最好的程序员是主程序员,提供所有支持。通信由一两个人进行。
主程序员,和主程序员一样高水平的后备程序员,负责事务性工作的编程秘书,辛勤工作的程序员
现代程序员组
将主程序员的技术工作和行政工作分开
技术组长进行代码审查工作
行政组长进行业绩考核工作
软件配置管理(SCM)
软件配置管理是软件系统发展过程中管理和控制变化的规范
软件配置管理的目标是,使变化更正确且更容易被适应,在必须变化时减少所需花费的工作量。
- 软件配置项(SCI)为了配置管理而作为单独实体处理的一个工作产品或一段软件。即软件过程输出的全部计算机程序、文档、数据
- 配置管理聚集:SCI的一个集合,简称CM聚集
- 基线:通过了正式复审的软件配置项
- 项目数据库:一旦一个SCI成为基线,就被放到项目数据库中
软件配置管理的5项任务:
- 标识
- 版本控制
- 变化控制
- 配置审计
- 配置状态报告
软件质量保证
软件质量:软件与(明确地/隐含地定义的)需求相一致的程度
软件质量的保证措施:
- 基于非执行的测试(复审或评审)
- 基于执行的测试(软件测试)
- 程序正确性说明
能力成熟度模型(CMM)
目的:通过定义能力成熟度的五个等级,引导软件开发机构不断识别出其软件过程的缺陷,并指出应该做那些改正
等级1:初始级
等级2:可重复级
等级3:已定义级
等级4:已管理级
等级5:优化级
风险管理
- 风险:能造成恶果的有害事件,未发生。
- 风险转化时刻:风险变成问题的时候。
- 风险=机遇
- 风险管理的策略:
- 被动策略:针对可能发生的风险监督项目,直到他们变成真正的问题时,才拨出资源来处理他们。
- 主动策略:标识潜在风险,评估出现概率和产生的影响,按重要性加以排序,建立计划管理风险。设立应急计划,对未知风险能采取可控有效方式回应
面向对象
什么是面向对象
面向对象=对象+类+继承+通过消息进行通信
主要概念:
- 标识身份
数据和操作都被组织到相互分离、区别开来的空间实体当中 - 抽象
- 分类
具有相同属性类型和操作的对象组成一组(类) - 封装
- 继承
- 多态
面向对象的优点:
- 与人类的思维习惯一致
- 稳定性好
- 可重用性好
- 对开发大型软件产品友好
- 可维护性好
使用面向对象方法的开发过程
- 需求获取
- 面向对象分析
- 系统设计
- 对象设计
- 面向对象编码测试
喷泉模型
“喷泉”体现了面向对象软件开发过程迭代和无缝的特点
代表不同阶段的源泉相互重叠,表示两个活动之间存在交叠。
一个阶段内向下的箭头代表着该阶段内的迭代/求精
UML基础
类建模
类模型:描述系统内部对象的特征、对象之间的关系及对象所属每个类的属性操作,捕获静态特征
- 类图:对类及其关系进行建模
- 对象图:对单独的对象及其关系建模
类图相当于对象图的无限集合
类建模的各种概念:
链接:物理上或概念上的连接
关联
多重性
关联终端名:指出了关联终端的名字,在链接的末尾注明。如:
限定关联
泛化
n元关联向2元关联转化
聚合、组合
依赖
实现
抽象类
约束:约束限制了元素可以假定的取值
高级类建模——包
包是一组拥有公共主题的元素(包括类、关联、泛化和更小的包)
注意:满足以下要求使得包设计更合理
- 仔细刻画每个包的作用域
- 单个包中定义每个类
- 使包变得内聚
状态建模
事件:在某个特殊时刻发生的事情
- 信号事件:信号是指从一个对象到另一个对象的明确的单向信息流动,是发生或接收信号的事件
- 变更事件:满足布尔表达式使状态发生改变的事件
- 时间时间:在某个时间点上或在某个时间段内发生的事情引发的事件
状态、迁移、警戒条件
状态机图:结点是状态,有向弧是状态间的迁移
为有时序意义的类构建状态机图
高级状态建模
- 嵌套状态:略
- 历史状态:历史状态记录了上次离开复合状态时最后的一个活动子状态,用一个包含字母“H”的小圆圈表示。从历史状态恢复时,会导致性的最好一个子状态,并执行入口动作
交互建模
交互建模描述了对象之间如何交互才能产生有用的结果
顺序图
活动图
用例建模
参与者:系统外部用户,直接与系统通信的一个对象或一组对象,不是系统的一部分
用例:系统通过与参与者交互可以提供的一个功能
用例图三要素:参与者,用例,关系
用例描述:
- 用例名:唯一标识(主键)
- 参与者:标注主动参与者
- 入口条件
- 事件流
- 出口条件
- 特殊需求:非功能性需求
用例图
用例关系:
- 拓展(Extend)
将常规动作加入基本用例,非常规动作加入拓展用例 - 包含(Include)
包含将一个用用例合并到另一个用例的行为序列中去 - 泛化
拓展不是基本用例的一部分,缺少拓展基本用例仍然完整。而包含是基本用例的一部分,缺少该部分,基本用例不完整。
构件图:构件图描述程序代码的组织结构
配置图:描述系统中软硬件的物理配置情况和系统体系结构
UML三种拓展机制
- 标记值
- 附属于UML元素的各种信息
- 具有形式:
- 约束
- UML中显示一种或多种元素语义的规则
- 形式:
- 构造性
- 在已有的建模元素基础上建立一种新的模型元素,与现有的元素相差不多,只是多了些特别的语义
5大视图
- 用例视图
- 逻辑视图
- 进程视图
- 实现视图
- 部署(实施)视图
面向对象分析
需求提取到分析的过程
需求提取->用例和场景形式的需求说明->细化描述、规范和形式化->形成分析模型
分析模型要求:
- 准确
- 完整
- 一致
- 可检验
分析模型包括:
- 功能模型:用例图
- 对象模型:类图/对象图
- 动态模型:顺序图/状态图
“分析类”
- 实体类(Entity):系统要记录和维护的信息
- 边界类(Boundary):系统和外界要素间交互的边界
- 控制类(Control):用例中行为的协调
注意:每个参与者至少要与一个边界对象进行交互
顺序图的试探画法
- 第一列对应用例的主动参与者
- 第二列是主动参与者对应的边界对象
- 第三列是管理用例剩余部分的控制对象
- 控制对象由初始化用例的边界对象来创建
- 其他边界对象被控制对象创建
- 实体对象被控制对象、边界对象访问
- 实体对象从不访问控制对象和边界对象
经典的顺序图对象布局为:
“参与类图”(VOPC)
VOPC图=分析类+关联关系
设计模式的重叠部分
- 单一职责原则(SRP)
每个类的职责应当是单一的 - 开闭原则
类应当对修改关闭,对拓展开放 - 里氏替换原则(LSP)
凡父类可用的地方,都应该可以透明的替换为子类 - 接口分离原则(ISP)
客户端不应该依赖任何不使用的接口:将多个接口的类分为多个单一接口(少接口)的子类 - 依赖倒转原则(DIP)
上层不应该依赖下层,两者都应该依赖于抽象 - 迪米特法则(LOD)
知道最少原则,只应该直到朋友的最少需要被知道的细节。
常考问题
在软件生命周期各个阶段是如何为软件维护做准备的:
- 问题定义阶段:清晰的问题定义有助于建立稳固的基础,从而降低为来维护的风险。在这个阶段,问题定义的明确性能够帮助维护团队更容易理解系统的核心目标和功能
- 可行性研究阶段:通过对可行性进行深入研究,团队可以评估不用的技术和解决方案的长期可维护性。选择可维护性高的技术和方法可以降低为来维护的成本
- 需求分析:清晰、详细的需求有助于未来维护人员更好的理解系统的预期行为,减少对系统进行修改时的不确定性
- 总体设计:通过良好的总体设计,可以更容易地理解系统的模块化结构,是维护人员能够定位和修改特定模块而不会对整个系统造成影响
- 详细设计:清晰的详细设计文档有助于维护人员理解每个模块的内部工作原理,从而更容易进行修复或修改
- 编码阶段:良好的编码实践和注释有助于未来维护人员理解代码的逻辑和目的
- 测试:通过全面的测试,可以减少在维护阶段引入新问题的风险。测试用例也可以成为维护团队验证修改是否正常工作的依据。
各阶段的定义
请解释可行性研究:
可行性研究的目的,就是用最小的代价在尽可能短的时间内确定问题是否能解决。实质上是要进行一次大大压缩简化了的系统分析与设计过程,即在较高层次上以较抽象的方式进行的系统分析和设计的过程。
请解释需求分析:
需求分析是软件定义时期的最后一个阶段,他的基本任务是准确地回答“系统必须做什么”这个问题。确定系统必须完成哪些工作,也就是对目标系统提出完整、准确、清晰、具体的要求。
请解释总体设计:
总体设计的基本目的就是回答“概括地说,系统应该如何实现”这个问题。另一项重要任务是设计软件的结构,确定每个程序是由哪些模块组成的,以及这些模块相互间的关系
请解释详细设计:
详细设计的根本目标是确定应该怎样具体地实现所要求的系统,设计出程序的“蓝图”,以后程序员将根据这个“蓝图”直接写出实际的代码。
请解释实现阶段的工作:
通常把编码和测试统称为实现。编码就是将软件设计结果翻译成用某种程序设计语言书写的程序;测试目的是在软件投入生产性运行之前,尽可能多地发现软件中的错误,是保证软件质量的关键步骤。
请解释软件维护:
软件维护就是在软件已经交付使用之后,为了改正错误或满足新的需要而修改软件的过程。
请解释软件配置管理:
软件配置管理是软件系统发展过程中管理和控制变化的规范;是一门应用技术、管理和监督相结合的学科,通过标识和文档来记录配置项的功能和物理特性、控制这些特性的变更、记录和报告变更的过程和状态,并验证它们与需求是否一致。