软件工程与计算II-复习
1 软件工程基础
软件工程的定义
1)应用系统的、规范的、可量化的方法来开发、运行和维护软件,即将工程应用到软件。
2)对1)中各种方法的研究。
从1950s到2000s之间的特点
- 1950s: 科学计算;以机器为中心进行编程;像生产硬件一样生产软件
- 1960s: 业务应用(批量数据处理和事务计算);软件不同于硬件;用软件工艺的方式生产软件
- 1970s: 结构化方法;瀑布模型;强调规则和纪律。奠定了软件工程的基础,是后续年代软件工程发展的支撑
- 1980s: 追求生产力的最大化;现代结构化方法/面向对象编程广泛应用;重视过程的作用
- 1990s: 企业为中心的大规模软件系统开发;追求快速开发、可变更性和用户价值;Web应用出现
- 2000s: 大规模Web应用;大量面向大众的web产品;追求快速开发、可变更性、用户价值和创新
2 项目启动
团队结构
- 主程序员团队:有一名技术能力出色的成员被指定为主程序员,主程序员负责领导团队完成任务。
- 民主团队:每个成员都能发挥能动性。
- 开放团队:为创新而存在;黑箱管理方式。
团队建设
- 建立团队章程
- 持续成功
- 和谐沟通
- 避免团队杀手
- 防范式管理
- 官僚主义
- 地理分散
- 时间分割
- 产品质量的降低
- 虚假的最后期限
- 小圈子控制
质量保障的措施
- 需求开发:需求评审和需求度量。
- 体系结构:体系结构评审、集成测试(持续集成)
- 详细设计:详细设计评审、设计度量、集成测试(持续集成)
- 实现:代码评审、代码度量、测试(测试驱动、持续集成)
- 测试:测试、测试度量
配置管理活动
标识配置项:确定哪些配置项需要被保存和管理;给配置项确定标识,设置唯一的ID;详细说明配置项的特征,包括生产者、基线建立时间、使用者。
版本管理:为纳入配置管理的配置项赋予初始版本号,并在发生变更时更新版本号。
变更控制:配置项发生变更时,需要依据变更控制过程进行处理。
配置审计:验证配置项的完整性、正确性、一致性和可追踪性。
状态报告:对在动态演化着的配置项信息及其度量取快照。
软件发布管理:将配置项发布到开发活动之外。
配置项:需要进行配置管理的软件开发制品,包括最终制品和中间制品。
基线:已经经过正式评审的规格说明或制品,可以作为进一步开发的基础,并且只有通过正式的变更控制过程才能变更。
变更控制
项目管理的目标
- 在限定时间内
- 在一定的成本内
- 在要求的质量水平上
- 高效使用资源
- 获得客户认可
3 需求基础
需求工程的活动
- 需求开发
- 需求获取
- 需求分析
- 需求规格说明
- 需求验证
- 需求管理
- 需求开发
需求获取的重要任务
- 目标分析
- 根据问题确定目标
- 通过分析利害关系人确定目标
- 用户需求获取
- 面谈
- 集体获取方法
- 头脑风暴
- 原型
- 目标分析
需求分析的任务
- 边界分析
- 需求建模
什么是需求
1)用户为了解决问题或达到某些目标所需要的条件或能力。
2)系统或系统部件为了满足合同、标准、规范或其他正式文档所规定的要求而需要具备的条件或能力。
3)对1或2中的一个条件或一种能力的一种文档化表述。
需求分层
业务需求(BR)
1)业务需求是高层次的解决方案和系统特性、系统开发的战略出发点、高层次的需求,描述为什么要开发系统。为了满⾜⽤户的业务需求,需求⼯程师需要描述系统⾼层次的解决⽅案,定义系统应该具备的特性(Feature),即系统特性(SF)。
2)Eg. 在系统使用3个月后,销售额度应该提高20%(期望,没有从软件角度进行描述,业务需求)
用户需求(UR)
1)执行具体任务的用户对系统所能完成任务的期望,描述了系统能帮用户做什么(直接用户、间接用户)
2)每一个系统特性对应一组用户需求
3)特性
- 模糊、不清晰(允许适度的用形容词和副词)
- 多特性混杂(功能和非功能的混杂)
- 多逻辑混杂(一个任务需要多次系统交互才能完成)
4)Eg.在系统要帮助收银员完成销售处理
系统级需求(SR)
1)需求分析模型:用户对系统行为的期望,每个系统级需求反映了一次外界与系统的交互行为,或者系统的一个实现细节(和用户需求有着很大的区别)
2)Eg.在接到客户经理的请求后,系统应该为客户经理提供所有会员的个⼈信息。
需求分类
- 需求
- 项目需求(人的数量、计划成本、时间)
- R5:项⽬的成本要控制在60万元人民币以下。
- R6:项⽬要在6个月内完成。
- 过程需求(人的分工、合作、方法、工具)
- R7:在开发中,开发者要提交软件需求规格说明文档、设计描述文档和测试报告。
- R8:项目要使用持续集成⽅法进行开发。
- 系统需求
- 软件需求
- 硬件需求
- 其他需求
- 其他需求
- R9:系统要购买专用服务器,其规格不低于….。
- R10:系统投⼊使⽤时,需要对⽤户进⾏1个星期的集中培训。
- 项目需求(人的数量、计划成本、时间)
- 不切实际的期望
- R11:系统要分析会员的购买记录,预测该会员将来⼀周和⼀个月内、会购买的商品;(技术上不可行)
- R12:系统要能够对每月的出入库以及销售行为进行标准的财务分析;(在有限的资源条件下不可行)
- R13:在使用系统时,收银员必须要在2个⼩时内完成一个销售处理的所有操作。(超出了软件所影响的问题域范围)
- 需求
软件需求的分类
功能需求:和系统主要共作相关的需求,即在不考虑物理约束的情况下,用户希望系统所能够执行的活动,这些活动可以帮助用户完成任务。功能需求主要表现为系统和环境之间的行为交互。
Eg.在接到客户经理的请求后,系统应该为客户经理提供所有会员的个人信息。
性能需求:系统整体或系统组成部分应该拥有的性能特征,例如CPU使⽤率、内存使⽤率等。
- 速度:系统完成任务的时间(所有用户查询必须在10s内完成)
- 容量:系统能存储的数据量(系统因该能够存储至少100万个销售信息)
- 吞吐量:系统在连续的时间内完成的事务数量(解释器每分钟应该能够至少解析5000条没有错误的语句)
- 负载:系统可以承载的并发工作量(系统应该允许50个营业服务器同时从集中服务器上进行数据的上传或下载)
- 实时性:严格的实时要求(系统监测到异常时,监视器必须在0.5s内发出警报,和故障警报不一样,故障不是系统的正常功能)
质量需求(QA):系统为了满足规定的及隐含的所有要求而需要具备的要素称为质量
- 可靠性:在规格时间间隔内和规定条件下,系统或部件执行所要求能力的能力。(在进⾏数据的下载和上传中,如果网络故障,系统不能出现故障。能不能检测网络中断,并且进行恢复)
- 可用性:软件系统在投⼊使用时可操作和可访问的程度或能实现其指定系统功能的概率。(系统的可用性要达到98%)
- 安全性:软件组织对其程序和数据进⾏未授权访问的能力,未授权的访问可能是有意,也可能是无意的。(VIP顾客只能查看⾃⼰的个⼈信息和购买记录)
- 可维护性:软件系统或部件能修改以排除故障、改进性能或其他属性或适应变更了的环境的容易程度,包括可修改性(Modifiability)和可扩展性(Extensibility)。(如果系统要增加新的特价类型,要能够在2个人月内完成。)
- 可移植性:系统或部件能从⼀种硬件或软件环境转换⾄另外⼀种环境的特性。(集中服务器要能够在1人月内从Window 7操作系统更换到Solaris 10操作系统。)
- 易用性:与⽤户使用软件所花费的努力及其对使用的评价相关的特性。(使⽤系统1个月的收银员进⾏销售处理的效率要达到10件商品/分钟。)
- 往往会有形容词和副词
对外接口:系统和环境中其他系统之间需要建立的接口,包括硬件接口、软件接口、数据库接口等等。
- 接口的用途
- 接口的输⼊输出
- 数据格式
- 命令格式
- 异常处理要求
- Eg.注册使用Google Maps API
约束:进⾏系统构造时需要遵守的约束,例如编程语言、硬件设施等
- 系统开发及运行的环境(包括目标机器、操作系统、网络环境、编程语⾔、数据库管理系统等,Eg.系统使用Java语言进行开发)
- 问题域内的相关标准(包括法律法规、行业协定、企业规章等。)
- 商业规则:(用户在任务执⾏中的一些潜在规则也会限制开发⼈员设计和构建系统的选择范围)
- Eg. 已过保质期的食品不能销售
- Eg. 顾客可以使用美元付款
数据需求(属于功能需求的一种,DR):功能需求的补充:如果在功能需求部分明确定义了相关的数据结构,那么就不需要再行定义数据需求。数据需求是需要在数据库、⽂件或者其他介质中存储的数据描述,通常包括下列内容:
- 各个功能使用的数据信息;
- 使用频率;
- 可访问性要求;
- 数据实体及其关系;
- 完整性约束;
- 数据保持要求。
- Eg.系统需要存储的数据实体及其关系为图6-14的内容。(数据实体及其关系)
- Eg.系统需要存储1年内的销售记录和退货记录。(数据保持)
需求汇总
- 项目的成本要控制在60万元人民币以下。
- 项目需求
- 项目要在6个月内完成。
- 项目需求
- 在开发中,开发者要提交软件需求规格说明⽂档、设计描述⽂档和测试报告。
- 过程需求
- 项目要使用持续集成方法进行开发。
- 过程需求
- 系统要分析会员的购买记录,预测会员将来一个月内会购买的商品
- 不切实际的需求
- 系统要能够对每月的出入库以及销售行为进行标准的财务分析
- 不求实际的需求
- 在使用系统时,收银员必须要在2个小时内完成一个销售处理的所有操作
- 不求实际的需求
- 所有用户查询必须在10秒内完成
- 性能需求(速度)
- 系统应该能够存储至少100万个销售信息
- 性能需求(容量)
- 解释器每分钟应该至少解析5000条没有错误的语句
- 性能需求(吞吐量)
- 系统应该允许50个营业服务器同时从集中服务器上进行数据的上传或下载
- 性能需求(吞吐量)
- 监测到病人异常后,监控器必须在0.5秒内发出警报
- 性能需求(实时性)
- 在进行数据的下载和上传中,如果网络故障,系统不能出现故障
- 质量需求(可靠性)
- 系统的可用性要达到98%
- 质量需求(可用性)
- VIP顾客只能查看自己的个人信息和购买记录
- 质量需求(安全性)
- 收银员只能查看,不能修改、删除VIP顾客的信息
- 质量需求(安全性)
- 如果系统要增加新的特价类型,需要能够在2个人月内完成
- 质量需求(可维护性)
- 集中服务器要能够在1个人月内从Window操作系统更换到Solaris操作系统
- 质量需求(可移植性)
- 使用系统1个月的收银员进行销售处理的效率要达到10件商品/分钟
- 质量需求(易用性)
- 系统要能够存储1年内的销售记录和退货记录
- 数据需求
- 当订单数量大于现有数量时,系统必须通知操作员
- 功能需求
- 顾客使用信用卡付款时,系统必须使用银联专用刷卡设备与银行交易;
- 其他需求(硬件需求)
- 系统应该能够存储3年的交易数据
- 性能需求(”应该能够“描述的是对容量的需求,如果是”需要存储“则是数据需求)
- 系统应支持使用第三方凭证如Google, Github登录
- 功能需求
- 该软件管理工具软件必须帮助项目管理者进行开发管理工作,以通过CMMI-4的评估
- 用户需求(系统能为用户实现什么)
- 系统的成本计算为:成本=人力成本+库存成本
- 软件规格(不懂)
- 系统使用之前 ,需要对收银员进行 10天的专门培训。
- 其他需求(人力需求)
- 经过10天培训的收银员就能够熟练使用系统
- 质量需求(易用性)
- 该软件管理工具的 开发过程自身必须符合CMMI-4的评估
- 过程需求
- 产品在发布1年之后,必须在出版的A、B、C三个产品评论刊物中被评为最可靠的产品
- 业务需求
- 系统必须能够与Oracle数据库交互
- 约束(数据库不是其他系统软件,是系统的一部分,因此不是对外接口)
- 商品标识的类型要能够在0.5个人月内更改为长整型
- 质量需求(可修改性)
- 默认的信用卡类型是“银联”
- 数据需求(默认值)
- 使用扫描仪扫描文件,传递回的数据为pdf格式文件。
- 对外接口(强调软件与硬件之间的传递数据)
- 超市的成本主要由人力成本和库存成本组成
- 问题域信息
- 系统应该一周7天每天24小时可用
- 不切实际的需求
- 系统每小时能够处理3000次呼叫
- 性能需求(负载)
- 订单数量不能大于现有库存数量
- 约束(商业规则)
- 系统开发必须在6个月内完成
- 项目需求
- 软件产品必须能够在3秒内对用户请求作出响应
- 性能需求
- 收银员完成一次商品交易过 程中操作失误的数不得超3次 ,且每失误都能在 5秒内更正
- 质量需求(易用性 – 出错率)
- 系统投入运行后,通过调查问卷的方式用户满意程度应在 70%以 上
- 质量需求(易用性 – 主观满意度)
- 每日报表中,标题的形式必须是 每日报告:dd-mm-yy
- 不确定,对外接口/数据需求
- 在销售商品之后,系统应该更新库存的数量如果低于最低限值,系统应该发出警示信号
- 功能需求
- 每一个收银员都应该有一个记录,记录的内容包括名字和 ID号。记录应该被建立成链表的形式。
- 数据需求
- 使用银联专刷卡设备,向银行传递的交易数据格式为 …
- 对外接口
- 过期商品的每日报表必须列出其名称、制造商和批号。
- 数据需求
- 商品的标识是由 0~24 位字母、数字混合组成的字符串。
- 数据需求
- 付款单上,默认的信用卡类型是 银联
- 数据需求(默认数据)
- 电梯的默认停运楼层必须是最低楼层到最高楼层范围内的某个数字
- 数据需求
- 当存储设备发生故障时,系统要在10秒内发现
- 质量需求(可靠性;系统本身故障不是正常功能,因此不是性能需求)
- 数据库与服务器之间的通信必须是加密的
- 质量需求(安全性)
- 项目的成本要控制在60万元人民币以下。
4 需求分析方法
4.1 用例图
- 用例的定义:在系统(或者子系统或者类)和外部对象的交互中所执行的行为序列的描述,包括各种不同的序列和错误的序列,它们能够联合提供一种有价值的服务。
用例图基本元素:
用例:水平椭圆
参与者:与系统交互(顾客不是参与者,只有收银员与系统交互);不一定是人。
关系
系统边界
用例图的建立
目标分析与解决方案的确定
寻找参与者
- 寻找用例
- 细化用例:粒度合适的判断标准:用例描述了为应对一个业务事件,由一个用户发起,并在一个连续时间段内完成,可以增加业务价值的任务。
4.2 概念类图(领域模型、分析类图)
基本元素
对象:标识符、状态、行为
类:共享相同属性和行为的对象的集合
链接:对象之间的互相协作关系;对象之间的物理或业务联系
关联:类之间的关系;关联是对象之间链接的抽象
- 聚合:表示部分与整体之间的关系
- 组合:整体包含部分,且对部分有完全的管理职责,部分无法同时属于其它整体,也无法单独存在
继承:
建立概念类图
1)对每个用例文本描述,尤其是场景描述,建立局部的概念类图
- 根据用例的文本描述,识别候选类
- 筛选候选类,确定概念类
- 识别关联
- 识别重要属性
2)将所有用例产生的局部概念类图进行合并,建立软件系统的整体概念类图
识别候选类
- 行为分析
- 名词分析:从用例文档描述中识别出有关的名词和名词短语,作为候选类
- CRC
确定概念类
- 准则:依据系统的需求,该类的对象实例的状态与行为是否全部必要
- 既需要维护状态,又依据状态表现一定行为,可确定为概念类
- 只需维护状态,不需要表现行为,应该成为其他概念类的属性
- 不需要维护状态,却需要表现行为:审视需求是否有遗漏;剔除候选类,转交行为给其他概念类
- 不维护状态也不表现行为,完全剔除
识别关联
- 分析用例文本描述,发现概念类之间的协作
- 分析和补充问题域内的关系
- 去除冗余关联和导出关联
识别重要属性
4.3 系统顺序图
将整个系统看做一个黑箱的对象描述,不是多个对象的详细顺序图
4.4 状态图
建立状态图:
- 确定上下文环境:识别状态的主体
- 识别状态
- 建立状态转移
- 补充详细信息
5 需求文档化与验证
- 用例文档
- 在⽤户的⻆度以⽤例⽂本为主描述软件系统与外界的交互
- 基本职责是把问题域信息和需求传达给软件系统解决⽅案的设计者
- 需求规格说明文档(SRS)
- 在软件产品的⻆度以系统级需求列表的⽅式描述软件系统解决⽅案
- 为什么需要需求规格说明
- 方便交流:软件开发过程中,子任务与人员之间存在错综复杂的关系,存在大量的沟通和交流,所以要编写软件开发中要编写不同类型的文档,每种文档都是针对项目中需要广泛交流的内容。因为软件需求需要进行广泛交流,所以要把需求文档化。
- 跟踪和度量:需求规格说明是在软件产品的角度以系统级需求列表的方式描述软件系统解决方案,书写需求规格说明,可以建立管理控制的基线,方便任务分配,制定工作计划,进行跟踪和度量。
- 过程管理:在实验中,需求规格的重要性不只体现在结果上,还包括中间过程,在书写需求规格过程中,才真正把问题域的问题和分析模型的成果转化为系统级需求,方便小组成员真正明确需求,个人认为在这个阶段包含一部分的需求在发现和完整化。
- 需求文档要点
- 技术文档写作要点
- 简洁
- 精确:不能使用模糊和起义词汇
- 易读
- 有效使用引言、目录、索引等能够增强文档易读性的方法
- 使用系统化的方法组织内容信息,提供文档内容的可读性
- 易修改
- 需求书写要点
- 使用用户术语
- 不能使用计算机术语:函数、参数
- 可验证
- 不可验证的例子:用户查询的界面应该友好
- 可行性
- 不可行需求:系统必须持续可用,即每周7天,每天24小时可用
- 使用用户术语
- 软件需求规格说明文档书写要点
- 充分利用标准的文档模板,保持所有内容位置得当
- 保持文档内的需求集具有完备性和一致性
- 为需求划分优先级
- 技术文档写作要点
- 以需求为基础开发系统测试用例
- 以需求列表为线索,开发测试用例套件。
- 对确定的测试用例套件,使用软件测试技术,主要是基于规格的技术,设计测试场景的输入与输出数据,建立测试用例。
8 软件设计基础
软件设计:
- 软件设计是关于软件对象的设计,是一种设计活动。
- 既指软件对象实现的规格说明,也指产生这个规格说明的过程。
- 软件设计阶段以需求开发的制品(需求规格说明和分析模型)作为设计的基础,构建软件设计方案描述和软件原型,为后期的构造活动提供规划和蓝图。
软件设计的核心思想
- 分解:横向上将系统分割为几个相对简单的子系统与子系统之间的关系
- 抽象:在纵向上聚焦各子系统的接口(这里的接口与实现相对),可以分离接口和实现,使得人们更好的关注软件系统本质,降低复杂度。
软件设计的核心层次
- 高层设计:基于反映软件高层抽象的构件设计,描述系统的高层结构、关注点和设计决策。
- 部件承载了系统主要的计算与状态
- 连接件承载部件之间的交互
- 部件与连接件都是抽象的类型定义(就像类定义),它们的实例(就像类的对象实例)组织构成软件系统的整体结构,配置将它们的实例连接起来
- 中层设计:更加关注组成构件的模块的划分、导入/导出、过程之间调用关系或者类之间的协作,模块划分隐藏⼀些程序片段(数据结构+算法)的细节,暴露接口于外界
- 低层设计:深入模块和类的内部,关注具体的数据结构、算法、类型、语句和控制结构等。
- 高层设计:基于反映软件高层抽象的构件设计,描述系统的高层结构、关注点和设计决策。
9 软件体系结构基础
软件体系结构是由部件,连接件,配置组成的。
部件
是软件体系结构的基本组成单位之⼀,承载系统的主要功能,包括处理与数据;
- 原始部件
- 复合部件
连接件
是软件体系结构的另一个基本组成单位,定义了部件间的交互,是连接的抽象表示;
- 原始连接件
- 复合连接件:是由更细粒度的部件和连接件组成。
配置是对”形式”的发展,定义了”部件”以及”连接件”之间的关联方式,将它们组织成系统的总体结构。
主程序/子程序风格
- 优点
- 流程清晰,易于理解
- 强控制性
- 缺点
- 程序调用是一种强耦合的链接方式,非常依赖交互方的接口规格,这会使得系统难以修改和复用。
- 程序调用的连接方式限制了各部件之间的数据交互,可能会使得不同部件使用隐含的共享数据交流,产生不必要的公共耦合,从而破坏它的“正确性”控制能力。
- 优点
面向对象式
- 优点
- 内部实现的可修改性
- 易开发、易理解、易复用
- 缺点
- 接口的耦合性
- 标识的耦合性
- 副作用
- 优点
分层
- 优点
- 设计设计清晰,易于理解
- 支持并行开发
- 更好的可复用性与内部可修改性
- 缺点
- 交互协议难以修改
- 性能损失
- 难以确定层次数量和粒度
- 优点
MVC风格
- 优点
- 易开发性
- 视图和控制的可修改性
- 适宜于网络系统开发的特征
- 缺点
- 复杂性
- 模型修改困难
- 优点
观察者模式
10 软件体系结构设计与构建
体系结构设计过程
- 分析关键需求和项⽬约束;
- 选择体系结构⻛格;
- 进⾏软件体系结构逻辑(抽象)设计;
- 依赖逻辑设计进⾏软件体系结构(实现)设计;
- 完善体系结构设计;
- 添加构件接⼝;
- 迭代过程3-7
包设计原理
- 重用发布等价原则(REP):重用的粒度就是发布的粒度
- 为重用器分组组件(类)
- 单个类通常是不可重用的:几个协作类组成一个包
- 包中的类应构成可重用和可释放的模块:模块提供一致的功能
- 减少重新使用者的工作
- 和相关联的类一起发布,而不是单独进行发布
- 共同封闭原则(CCP):包中所有类对于同一类性质的变化应该是共同封闭的,一个变化若对一个包产生影响,则对该包中的所有类产生影响,而对于其他包不造成任何影响。
- 最小化修改程序员的影响
- 包尽可能大,和CRP互斥
- 方法
- 将具有相似闭包的类分组
- 面向可以预期的变更封闭包
- 将更改限制为几个软件包
- 降低包装释放频率
- 减少程序员的工作量
- 只对可预测的变更有作用,不可预测的变更会为系统带来极大的破坏能力,并且无法进行预测。
- 共同重用原理(CRP):一个包中的所有类应该是能够共同重用的。
- 根据常见重用对类进行分组:避免给用户不必要的依赖
- 遵循CRP通常会导致软件包拆分:获得更多,更小,更专注的包
- 减少重新使用者的工作
- 包尽可能小,和CCP互斥
- 无环依赖原则(ADP):在包的依赖关系图中不能存在环。必须是有向无环图。
- 第一种单环,DIP依赖倒置即可解决
- 第二种互环,A依赖B,且B依赖A
稳定依赖原则(SDP):朝着稳定(别人的修改不影响我)的方向进行依赖
Ca:输入耦合度,包外部依赖本包的类个数
Ce:输出耦合度,包内部依赖于包外部的类的个数
不稳定性: I=Ce/(Ce+Ca), I越小越稳定
- 稳定抽象原则(SAP):包的抽象程度应该和其稳定程度一致
- 稳定的包应该是抽象的包
- 不稳定的包应该是具体的包
- Na:包中抽象类个数
- Nc:包中所有类个数
- 抽象度A=Na/Nc
- 前三条描述的是依赖性,后三条描述的是耦合性
- 包设计过程:
- 开发包(构件)设计
- 运⾏时的进程
- 物理部署
- 接口定义步骤
- 根据分配的需求确定模块对外接口
- 初步设计关键类
- 编写接口规范
- 体系结构开发集成测试用例
- Stub: 为了完成程序的编译和连接而使用的暂时代码;对外模拟和代替承担模块接口的关键类;比真实程序简单,使用最为简单的逻辑
- Dirver: 模拟上层模块,驱动一个测试, 就是驱动一个Service的行为
- 重用发布等价原则(REP):重用的粒度就是发布的粒度
11 人机交互设计
- 易用性
- 易学性:新手用户容易学习,能够很快使用系统
- 效率:熟练用户使用系统完成任务的速度快
- 易记忆性:以前使用过软件系统的用户,能够有效记忆或者快速地重新学会使用该系统
- 出错率:用户在使用系统时,会犯多少错,错误有多严重,以及是否能从错误中很容易地恢复
- 主观满意度:让用户有良好的体验
- 人机交互设计原则
- 简洁设计
- 7±2原则
- 摘要图片比描述文字更简洁和清晰
- 不要使用太大的菜单,不要在一个串口中表现过多的信息类别,不要在一个表单中使用太多的颜色和字体作为线索
- 一致性设计
- 若一个系统中相似的任务具有完全不一致的交互机制,会导致用户精神模型的不一致,如按OK和Cancel钮位置不一致
- 低出错率设计
- 不适当的菜单功能灰色屏蔽;禁止数值输入域出现字母字符;提供建议来消除错误
- 易记性设计
- 减少短期记忆负担
- 使用逐层递进的方式展示信息
- 使用直观的快捷方式
- 设置有意义的默认值
- 可视化设计
- 按照任务模型设计界面隐喻,同时不要把软件系统的内部构造机制暴露给用户
- 可视化设计还应该基于界面隐喻,尽可能地把功能和任务细节表现出来
- 简洁设计
- 精神模型:用户进行人机交互时头脑中的任务模型。人机交互设计需要依据精神模型进行隐喻设计
- 差异性
- 新手用户:对业务不熟悉的人
- 专家用户:能够熟练操作计算机完成业务的人
- 熟练用户:介于新手用户和专业用户之间的人
- 好的人机交互应该为不用的用户群体提供差异化的交互机制。
- 导航:为用户提供一个很好的完成任务的入口,好的导航会让这个入口非常符合人的精神模型
- 全局结构按照任务模型将软件产品的功能组织起来,并区分不同的重要性和主体提供给不同的用户;常用的导航控件包括窗口、菜单、列表、快捷方式、热键
- 局部结构通过安排界面布局细节,制造视觉上的线索来给用户提供导航;常用导航控件包括可视化控件布局与组合、按钮设计、文本颜色或字体大小;局部结构的设计主要以用户关注的任务细节为主要依据
- 反馈
- 目的是提示用户交互行为的结果,但不能打断用户工作时的意识流
- 协作式交互:⼈和计算机是⼈机交互的两⽅,其中⼈的因素是⽐较固定的,⼀定时期内不会发⽣⼤的变化,所以要让⼆者交互顺畅,就需要让计算机更多地适应⼈的因素,这也是⼈机交互设计以⽤户为中⼼的根本原因。
这种调整计算机因素以更好地适应并帮助⽤户的设计⽅式被称为协作式设计
12 详细设计
- 详细设计的出发点:软件详细设计是在软件体系结构设计之后进行,以需求开发的结果(需求规格说明和需求分析模型)和软件体系结构的结果(软件体质结构设计方案与原型)为出发点。
- 职责是执行任务(操作职责)或维护某些数据(数据职责)的义务。
- 行为职责通常由行为来履行。
- 数据职责通常由属性来完成。
- 可能会涉及到类之间的协作。
- 职责分配:将大的职责分给很多对象
- 降低耦合提高内聚
- 协作:每个类/对象的职责都是比较有限的,但是通过对象之间进行协作可以完成更大的职责。
- 从小到大,将对象的小职责聚合形成大职责
- 从大到小,将大职责分配给各个小对象
- 控制风格
- 集中式:做决策的只有一个对象,其他对象都只和这个中心控制对象进行交互
- 委托式:做决策的对象不止一个,这些对象分别承担一定的职责,作出一定决策
- 分散式:无法找到明确的控制对象,每个对象都只承担一个相对较小的职责,完全靠各个对象自治的方式来实现大的职责。
- 协作的测试
- Mock Objedt
13 模块化与信息隐藏
耦合:两个模块之间关系的复杂程度
内聚:一个模块内部的联系的紧密性
信息隐藏:隐藏会改变的设计决策,把每个设计秘密指派给单独的模块,封装每个秘密,使得即使发生变化,变化也不会对其他部分产生影响
- 主要秘密:模块所要实现的用户需求
- 次要秘密:模块在实现职责时所涉及的关键实现细节
- 角色:模块在整个系统中所承担的角色、所起的作用,以及与哪些模块有关系
- 对外接口:模块提供给别的模块的接口
- 两种常见的信息隐藏决策:职责的实现;实现的变更
14 详细设计中面向对象方法下的模块化
14.1 设计原则
Global Variables Consider Harmful 全局变量有害
- 公共耦合有风险,增加潜在的链接数量
To be Explicit 让代码清晰一点
让代码兼顾清晰性和可修改性
例如:可修改性强,但代码不清晰
修改:
Do not Repeat 避免重复
- 面向接口编程,而不是重复地写逻辑上一致的代码
Programming to Interface 针对接口编程
The Law of Demeter 迪米特法则
- 每个单元对于其他的单元只能拥有有限的知识,只是与当前单元紧密联系的单元
- 每个单元只能和它的朋友交谈,不能和陌生单元交谈
- 只和自己直接的朋友交谈
- 若对象O有方法M,那么M只能调用下列对象的方法:
- O自己
- M中的参数对象
- 任何在M中创建的对象
- O的成员变量
Interface Segregation Principle(ISP) 接口分离原则
- 将一个统一的接口匹配为多个更独立的接口,避免不必要的耦合,实现接口最小化
Liskov Subsititution Principle(LSP) 里氏替换法则
- 子类型必须能够替换掉基类型而起同样的作用
- 为满足LSP:(前置更弱,后置更强)
- 子类方法的前置条件必须与超类方法的前置条件相同或者要求更少
- 子类方法的后置条件必须与超类方法的后置条件相同或者要求更多
- 子类是父类的一个特殊类型,而不是父类扮演的一个角色
Favor Composition Over Inheritance 使用组合替代继承
- 只为了复用而不为了组织类型差异的继承用法往往不符合LSP,应该用组合替代继承
Single Responsibility Principle(SRP) 单一职责原则
- 信息与行为除了要集中之外,还要联合起来表达一个内聚的概念,而不是单纯的堆砌
- 一个类只能有一个改变的理由
面向对象的内聚
15 详细设计中面向对象方法下的信息隐藏
信息隐藏:一个模块应该通过稳定的接口对外表现其所承载的需求,而隐藏它对需求的内部实现细节。
封装的含义:
- 将数据和行为同时包含在类中
- 分离对外接口与内部实现
封装实现细节
- 封装数据和行为:Getter和Setter
封装内部结构:
迭代器模式
封装其他对象的引用
封装类型信息:LSP使用父类接口隐藏子类的类型信息
封装潜在变更:如果预测会发生变更,就应该将其独立为单独的类或者方法,然后为单独的类或方法抽象建立稳定的接口,并在原类中使用该稳定接口以屏蔽潜在变更的影响
Minimize The Accessibility of Classes and Members 权限最小化原则
Open Close Principle(OCP) 开闭原则
- 对扩展开放;对修改封闭
- RTTI is Ugly and Dangerous:运行时类型信息很丑(instanceof😡丑归丑,用还是得用)
Dependency Inversion Principle(DIP) 依赖倒置原则
- 抽象不应该依赖与细节,细节应该依赖于抽象
- 高层模块不应该依赖与低层模块,而是双方都依赖于抽象
16 详细设计的设计模式
- 可修改性的含义
- (狭义)可修改性(对已有实现的修改)
- 可扩展性(对新的实现的扩展)
- 灵活性(对实现的动态配置)
- 如何实现可修改性、可扩展性、灵活性
- 接口与实现的分离
- 通过接口与实现该接口的类,将接口与实现相分离
- 通过子类继承父类,将父类的接口与子类的实现相分离
- 接口与实现的分离
- 策略模式:首先,可以把上下文和策略分割为不同的类实现不同的职责。上下文Context类负责通过执行策略实现自己职责;而策略类Strategy只负责复杂策略的实现。
- 上下文(Context): 被配置了具体策略信息;ConcreteStrategy;拥有Strategy对象的一个引用;实现了一些方法以供Strategy访问其数据。
- 策略(Strategy): 声明了所支持策略的接口。Context利用这些被ConcreteStrategy定义的接口。
- 具体策略(ConcreteStrategy): 实现了Strategy声明的接口,给出了具体的实现。
- 优点:
- 避免多重选择语句
- 可以动态选择不同的策略
- 缺点:
- 用户必须事先知道所有的策略
- 会创建出较多的对象
- 使用的原则:依赖倒置、减少耦合、用组合代替继承、OCP、LSP
抽象工厂
- 抽象工厂:声明了创建抽象产品的各个接口
- 具体工厂:实现了对具体产品的创建
- 抽象产品:声明一种产品的接口
- 具体产品:定义了具体工厂中创建出来的具体产品,实现了抽象产品的接口
- 客户:使用抽象工厂和抽象产品的类。使用抽象工厂的方法来创建产品
单件模式:内存中只存在一个对象实例,只能通过getInstance()获取唯一的静态实例对象
迭代器模式:提供一种顺序访问一个聚合对象的各个元素,而不暴露其内部表示。
- 迭代器:迭代器定义访问和遍历元素的接口
- 具体迭代器:实现迭代器接口。对该聚合遍历时跟踪当前位置
- 聚合:聚合定义创建相应迭代器对象的接口
- 具体聚合:具体聚合是想创建相应迭代器的接口,该操作返回ConcreteIterator的一个适当的实例
17 软件构造
- 构造包含的活动:详细设计、编程、测试、调试、代码评审、集成与构建、构造管理
- 重构:修改软件系统的严谨方法,它在不改变代码外部表现的情况下改进其内部结构。
- 测试驱动开发:要求程序员在编写一段代码之前,优先完成该段代码的测试代码。完成测试代码之后,程序员再编写程序代码,并在编程中重复执行测试代码,以验证程序代码的正确性
- 结对编程:两个程序员挨着坐在一起,共同协作进行软件构造活动
18 代码设计
18.1 易读性
- 格式
- 使用缩进与对齐表达逻辑结构
- 将相关逻辑组织在一起:变量声明、构造函数、析构函数、public方法、private方法分别放在一起
- 使用空行分割逻辑
- 语句分行
- 命名
- 使用有意义的名称进行命名
- 名称要与实际内容相符
- 如果存在惯例,命名时要遵守惯例
- 临时变量命名要符合常规:i, j计数;c字符;s 字符串
- 不要使用太长的名称
- 不要使用易混字符进行命名:如I, l, o, 0
- 不要仅仅使用不易区分的多个名称:sales和sale
- 不要使用没有任何逻辑的字母缩写进行命名
- 注释
- 文档注释
- 内部注释
- 注释要有意义,不要简单重复代码的含义
- 重视对数据类型的注释
- 重视对复杂控制结构的注释
18.2 易维护性
- 小型任务:通过将不同的代码片段抽象为不同的任务接口,可以解决复杂代码的几种不理想但无法回避的内聚—时间内聚、过程内聚和通信内聚。
- 复杂决策:业务规则容易发生修改,表现为对复杂决策(布尔表达式)的修改
- 使用新的布尔变量简化复杂决策
- 使用有意义的名称封装复杂决策
- 表驱动编程
- 数据使用
- 不要将变量应用于与命名不符的目的
- 不要将单个变量用于多个目的。例如total用来表示销售的总结,使用结束后客串for的计数器
- 限制全局变量的使用
- 不要使用突兀的数字与字符,要将它们定义为常量或变量后使用
- 明确依赖关系
- 明确注释
18.3 可靠性
- 契约式设计
- 基本思想:如果一个函数或方法,在前置条件满足的情况下开始执行,完成后能够满足后置条件,那么这个函数或方法就是正确的、可靠的。
- 实现方式:异常和断言
- 防御式编程
- 基本思想:在一个方法与其他方法、操作系统、硬件等外界环境交互时,不能确保外界都是正确的,所以要在外界发生错误时,保护方法内部不受损害
18.4 单元测试用例的设计
18.5 代码复杂度度量
衡量圈复杂度的基本思想是计算程序中独立路径的最大数量
- 方法一:节点数N,边数E,圈复杂度V = E - N + 2
- 方法二:
- 从1开始,往下通过程序
- 遇到下列关键字加1:if, while, loop, for
- case语句中的每一种情况都加一
19 软件测试
19.1 黑盒测试
把测试对象看做一个黑盒子,完全基于输入和输出数据来判定测试对象的正确性。使用规格说明来设计输入和输出数据
等价类划分:将所有可能的输入数据,即程序的输入域划分成若干部分,然后从每一个子集中选取少数具有代表性的数据作为测试用例。需要同时考虑有效等价类和无效等价类
边界值分析:针对边界情况设计测试用例
决策表:决策表是为复杂逻辑判断设计测试用例的技术。决策表示由条件声明、行动声明、规则选项和行动选项等四个象限组成的表格。
- 状态转移:状态转换是针对复杂测试对象的测试技术。该类复杂测试对象对输入数据的反映是多样的,还需要依赖自身的状态才能决定;状态转换包含有效转换和无效转换,只有在复杂情况和可靠性要求较高的情况下才会为无效转换设计测试用例。
19.2 白盒测试
白盒测试是将测试对象看做透明的,不关心测试对象的规格,而是按照测试对象内部的程序结构来设计测试用例进行测试工作。
- 语句覆盖:确保被测试对象的每一行程序代码都至少执行一次
- 比较弱,不能覆盖所有的执行路径
- 条件覆盖:确保程序中每个判断的每个结果都至少满足一次
- 比语句覆盖强,但仍不保证覆盖所有执行路径
- 路径覆盖:确保程序中每条独立的执行路径都至少执行一次
19.3 测试方法
给出一个场景,判断应该使用哪种测试方法,如何去写(*)
- 对给定的场景和要求的测试方法,设计测试用例
- 给出功能需求,则要求写功能测试用例
- 给出设计图,则要求写集成测试用例,Stub and Driver
- 给出方法的描述,则要求写单元测试用例,Mock Object
- JUnit基本使用方法
20 软件交付
- 用户文档
- 用户文档是指为用户编写参考指南或者操作教程,常见的如用户使用手册、联机帮助文档等,统称为用户文档。
- 文档内容的组织应当支持其使用模式,常见的是指导模式和参考模式两种。
- 系统文档
- 与用户文档注重系统使用细节不同,系统管理员文档更注重系统维护方面的内容,例如系统性能调整、访问权限控制、常见故障解决等等。因此,系统管理员文档需要详细介绍软硬件的配置方式、网络连接方式、安全验证与访问授权方法、备份与容灾方法、部件替换方法等等。
21 软件维护与演化
- 软件维护是在交付之后修改软件系统或其部件的活动过程,以修正缺陷、提高性能或其他属性、适应变化的环境。
如何理解软件维护的重要性
- 由于会出现新的需求,如不维护软件将减小甚至失去服务用户的作用。
- 随着软件产品的生命周期越来越长,在软件生存期内外界环境发生变化的可能性越来越大,因此,软件经常需要修改以适应外界环境的改变
- 软件产品或多或少的会有缺陷,当缺陷暴露出来时,必须予以及时的解决
- 开发可维护软件的方法
- 考虑软件的可变更性:分析需求易变性、为变更进行设计
- 为降低维护困难而开发:编写详细的技术文档并保持及时更新、保证代码可读性、维护需求跟踪链、维护回归测试基线
- 演化式生命周期模型
- 初始开发阶段完成第一个运行版本
- 演化阶段通过修改保持软件产品的持续增值,让软件产品能够满足用户越来越多的需要,实现更大的业务价值
- 服务阶段不再增加自己的价值,周期性地修复已有缺陷
- 逐步淘汰阶段不再维护软件,但用户可能继续使用软件
- 停止状态开发者不再维护,用户也不再使用
逆向工程
处理遗留软件时,维护人员接受的维护对象可能是一个没有任何文档也没有程序源代码的软件程序,此时,维护人员需要使用逆向工程技术
逆向工程技术是指:”分析目标系统,标识系统的部件及其交互关系,并且使用其它形式或者更高层的抽象创建系统表现的过程[Chikofsky1990]”。
逆向工程的基本原理是抽取软件系统的需求与设计而隐藏实现细节,然后在需求与设计的层次上描述软件系统,以建立对系统更加准确和清晰的理解。
再工程:检查和改造一个目标系统,用新的模式及其实现复原该目标系统
[Arnold1993]认为再工程主要是下列两类活动:
- 改进人们对软件的理解
- 改进软件自身,通常是提高其可维护性、可复用性和可演化性
常见的具体活动有
- 重新文档化
- 重组系统的结构
- 将系统转换为更新的编程语言
- 修改数据的结构组织。
22 软件开发过程模型
22.1 软件生命周期模型
人们将软件从生产到报废的生命周期分割为不同阶段,每段阶段有明确的典型输入/输出、主要活动和执行人,各个阶段形成明确、连续的顺次过程,这些阶段划分就被称为软件生命周期模型。
22.2 构建—修复模型
开发人员在开始生产软件时,依靠个人分析和理解直接构建软件的第一个版本,并提交给用户使用。第一版提交后常常会发现缺陷,开发人员就修改代码修复缺陷,把发现的缺陷都修复完成后才算是完成了有效的交付,进入维护阶段
特点:
没有对开发过程进行规范和组织,因此一旦开发过程超出个人控制能力,就会导致开发过程无法有效进行而失败。
对需求的真实性没有进行分析
没有考虑软件结构的质量,导致结构在修改中越来越糟,直至无法修改
没有考虑测试和程序的可维护性,也没有任何文档,导致难以维护
- 适用场景:软件规模很小,只需要几百行程序,其开发复杂度是个人能力能够胜任的;软件对质量的要求不高,即使出错也无所谓;只关注开发活动,对后期维护的要求不高,甚至不需要进行维护。
22.3 瀑布模型
按照软件生命周期模型将软件开发活动组织为需求开发、软件设计、软件实现、软件测试、软件交付和软件维护等活动,并且规定了它们自上而下、相互邻接的次序。
优点:为软件开发活动定义了清晰的阶段划分(包括输入/输出、主要工作及其关注点),这让开发者能够以关注点分离的方式更好地进行那些复杂度超越个人能力的软件项目的开发活动。
缺点:
- 对文档的过高的期望具有局限性
- 对开发活动的线性顺序假设具有局限性
- 客户、用户的参与具有局限性:成功的项目开发需要客户、用户从始至终的参与,而不仅仅是一个阶段。
- 里程碑粒度具有局限性:里程碑粒度过粗,基本丧失了”早发现缺陷早修复”这一思想
适用
- 需求非常成熟、稳定,没有不确定的内容,也不会发生改变
- 所需的技术成熟、可靠,没有不确定的技术难点,也没有开发人员不熟悉的技术问题
- 复杂度适中,不至于产生太大的 文档负担和过粗的里程碑
22.4 增量迭代模型
增量迭代模型是在项目开始时,通过系统需求开发和核心体系结构设计活动完成项目对前景和范围的界定,然后再将后续开发活动组织为多个迭代、并行的瀑布式开发模型。需求驱动。
优点:
- 迭代式开发更加符合软件开发的实践情况,具有更好的适用性;
- 并行开发可以帮助缩短软件产品的开发时间;
- 渐进交付可以加强用户反馈,降低开发风险。
缺点:
- 由于各个构件是逐渐并入已有的软件体系结构中的,所以加入构件必须不破坏已构造好的系统部分,这需要软件具备开放式的体系结构。
- 增量交付模型需要一个完备、清晰的项目前景和范围以进行并发开发规划,但是在一些不稳定的领域,不确定性太多或者需求变化非常频繁,很难在项目开始就确定前景和范围。
适用:适用于大规模软件系统的开发
22.5 演化模型
演化模型将软件开发活动组织为多个迭代、并行的瀑布式开发活动。初始开发后根据用户反馈规划后续迭代。对需求的反馈是演化模型进行迭代规划、开发活动组织和控制的主要依据,因此它也是“需求驱动”的。
优点:
- 使用了迭代式开发,具有更好的适用性,尤其是其演化式迭代安排能够适用于那些需求变更比较频繁或不确定性较多的软件系统的开发;
- 并行开发可以帮助缩短软件产品的开发时间;
- 渐进交付可以加强用户反馈,降低开发风险。
缺点:
- 无法在项目早期阶段建立项目范围,所以项目的整体计划、进度调度、尤其是商务协商事宜无法准确把握;
- 后续迭代的开发活动是在前导迭代基础上进行修改和扩展的,这容易让后续迭代忽略设计分析与设计工作,蜕变为构建-修复方式。
适用:不稳定领域的大规模软件系统开发
22.6 原型模型
为了解决不确定性,原型模型将需求开发活动展开为抛弃式原型开发的迭代,充分利用抛弃式原型解决新颖领域的需求不确定问题。
优点:
- 对原型方法的使用加强了与客户、用户的交流,可以让最终产品取得更好的满意度;
- 适用于非常新颖的领域,这些领域因为新颖所以有着大量的不确定性。
缺点
原型方法能够解决风险,但是自身也能带来新的风险,例如原型开发的成本较高,可能会耗尽项目的费用和时间;
实践中,很多项目负责人不舍得抛弃”抛弃式原型”,使得质量较差的代码进入了最终产品,导致了最终产品的低质量。
适用:适用于具有大量不确定的新颖领域进行开发活动组织。
22.7 螺旋模型
螺旋模型是风险驱动的,完全按照风险解决的方式组织软件开发活动。
将软件开发活动组织为风险解决的迭代:确定目标、解决方案和约束->评估方案,发现风险->寻找风险解决方法->落实风险解决方案->计划下一个迭代
自内向外,螺旋模型有4次风险解决迭代,分别解决了几个高风险的阶段的问题
解决系统需求开发中的风险,尤其是产品概念设计风险,得到一个确定的产品前景和范围。
解决软件需求开发中的风险,得到清晰的软件需求
解决软件体系结构设计中的技术风险,构建高质量的核心体系结构原型。
解决详细设计和实现中的关键技术风险,建立一个可实现的高质量软件结构。
优点:可以降低风险,减少项目因风险造成的损失
缺点:
- 风险解决需要使用原型手段,也就会存在原型自身带来的风险,这一点与原型模型相同;
- 模型过于复杂,不利于管理者依据其组织软件开发活动
适用:高风险的大规模软件系统开发
22.8 Rational统一过程模型
统一过程(Rational Unified Process,RUP)总结和借鉴传统上的各种有效经验,建立最佳实践方法的集合,并提供有效的过程定制手段,允许开发者根据特定的需要定制一个有效的过程模型。
实践方法:
- 迭代式开发,这是过去被反复证明的最佳实践方法;
- 管理需求,重视需求工程中除了需求开发之外的需求管理活动;
- 使用基于组件的体系结构,它帮助建立一个可维护、易开发、易复用的软件体系结构;
- 可视化建模,利用UML进行建模;
- 验证软件质量,尽早和持续地开展验证,以尽早发现缺陷,降低风险和成本;
- 控制软件变更,适应1990s以后需求变更越来越重要的事实。
优点:
- 吸收和借鉴了传统上的最佳实践方法,尤其是其核心的6个实践方法,能够保证软件开发过程的组织是基本有效和合理的。
- RUP依据其定制机制的不同,可以适用于小型项目,也可以适用于大型项目的开发,适用面广泛。
- RUP有一套软件工程工具的支持,这可以帮助RUP的有效实施。
缺点:
没有考虑交付之后的软件维护问题
裁剪和配置工作不是一个简单的任务,无法保证每个项目都能定制一个有效的RUP过程。
适用:RUP是重量级过程,能够胜任大型软件团队开发大型项目时的活动组织。但RUP经过裁剪和定制,也可以变为轻量级过程,也能够胜任小团队的开发活动组织。
22.9 敏捷过程
并不是要为软件开发活动组织提供一种特定的过程模型,而是倡导一些指导性的思想和原则。
最为重要的敏捷思想是敏捷联盟宣言所声明的价值观:
个体和互动 高于 流程和工具
工作的软件 高于 详尽的文档
客户合作 高于 合同谈判
响应变化 高于 遵循计划
极限编程:利用简单、有效的方法解决问题
23 软件工程职业基础
- 软件工程知识体系的知识域:
- 软件需求
- 软件设计
- 软件构造
- 软件测试
- 软件维护
- 软件配置管理
- 软件工程管理
- 软件工程过程
- 软件工程工具和方法
- 软件质量