第一章 软件工程学概述
1.1 软件危机
软件危机:在计算机软件的开发和维护过程中所遇到的一系列严重问题。
典型表现P2:
- 开发成本和进度估计不准确
- 用户对完成的软件系统不满意
- 软件产品质量靠不住
- 软件不可维护
- 没有适当的文档资料
- 软件成本在计算机系统成本中占比上升
- 软件开发生产效率的提高追不上计算机普及的深入
产生原因P3:
- 不同于硬件,缺乏可见性,维护即要修改原来的设计
- 不同于一般程序,规模庞大多人分工
软件开发工作量:
- 编写10%-20%
- 测试40%-50%
- 运行维护:长期,费用占55%-70%
软件配置:程序、文档、数据
软件开发最主要的失败原因:对客户的需求没有全面理解
软件开发的修改成本:在中期剧增
消除途径P4:
- 正确认识软件是程序文档数据的完整集合
- 充分认识软件开发是各类人员协同配合的工程项目
- 推广软件开发的成功技术,消除早期错误观念
- 开发和使用更好的软件工具
1.2 软件工程
软件工程:采用工程的概念、原理、技术、方法来开发和维护软件,把经过时间考验而证明正确的管理技术和当前能够得到的最好技术方法结合起来,以经济地开发出高质量的软件并有效地维护它。
本质特征7个:中心课题是控制复杂性、注重大型程序的构造、和谐开发合作P6
软件工程方法学(泛型):在软件生命周期全过程中使用的一整套技术方法
- 三要素:方法、工具、过程
- 方法P9:传统方法、面向对象方法
1.3 软件生命周期
软件生命周期P11-14:
- 定义时期:问题定义、可行性研究、需求分析
- 开发时期:
- 系统设计:总体设计、详细设计
- 系统实现:编码和单元测试、综合测试
- 维护时期:
- 软件维护:改正性维护、适应性维护、完善性维护、预防性维护
- 问题定义:确定客户的问题性质,工程目标和工程规模
- 可行性研究:经济、技术、法律可行性 → 第二章
- 需求分析:确定系统必须具备的功能、性能,产生规格说明书 → 第三章
- 总体设计(概要设计):推选设计方案及其详细计划,确定程序模块间的关系 → 第五章
- 详细设计:详细设计每个模块,确定实现模块功能需要的算法和数据结构 → 第六章
- 编码和单元测试:仔细编写、测试每一个模块 → 第七章
- 综合测试:集成测试(装配后测试)、验收测试(用户参与测试) → 第七章
- 软件维护:使系统持久满足用户的需要 → 第八章
1.4 软件过程
生命周期模型(过程模型):描述软件过程,规定了把生命周期划分成哪些阶段和各个阶段的执行顺序。
- 瀑布模型P15:文档驱动型,适合小规模软件
- 阶段间具有顺序性和依赖性(各阶段依次进行)
- 推迟实现(尽可能推迟物理实现)
- 质量保证(每个阶段完成文档,并进行审核)
- 优点:强迫开发人员采用规范方法,文档驱动降低维护成本
- 缺点:前期没产品,无法应对改进的需求
- 快速原型模型P16:取得客户满意的原型再做规格说明书,无反馈环
- 较好适应客户需求描述不完全或不确定的情况,减少返工
- 开发人员在建立原型中得到经验,减少犯错的可能
- 原型在得到用户确认后会被抛弃,但界面等可以得到保留
- 优点:快速、经济,适合招标
- 增量模型(渐增模型)P17:将软件拆分成构件逐步交付客户
- 完成需求分析规格说明和概要设计后,将软件看作增量构件的组合
- 从核心构件开始逐渐集成软件(但必须是可测试的)
- 优点:逐步交付客户,使客户有充裕时间学习适应
- 缺点:开发人员需要协调软件的整体性和构件的独立性
- 螺旋模型P19:以瀑布和快速原型为基础,每个阶段前都增加风险分析
- 优点:风险驱动,比快速原型更好的控制各类风险,适合内部开发的大规模软件
- 缺点:开发人员需要有风险意识和经验
- 喷泉模型P21:面向对象模型
- 将线性过程作为总目标
- 对开发活动进行迭代求精,不同阶段无明显边界
- Rational统一过程RUP P22:
- 6个开发经验:迭代开发、管理需求、使用基于构件的体系结构、可视化建模、验证软件质量、控制软件变更
- 6个核心过程工作流程、3个核心支持工作流程
- 4个工作阶段:
- 初始阶段(建立业务模型,定义产品视图,确定项目范围)
- 精化阶段(确定体系结构,制定项目计划,确定资源需求)
- 构建阶段(开发构件和应用程序、集成并测试)
- 移交阶段(将开发的产品提交用户使用)
- 敏捷过程P25:客户密切合作、快速响应
- 个体和交互胜过过程和工具:构建开发团队,再配置过程和工具
- 可以工作的软件胜过面面俱到的文档:文档尽可能精简
- 客户合作胜过合同谈判:开发团队和客户密切合作
- 相应变化胜过遵循计划:计划具备可塑性
- 极限编程P26:敏捷过程的一种,适合需求模糊且经常改变的场合
第二章 可行性研究
2.1 可行性研究的任务
可行性研究:研究问题是否值得解决,而非解决问题
- 技术可行性:有技术实现?
- 经济可行性:经济效益超过开发成本?
- 操作可行性:系统的操作方式在用户组织内是否可行?
- 法律、社会效益可行性
根本性任务:对以后的行动方针提出建议。
过程:复查系统规模和目标、研究目前正在使用的系统、导出新系统的高层逻辑模型、进一步定义问题、导出和评价供选择的解法、推荐行动方针、草拟开发计划、书写文档提交审查
2.4 数据流图☆
数据流图OFD:逻辑模型,分层次的描绘方法便于读者从抽象到具体逐步深入了解系统。
- 描绘信息和数据从输入移动到输出过程中所经受的变化
- 只描绘数据在软件流动和被处理的逻辑过程
- 数据存储和数据流是数据的静态和动态表现
- 数据的符号表示(*+异或)
数据库的写入写出流可以不写名字
事物流、变换流
父子图平衡、数据守恒
2.5 数据字典
逻辑模型:数据流图+数据字典
定义数据的方法:
- 顺序:确定顺序的连接分量,如D=A+B+C
- 选择:标所多个元素中取一个,如G=[D|E|F]
- 重复:将分量重复若干次,如H=3{G}5表示G重复3到5次
- 可选:重复零到一次,如I=(H)
第三章 需求分析
3.1 需求分析的任务
需求分析:系统必须做什么?定义时期最后一个阶段,产生软件需求规格说明书。
- 确定对系统的综合要求:
- 功能需求:系统必须提供的服务
- 性能需求:系统必须满足的定时约束和容量约束
- 可靠性和可用性需求:系统的可靠性(重启,出错等)
- 出错处理需求:系统对环境错误的处理和响应(别的系统或者环境错了)
- 接口需求:系统与环境的通信格式
- 约束:设计和实现时需要遵守的限制(语言限制、硬件平台限制)
- 逆向需求:软件系统不该做什么
- 将来可能提出的要求:据分析未来很可能出现的需求
- 分析系统的数据要求
- 导出系统的逻辑模型
- 修正系统开发计划
3.2 与用户沟通获得需求的方法
与用户沟通获得需求的方法P59:
- 访谈:正式、非正式访谈;调查表、情景分析技术
- 面向数据流自顶向下求精:把数据流图中数据流和数据存储定义到元素级
- 建议的应用规格说明技术:面相团队,客户和开发人员密切合作(开需求分析会)
- 快速建立软件原型:快速建立用于演示目标系统功能的模型
3.3 分析建模与规格说明
结构化分析SD:面向数据流自顶向下,逐步求精进行需求分析的方法。
实质上是一种创建模型的活动:实体-联系图、数据流图、状态图
3.8 验证软件需求
验证软件需求P70:
- 一致性:需求与需求不矛盾。采用形式化需求陈述语言书写
- 完整性:规格说明书包括所有用户需要的功能和性能。使用原型系统,和客户密切合作。
- 现实性:需求的软件硬件实现现实性。根据过往开发经验。必要时使用仿真技术
- 有效性:需求能有效解决用户面对的。使用原型系统,和客户密切合作
第五章 总体设计
5.1 设计过程
总体设计(概要设计、初步设计):概括的说,系统应当如何实现。划分出组成系统的物理元素,并设计软件的结构,设计数据库并制定测试计划。P92
- 设想供选择的方案:从数据流图出发,抛弃技术不可行的方法
- 选择合理的方案:低、中、高成本三套方案,并提供系统流程图、物理元素清单、成本效益分析、实现系统的进度计划
- 推荐最佳方案:综合利弊推荐最佳方案,制定详细计划
- 功能分解:将复杂的功能分解,使每个功能对程序员明显易懂
- 设计软件结构:把模块组织成良好的层次系统,顶层调用下层实现
- 设计数据库:详见数据库课程
- 制定测试计划:尽早考虑测试问题,详见第七章
- 书写文档:系统说明、用户手册、测试计划、详细的实现计划、数据库设计结果
- 审查和复查:严格审查,客户复审
5.2 设计原理
设计原理P94:
- 模块化:把程序划分成独立命名且可独立访问的模块,每个模块完成一个子功能。把模块集成在一起又能满足客户需求。模块化程度适中开发成本最低(接口成本)
- 抽象:用层次的方式构造和分析
- 逐步求精:将精力放在当前开发阶段的重点上,稍后处理其他必要细节
- 信息隐藏和局部化:模块内部信息独立于其他模块而不可被访问;将关系密切的软件元素物理上彼此靠近
- 模块独立P97:高内聚,低耦合
- 耦合:(好、低)数据<控制=特征<公共环境<内容(不好、高)
- 数据耦合:模块通过参数交换数据
- 控制耦合:模块通过参数交换控制信息(可分解模块转化为数据耦合)
- 特征耦合:模块接收的参数数据结构中有冗余数据信息(访问权限漏洞)
- 公共环境耦合:多个模块共用数据环境(全局变量)
- 内容耦合:访问模块内部数据、不正常访问、代码重叠、多入口
- 内聚:(好、高)功能>顺序>通信>过程>时间>逻辑>偶然(不好、低)
- 偶然内聚:一组没有完整功能的语句巧合地被多次使用
- 逻辑内聚:不同功能的语句混在一起,共用部分代码
- 时间内聚:语句在同一段时间内执行
- 过程内聚:模块内元素相关,且以特定顺序执行
- 通信内聚:模块使用同一个输入产生同一个输出
- 顺序内聚:模块处理元素和同一功能密切相关,且顺序执行
- 功能内聚:模块所有元素属于同一整体,完成单一功能
- 耦合:(好、低)数据<控制=特征<公共环境<内容(不好、高)
5.3 启发规则
软件系统总体设计的启发规则:
- 改进软件结构提高模块独立性:高内聚低耦合
- 模块规模适中:模块复杂和借口复杂折衷
- 深度、宽度、扇出和扇入适当:
- 深度:软件结构中控制的层数
- 宽度:一层中模块总数的最大值
- 扇出:一个模块直接调用的模块数
- 扇入:一个模块被上级调用的模块数
- 模块的作用域应该在控制域之内:
- 控制域:自身和直接或间接从属模块的集合
- 力争降低模块接口的复杂度:参数和返回值应当简单且易理解
- 设计单入口单出口模块:不要随意return
- 模块功能要可预测:相同输入对应相同输出
5.5 面向数据流的设计方法
变换流:数据从外部进入软件系统,经处理后离开系统进入外部
事物流:数据到达一个处理后根据输入类型从若干个动作中选取一个执行
第六章 详细设计
6.1 结构程序设计
详细设计:确定应该怎么具体实现所要求的系统。不仅要保证逻辑上正确实现每个模块的功能,更要保证处理过程尽可能简明易懂。
结构程序设计:(五要素)仅通过顺序、选择和循环控制结构连接,且每个代码块只有一个入口,一个出口。
- 经典结构程序设计:顺序、IF-ELSE、DO-WHILE
- 扩展结构程序设计:CASE、DO-UNTIL
- 修正结构程序设计:LEAVE、BREAK
6.3 过程设计工具
过程设计工具 P124-129:大题
- 程序流程图
- 不是逐步求精的好工具,诱导程序员过早考虑控制流程
- 允许随意转移数据,不受约束
- 不易表示数据结构
- 盒图(NS图)
- 功能域明确
- 不能任意转移控制
- 容易确定数据的作用域
- 容易表现嵌套关系,体现层次结构
- PAD图
- 设计产生的必然是结构化程序
- 程序结构清晰,竖线总条数即程序层次数
- 表现程序逻辑,易读易懂易记
- 易于转化为高级语言
- 可以表示程序逻辑,也可以表示数据结构
- 支持自顶向下逐步求精的方法
- 判定表、判定树
6.5 程序复杂度的定量度量
环形复杂度:度量程序复杂度的一种方法。
- 线性无关的区域数(即离散中平面图面数)
- 边数-节点数+2
- 判定节点数+1
第七章 实现
实现:编码和测试。软件测试工作量占开发的40%以上,安全软件更多。
7.1 编码
选择程序设计语言:除了选择适宜的程序设计语言外,也要考虑以下因素 P146
- 系统与用户的要求:客户限制
- 可以使用的编译程序:目标环境限制编译程序
- 可以得到的软件工具
- 工程规模:规模过大可以自己设计语言(书上观点)
- 程序员的知识:技术栈
- 软件可移植性要求
- 软件的应用领域:科学计算、Web开发等
编码风格: P147
- 程序内部文档:变量命名、模块注解、代码注解、程序清单的布局
- 数据说明:数据结构和用途的说明
- 语句构造:避免写一行、避免非常复杂的条件测试、避免大量嵌套等
- 输入输出:对输入做合法性检验,设计良好的输出报表等
- 效率:程序运行时间、存储器效率、输入输出效率
7.2 软件测试基础
软件测试的目标:
- 测试是为了发现程序中的错误
- 好的测试方案极可能发现迄今为止未发现的错误
- 成功的测试是发现了迄今为止未发现的错误的测试
软件测试:为了发现程序中的错误而执行程序的过程
软件测试的目的:在软件投入生产性运行之前,尽可能多地发现软件中的错误
软件测试准则:
- 测试追溯客户需求
- 远在测试开始前制定测试计划(需求分析阶段)
- Pareto原理:80%的错误来自20%的模块
- 从小规模测试开始逐步到大规模测试
- 穷举测试是不可能的
- 由第三方从事测试工作可以最大可能发现错误
黑盒测试:已经知道产品应该具有的功能,通过测试验证每个功能是否正常使用
白盒测试:知道产品内部工作过程,通过测试检验内部动作是否按照规格说明书正常进行
测试步骤:
- 模块测试(单元测试):验证每个模块作为一个单元能正确运行
- 子系统测试:将模块组成子系统测试,着重测试模块的接口
- 系统测试:子系统组成系统测试需求和功能,寻找需求说明的错误和软件总体设计错误
- 验收测试(确认测试):将软件实体作为单一实体测试,在用户积极参与下进行
- 平行运行:同时运行新系统和旧系统比较差异
7.3 单元测试
单元测试 P153:
- 测试重点:模块接口、局部数据结构、重要执行通路、出错处理通路、边界条件
- 代码审查:单元测试由组长、设计者、编写者、测试者建立审查小组测试
- 计算机测试:编写驱动程序测试模块,代替模块接口进行测试
7.4 集成测试
集成测试 P156:子系统测试 + 系统测试
将模块组装成程序的两种方法:非渐增式测试方法、渐增式测试方法
非渐增式测试:
- 对模块做单元测试
- 将所有模块组装在一起
- 将组装后的程序作为整体进行测试
缺点:无错误隔离手段,主要设计错误发现迟
渐增式测试:将单元测试和集成测试组合起来,把下一个要测试的模块同已经测试好的那些模块结合起来进行测试
- 自顶向下集成:
- 用存根程序代替直属控制模块
- 根据树的dfs序或bfs序进行集成
- 逐步加入新的模块,可能需要进行回归测试
- 优点:错误隔离;不需要驱动程序;早期就可以验证程序功能;较早发现接口错误
- 缺点:需要存根程序(桩模块);低层错误可能很晚才能发现
- 回归测试:加入新模块后将原有的测试再做一遍
- 自底向上集成:
- 根据树的逆bfs序进行集成
- 将底层模块组成簇,编写驱动程序测试
- 去掉驱动程序,将上层模块加入进来,簇合并
- 优点:错误隔离;可复用模块得到充分测试;不需要存根程序
- 缺点:需要驱动程序;主要设计错误发现迟;验证系统功能迟
- 三明治式集成:
- 确立一个模块层为界限
- 上层使用自顶向下策略
- 下层使用自底向上策略
- 优点:集大成,减少了驱动模块和存根程序的开发
- 缺点:中间层的测试非常晚
7.5 确认测试
确认测试(验收测试):验证软件的有效性,即符合用户的功能和性能需求。用户积极参与,通常使用黑盒测试方法。
- 软件配置复查:软件配置齐全符合要求;文档和程序一致;手册正确性完整性
- Alpha测试:用户在开发者的场所进行,在开发者指导下进行测试
- Beta测试:最终用户在客户场所进行,开发者不在场,但记录测试过程中的问题
7.8 调试
调试:在测试发生错误后排除错误的过程(艰巨的脑力劳动)
- 蛮干法:输出日志,并查找问题
- 回溯法:人工沿控制流回溯源码
- 原因排除法:对分查找法、归纳法、演绎法
第八章 维护
8.1 软件维护的定义
软件维护:在软件已经交付使用后,为了改正错误或满足新的需要而修改软件的过程。
- 改正性维护:诊断和改正错误的过程
- 适应性维护:为了和变化的环境适当配合而进行的修改软件活动
- 完善性维护:增加用户提出的新功能或修改已有功能
- 预防性维护:为未来的改进奠定良好的基础
完善>>适应>=改正>其他
8.2 软件维护的特点
结构化维护:完整的软件配置(文档、测试等)减少了精力浪费并提高了维护的总体质量
维护代价高昂:甚至达到预算的70-80%
维护的问题:
- 理解他人代码的困难
- 文档资料不足
- 开发人员可能已经调离
- 设计时未考虑将来的修改
- 软件维护不是吸引人的工作
8.4 软件的可维护性
决定软件可维护性的因素:
- 可理解性:功能、接口、内部处理逻辑;模块化、文档、语言
- 可测试性:可用的测试、调试工具
- 可修改性:耦合、内聚、信息隐藏、局部化、控制域与作用域的关系
- 可移植性:硬件环境移植的难题程度
- 可重用性:可重用构件越多,测试的可靠性越高,维护的精力越少
第九至十一章 面向对象方法学
9.1 面向对象方法学概述
OO = objects + classes + inheritances + communication with messages
面向对象 = 对象 + 类 + 继承 + 消息通信
- 对象:客观世界由对象组成,复杂的对象由简单的对象组成
- 类:将对象划分成对象类,定义属性和方法
- 继承:子类具有和基类相同的属性或方法
- 消息传递:对象间通过传递消息相互联系
面向对象方法学的优点P205:
- 与人类思维习惯一致
- 稳定性好:以对象为中心构造系统
- 可重用性好:数据和操作是平等的;对象类可重复使用
- 较易开发大型软件产品
- 可维护性好:易改易维护易调试易理解
在UML术语中“类”的实际含义是“一个类及属于该类的对象”。
- 对象模型:类图
- 功能模型:用例图
- 动态模型:顺序图、状态图
11.1 面向对象设计准则
面向对象设计:
- 系统设计:系统策略、目标系统的高层结构
- 对象设计:类、关联、接口
面向对象设计准则:
- 模块化
- 抽象
- 信息隐藏
- 弱耦合P260:
- 交互耦合:对象通过消息连接来实现耦合(应尽可能松散、参数少而简单)
- 继承耦合:基类和派生类的耦合(应保持紧密)
- 强内聚P261:
- 服务内聚:一个服务完成且仅完成一个功能
- 类内聚:一个类有且仅有一个用途
- 一般-特殊(层次)内聚:符合多数人的观念
- 可重用
11.2 启发规则
提高面向对象设计质量的启发规则P261:
- 设计结果清晰易懂
- 一般-特殊结构深度适当:类层数7±2
- 使用简单的协议:参数少
- 使用简单的服务:避免一个方法太复杂
- 把设计变动减至最小
11.3 软件重用
软件重用:
- 知识重用
- 方法和标准重用
- 软件成分重用
- 代码重用:复制粘贴、包含、继承
- 设计结果重用:设计模型的重用
- 分析结果重用:需求模型的重用
类构件的重用方式:
- 实例重用:多个对象用同一个实例;一个复杂的类由多个简单的类创建
- 继承重用:利用继承性对类构件进行剪裁
- 多态重用:利用多态性使对外接口更具一般性
12.3 测试策略
面向对象软件不存在层次的控制结构,传统的自顶向下和自底向上集成策略没有意义。
集成测试方法:
- 基于线程的测试:把相应系统的一个输入或事件需要的类集成分别进行测试和回归测试
- 基于使用的测试:把独立类测试完成后再测试依赖类直至测试完成
软工设计图
其实我软工学得也一般,历年试题做得也总是和标答不太一样,以下总结仅供参考。
数据流图☆
https://www.bilibili.com/video/BV1Jt411d7nR?p=3
父子图平衡、子图内平衡
- 顶层图:整个软件系统看作一个大的加工,确定软件的数据源点和终点、源点和终点与软件系统之间的输入/输出数据流
- 零层图、一层图
NS图(盒图)
PAD图
判定表
判定表化简
判定树
用例图☆
用例:椭圆,描述从行为者看到的系统全部完整的功能(存款而非插卡输密码等)
- 行为者希望系统提供什么功能
- 行为者必须的增删改查的功能
- 系统给行为者的通知
- 系统从行为者得到的信息
系统边界:黑色方框,用于划定系统的功能范围
行为者:小人,位于系统外和系统进行交互的人和系统;
用例:功能
- 包含、使用:A使用B的功能,描述一种必然性
- 销户/下单—-《include》—->结算/搜索商品
- 每次都必须做。先做什么才能做什么…
- 扩展:A扩展出了B的功能,B用到了A的功能,描述一种可能性
- 注册/短信提醒—-《extend》—->登录/停机提醒
- 额外可能做的功能,通常是扩展(登录发现没注册)。
- 新功能用老功能,通常是扩展(停机提醒用短信提醒)
- 只能额外做操作,不能修改
- 继承、泛化:A继承基类B
- 空心三角箭头:管理员 —》 用户
- 修改了原有的某些功能。做了不一样的东西
白盒测试(结构测试、逻辑驱动测试)
语句覆盖(点覆盖):测试数据需要覆盖所有语句
判定覆盖(边覆盖):测试数据满足所有的判定结果(即每个判定的TF结果都要走过)
条件覆盖:将判定中的判定子句的所有TF都走过(只看条件,可能大判定TF没都走)
判定/条件覆盖:满足判定+条件覆盖
多重条件覆盖(条件组合):将所有判定的子句TF的组合都走过,但不同判定之间无关联
路径覆盖:将每条可能的路径至少执行一次,如有循环至少进入一次
条件组合覆盖 覆盖 判定/条件覆盖
判定/条件覆盖 覆盖 判定覆盖和组合覆盖
黑盒测试(功能测试、数据驱动测试)
等价类:有效等价类、无效等价类
设计测试用例:
- 有效的:尽可能多的覆盖未覆盖的有效等价类,直至覆盖全部
- 无效的:只覆盖一个,直至覆盖全部
设计等价类表:
设计有效类测试用例:
设计无效类测试用例:
边界值分析
类图☆
类图属于对象模型,描述类及类与类之间的静态关系
public +
- private –
- protected #
重数:0..1,0..*,1..*,1..15,3
一元关联:学生管理学生
导航:箭头,表示使用到对象
关联类:
限定关联:
聚集关系:菱形表示
组合:(公司和部门)强依赖删除父类,则所有子类失去意义
聚合:(部门和员工)弱依赖,由什么组成
组合(强) 聚合(弱)
泛化:
依赖:
顺序图(事件跟踪图)
顺序图描述对象之间动态的交互关系
状态图☆
借书(n)[n<10]/n=n+1
组合状态
顺序图改状态图:
- 射入线:状态图中的事件。
- 射出线:状态图中的状态的行为。
- 两个事件之间的间隔就是一个状态。