软件工程
目录
软件:
- 概念:是计算机系统中与硬件相互依存的另一部分,包括程序、数据及其相关文档的完整集合
- 数据:是使程序能够适当处理信息的数据结构
- 程序:是能够完成预定功能和性能的可执行指令序列
- 文档:是开发、使用和维护过程中程序所需要的图文资料 ## 软件危机:
- 概念:在计算机软件开发和维护过程中所遇到的一系列严重问题,主要包括开发和维护。
- 表现:
- 对软件开发成本和进度估算不准确。
- 软件质量不可靠。
- 软件不可维护。
- 没有适当的文档资料。
- 软件成本在计算机系统中所占比例逐年上升。
- 软件开发生产率低。
- 原因:
- 忽视需求分析
- 轻视软件维护
- 没有认识到程序只是软件的一部分(很多人的共性问题)
- 没有认识到软件开发只是软件漫长生命周期中一个比较次要的阶段
- 越到后期如果引入变动则代价越高
- 软件是逻辑实体,具有不可见性,所以管理和控制较为困难
- 软件不会磨损,维护意味着需要修改原来的设计,维护困难
- 软件规模庞大,程序复杂性随规模增加而增加
- 解决方法:
- 对计算机软件应该有正确的认识
- 要吸取和借鉴人类长期从事各种工程项目积累的原理、概念、技术和方法
- 积极开发和使用计算机辅助开发软件
- 探索更好更有效的管理措施和手段对开发过程进行控制和管理 ## 软件工程:
- 概念: 采用工程的概念、原理、技术和方法来开发与维护软件,把经过时间考验而证明正确的管理技术和当前能够得到的最好的技术方法结合起来,经济的开发出高质量的软件并维护它
- 原则:
- 按软件生存期分阶段制定计划并认真实施
- 坚持进行阶段评审
- 坚持严格的产品控制
- 使用现代程序设计技术
- 结果能够得到清楚的审查
- 用人少而精
- 承认不断改进软件工程实践的必要性 ### 软件工程方法学: 把在软件生命周期全过程中使用的一整套技术方法的集合称之为方法学,也称为泛型。软件工程方法学包含三个要素:方法、工具、过程
- 方法:完成软件开发各项任务的技术方法,回答"怎么做"的问题
- 工具:为运用方法提供的自动或半自动软件工程支撑环境
- 过程:是为了获得高质量软件所需要完成的一系列任务框架,回答"何时做"的问题 ### 传统方法学:
- 采用结构化技术完成软件开发各项任务
- 把软件生命周期的全过程依次划分为若干阶段
- 每个阶段开始和结束都有严格标准
- 每个阶段结束后要有严格审查 ### 面向对象方法学:
- 把对象作为融合了数据及在数据上的操作行为的统一的软件构件
- 把所有对象划分为类
- 按照父类与子类的关系,把若干类组成层次结构的系统
- 对象彼此间仅能通过发送消息互相联系 ## 软件生命周期: ### 定义:
- 角色:系统分析员
- 问题定义:弄清用户要解决什么问题,需要得到用户确认。关于问题性质、工程目标和工程规模的书面报告。
- 可行性研究:系统分析和设计过程。研究问题的范围,探索这个问题是否值得去解,是否有可行的解决办法。
- 需求分析:为了解决这个问题,系统需要具备怎样的功能。软件需求规格说明书(SRS)。 ### 开发:
- 总体设计:设计软件结构,确定程序由哪些模块组成以及模块间的关系。设计程序的体系结构,可以应用设计模式。
- 详细设计:针对每个模块,设计详细规格说明,确定算法和数据结构,可以应用设计模式。
- 编码:将详细设计内容用语言实现。
- 单元测试:测试每个单一模块。
- 综合测试:通过各种类型测试使软件达到预定要求。 ### 维护:
- 改正性维护:即诊断和改正在使用过程中发现的软件错误;
- 适应性维护:即修改软件以适应环境的变化;
- 完善性维护:即根据用户的要求改进或扩充软件使它更完善;
- 预防性维护:即修改软件,为将来的维护活动预先做准备。 ## 软件生命周期模型: 软件过程:是为了获得高质量软件所需要完成的一系列任务框架,它规定了完成任务的工作步骤。通常用软件生命周期模型来描述软件过程。
| 模型 | 核心特点 | 适用场景 |
|---|---|---|
| 瀑布模型 | 线性串行,阶段依次执行,变更成本高 | 需求固定、合规要求高的军工传统项目 |
| V 模型 | 开发与测试阶段一一对应,测试提前规划 | 嵌入式、安全关键类软件 |
| 增量模型 | 先定整体架构,分模块分批交付 | 大型复杂系统 |
| 迭代模型 | 多轮迭代循环,逐步完善产品,接纳需求变化 | 需求不明确,需要持续打磨的产品 |
| 螺旋模型 | 风险驱动,循环迭代结合原型 | 大型高风险、需求不确定项目 |
| 快速原型模型 | 搭建原型确认需求,抛弃原型后正式开发 | 需求模糊、交互界面复杂的中小型项目 |
| Scrum 敏捷 | Sprint 短迭代,持续交付,快速响应需求变更 | 互联网产品,需求频繁变化项目 |
瀑布模型(大型复杂)
- 阶段串行,上一阶段完成才进入下一阶段,需求前期确定,变更代价大。
| |
V模型(瀑布扩展)
- 开发阶段左侧,对应右侧测试,强调测试尽早规划,多用于安全关键系统。
| |
增量模型
- 整体架构先定,分多个增量包逐个开发,每完成一个增量就交付一部分功能给客户
| |
迭代模型
- 完整做一轮完整流程(需求‑设计‑编码‑测试),多轮迭代,每轮迭代完善全部功能,不是只做一部分模块
| |
螺旋模型
| |
快速原型模型
- 需求模糊、用户说不清完整需求的场景,快速搭建一个可运行的简易原型,给用户体验、收集反馈,反复迭代打磨需求;需求确认后,再开发正式产品。
| |
敏捷开发(主流)
- 概念:是一套软件开发价值观,来源于《敏捷宣言》,核心:拥抱变化、小步交付、重视人、客户持续反馈,可工作的软件 > 完备的文档,个体和交互 > 过程和工具
- 工具:白板、在线文档、PingCode、Jira
- 基于迭代增量模型,除了scrum还有看板、SAFe等模型 #### Scrum
- 主要负责项目管理
- **Sprint(时间盒迭代)**为核心,迭代周期一般 2‑4 周,一个 Sprint 产出可工作、可评审的软件版本。
- 设施:Product Backlog(全部需求池)、Sprint Backlog(当前Sprint需求池)
- 角色:产品负责人 PO(排需求优先级)、Scrum Master(保障 Scrum 流程正常运行,协助坚持Scrum 原则)、开发团队(开发、测试)
- 事件:Sprint 规划会(生成 Sprint 待办)、站会(昨天做了什么、今天做什么、遇到什么阻碍)、Sprint 执行(团队开发测试)、Sprint 评审会(向PO、客户演示软件效果收集反馈)、Sprint 回顾会(复盘总结本次迭代,准备改进措施)
| |
- 图表:看板、燃尽图
- 看板:任务(需求)状态,包括责任人,时间,进度【待办、进行中、已完成】
- 燃尽图:折线图,Sprint 迭代周期内,剩余工作量随时间下降的趋势 #### 极限编程XP
- 主要负责代码开发质量
- 主要是规定一系列在实际开发和测试中的原则
- TDD 测试驱动开发:先写测试用例,再写业务代码,测试先行。
- 结对编程:两人一台机器,一人写代码,一人实时评审,角色轮换。
- 重构:不改变外部行为,优化内部代码结构
- 简单设计:只实现当前需要的功能,拒绝过度设计。
- 小版本频繁发布
- 现场客户:客户深度参与,随时澄清需求。 #### DevOps
- 保证可靠快速的构建、测试、部署、上线运维
- CI 持续集成:自动编译,自动测试,频繁合并代码到主干
- CD 持续交付:自动把软件构建成可执行文件
- CD 持续部署:完全自动部署到生产环境,无需人工审批
- 容器:把应用和环境打包成镜像解决环境不一致问题
- 容器管理:批量管理容器,扩容缩容、滚动更新、故障自愈
- 配置设施:批量配置服务器,用代码管理云服务器
- 制品仓库:存储Docker镜像、产品软件包
- DevOps一般流程包括:代码管理(git)-CI(GitHub Actions)-代码安全(GitHub Actions、SonarQube)-容器(Docker、Kubernetes(K8s))-配置设施(Ansible)-制品仓库(Harbor)-CD(GitHub Actions、ArgoCD)-监控告警(Prometheus + Grafana、Elasticsearch+Logstash+Kibana) # 可行性研究:
- 目的:用最小的代价在最小的时间内确定问题是否可以被解决。
- 任务:
- 分析和澄清问题的定义
- 导出系统的逻辑模型
- 根据逻辑模型探索若干种可供选择的解法
- 研究每种解法的可行性
- 经济可行性:经济效益是否大于开发成本
- 技术可行性:现有技术能够实现
- 操作可行性:系统操作方式是否可行
- 其它可行性:法律、社会效益
- 过程:
- 复查系统规模和目标
- 研究目前正在使用的系统
- 导出新系统的高层逻辑模型
- 进一步定义问题
- 导出和评价供选择的解法(经济、技术、操作、其他可行性)
- 推荐行动方针
- 草拟开发计划
- 书写文档提交审查 ### 数据流图DFD
- 概念:结构化分析工具,只表达数据流动,不表达控制逻辑(分支、循环)
- 可以分层绘制,0 层顶层图描述整体,子图逐层细化加工
- 数据字典描述具体的数据结构,现在一般用数据库表设计文档替代
- 基础构件:

- 实例:
银行计算机储蓄系统的工作过程大致如下:储户填写的存款单或取款单由业务员键入系统,如果是存款则系统记录存款人姓名、住址(或电话号码)、身份证号码、存款类型、存款日期、到期日期、利率及密码(可选)等信息,并印出存单给储户;如果是取款而且存款时留有密码,则系统首先核对储户密码,若密码正确或存款时未留密码,则系统计算利息并印出利息清单给储户
# 需求分析: - 原则:
- 必须理解并描述问题的信息域,根据这条准则应该建立数据模型(E-R图)
- 必须定义软件应完成的功能,这条准则要求建立功能模型(数据流图)
- 必须描述作为外部事件结果的软件行为,这条准则要求建立行为模型(状态转换图)
- 必须对描述信息、功能和行为的模型进行分解,用层次的方式展示细节
- 内容:
- 确定对系统的综合要求:
- 功能要求:系统必须提供的服务功能
- 性能要求:系统必须满足的约束条件(如响应速度、安全性等)
- 可靠性和可用性需求:可靠性定量、可用性量化
- 出错处理需求: 错误响应机制,说明系统对环境错误应该如何响应
- 接口需求:
- 用户接口需求
- 硬件接口需求
- 软件接口需求
- 通信接口需求
- 约束: 用户或环境强加的限制条件(如工具、语言等)
- 逆向需求: 系统不应该做什么
- 将来可能提出要求: 将来可能需要实现的需求
- 分析系统的数据要求:
- 建立数据模型:
- 层次方框图、warnier图
- 导出系统的逻辑模型:
- 数据流图、数据字典、实体联系图、状态转换图
- 修正系统开发计划
## 层次方框图
## Warnier图
- 确定对系统的综合要求:
⊕:异或,二选一(a,b):重复次数a-b
## IPO图
## E-R图- 用于数据库结构设计,描述现实中的实体、实体属性、实体间关系
- 实体:客观存在、可以区分的事物,比如学生、图书。
- 属性:实体的特征,比如学号、书名。
- 联系:实体与实体之间的关联。
- 一对一 1:1:A 一个实例对应 B 一个实例,例如:学生‑身份证
- 一对多 1:N:A 一个对应 B 多个;B 一个只对应 A 一个。例如:班级‑学生
- 多对多 M:N:两边都可以对应多条。例如:学生‑课程(一个学生选多门课,一门课多个学生选)
- 基础构件:

- 实例:
一个学生可选修多门课,一门课有若干学生选修;一个教师可讲授多门课,一门课只有一个教师讲授;学生选修一门课,产生成绩;学生的属性有学号、姓名等;教师的属性有教师编号,教师姓名等;课程的属性有课程号、课程名等。请画出该系统E-R图
## 状态转换图
见UML状态图:
## 需求验证 - 一致性:所有需求必须是一致的,任何一条需求不能和其他需求互相矛盾
- 完整性:需求必须是完整的,规格说明书应该包括用户需要的每一个功能或性能
- 现实性:指定的需求应该能用现有的硬件和软件技术可以实现
- 有效性:必须证明需求是正确有效的,确实能解决用户面对的问题
- 验证方法:
- 一致性:软件工具
- 现实性:开发经验
- 完整性、有效性:软件原型
- PSL/PSA系统
- PSL(问题陈述语言):是用来描述系统的形式语言
- PSA(问题陈述分析程序):是处理PSL描述的分析程序
- 用PSL描述的系统属性放在一个数据库中。一旦建立起数据库之后即可增加信息、删除信息或修改信息,并且保持信息的一致性。PSA对数据库进行处理以产生各种报告,测试不一致性或遗漏,并且生成文档资料。 # 概要设计: ## 概要设计
- 系统设计阶段:
- 设想供选择的方案:设想把数据流图中处理分组的各种可能的方法
- 选取合理的方案:低成本、中等成本和高成本的3种方案,系统流程图、物理元素清单、成本/效益分析、进度计划
- 推荐最佳方案
- 结构设计阶段:
- 功能分解
- 设计软件结构(层次图、结构图)
- 设计数据库
- 制定测试计划
- 书写文档(系统说明、用户手册、测试计划、详细实现计划、数据库设计结果)
- 审查和复查 ## 设计原理(高内聚、低耦合):
- 模块:模块是由边界元素限定的相邻程序元素的序列,而且有一个总体标识符代表它。模块是构成程序的基本构件。过程、函数、子程序和宏等,都可作为模块。面向对象方法学中的对象是模块,对象内的方法也是模块。
- 模块化:模块化就是把程序划分成独立命名且可独立访问的模块,每个模块完成一个子功能,把这些模块集成起来构成一个整体,可以完成指定的功能满足用户的需求。模块化是为了使一个复杂的大型程序能被人的智力所管理,是软件应该具备的唯一属性。 抽象:抽出事物的本质特性而暂时不考虑它们的细节。
- 逐步求精:逐步求精是软件工程技术的基础,为了能集中精力解决主要问题而尽量推迟对问题细节的考虑。
- MIller法则:注意力集中在(7 ± 2)个事物上。
- 信息隐藏:指一个模块内包含的信息对于不需要这些信息的模块来说是不能访问的,主要是指模块的实现细节
- 局部化:指把一些关系密切的软件元素物理地放得彼此接近,有助于实现信息隐藏
- 模块独立:开发具有独立功能而且和其他模块之间没有过多的相互作用的模块,就可以做到模块独立。使得每个模块完成一个相对独立的特定子功能,并且和其他模块之间的关系很简单。模块独立的概念是模块化、抽象、信息隐藏和局部化概念的直接结果。其质量标准是耦合和内聚
- 耦合:是对一个软件结构内不同模块间互连程序的度量。耦合强度取决于模块接口的复杂程度、通过接口的数据等。耦合度越高,模块独立性越弱,以下耦合从低到高
- 完全独立:如果两个模块中的每一个都能独立地工作而不需要另一个模块的存在
- 数据耦合:如果两个模块彼此间通过参数交换信息,而且交换的信息仅仅是数据
- 特征耦合:如果整个数据结构作为参数传递而被调用的模块只需要使用其中一部分数据元素
- 控制耦合:如果两个模块彼此间通过参数交换信息,并且传递的信息中包含控制信息(这种控制信息可以以数据的形式出现)
- 外部耦合:一组模块都访问同一全局简单变量,而且不通过参数表传递该全局变量的信息
- 公共耦合:两个或多个模块通过一个公共数据环境相互作用
- 内容耦合:一个模块直接访问另一模块的内容
- 内聚:是用来度量一个模块内部各个元素彼此结合的紧密程度。内聚度越高,紧密程度越高,以下内聚从低到高
- 偶然内聚:即使有关系,关系也是很松散的
- 逻辑内聚:一个模块完成的任务在逻辑上属于相同或相似的一类
- 时间内聚:一个模块包含的任务必须在同一段时间内执行
- 过程内聚:一个模块内的处理元素是相关的
- 通信内聚:模块中所有元素都使用同一个输入数据和(或)产生同一个输出数据
- 顺序内聚:一个模块内的处理元素和同一个功能密切相关
- 功能内聚:模块内所有处理元素属于一个整体,完成一个单一的功能 ## 启发规则
- 改进软件结构提高模块独立性
- 模块规模应该适中
- 深度、宽度、扇入和扇出应适当
- 模块的作用域应该在控制域之内
- 力争降低模块接口的复杂程度
- 设计单入口单出口的模块
- 模块功能应该可以预测但要防止过分局限
- 深度:表示软件结构中控制的层数,能粗略地标志一个系统的大小和复杂程度
- 宽度:宽度是软件结构内同一个层次上的模块总数的最大值。
- 扇出:是一个模块直接控制的模块数目
- 扇入:表明有多少个上级模块直接调用它。
## 层次图
## 结构图 - 尾部是空心圆表示传递的是数据;若是实心圆则表示传递的是控制信息。

- 分支:真A假B

- 循环:
# 详细设计:
## 程序流程图 - 构件:
- a:选择
- b:注释
- c:预先定义的处理
- d:多分支
- e:开始或停止
- f:准备
- g:循环上界限
- h:循环下界限
- i:虚线
- j:省略符
- k:并行方式
- l:处理
- m:输入输出
- n:连接
- o:换页连接
- p:控制流

- 实例:
## 盒图(N-S图) - 构件
- a:顺序结构
- b:IF_TEEN_ELSE型分支
- c:CASE型多分支
- d:循环结构
- e:调用子程序A
## PAD图
- 构件
- a:顺序
- b:选择(IF C THEN P1 ELSE P2)
- c:CASE型多分支
- d:WHILE型循环(WHILE C DO P)
- e:UNTIL型循环(REPEAT P UNTIL C)
- f:语句符号
- g:定义
## 过程设计语言(PDL)
- 伪代码 ## 程序复杂度
- 自动计算工具:**SonarQube **同时输出圈复杂度 + 认知复杂度
- 本地编辑器插件:SonarQube for IDE提示复杂度超标 ### McCabe圈复杂度
- 控制流图中的区域数:封闭区域数量,包括最外层
- 控制流图G的环形复杂度V(G)=E-N+2,E是流图中边的条数,N是结点数。
- 控制流图的环形复杂度V(G)=P+1,其中,P是流图中判定结点的数目。
- V(G)小于等于10比较好 ### 认知复杂度
- 顺序并列的 if,加分少
- 嵌套越深扣分越多
- 提前 return不额外扣分
- 逻辑与
&&连续条件视为一个整体,不重复加分 ### Halstead方法 - 根据程序中运算符和操作数的总数来度量程序的复杂程度。
### 辅助度量 - 代码行数:单方法不要超过50-80行
- 重复代码率:大段复制粘贴会提升维护成本 # 测试: ## 准则:
- 所有测试都应该能追溯到用户需求
- 应该远在测试之前就制定测试计划
- Pareto原理:80%的错误是由20%的模块造成的
- 应该从小规模测试开始,并逐步进行大规模测试
- 穷举测试是不可能的,测试只能证明程序有错误,而不能证明程序没有错误
- 为了尽最大可能的发现错误,应该由独立的第三方担任测试工作
- 平行运行:平行运行就是同时运行新开发出来的系统和将被它取代的旧系统,以便比较新旧两个系统的处理结果。 ## 整体类型 | 测试阶段 | 被测对象 | 主要做什么 | | ——– | ———– | ——————————- | | 单元测试 | 函数、类、组件 | 最小单元,隔离外部依赖;白盒为主;开发者自己做 | | 集成测试 | 模块 / 组件之间接口 | 把单元拼起来,测模块间交互;发现接口调用问题;可白盒 + 黑盒 | | 系统测试 | 完整整个软件系统 | 完整产品,包含软硬件;完全黑盒;功能、性能、兼容性等 | | 验收测试 | 完整系统,面向用户角度 | 确认是否满足需求;分为 α 测试、β 测试 |
- α 测试:开发环境内部,用户到开发场地测试(内部模拟用户)。
- β 测试:软件发给外部真实用户,在用户自己环境下试用,收集反馈。
- 与v模型的对应关系:需求规格 → 验收测试;概要设计 → 系统测试;详细设计 → 集成测试;编码 → 单元测试
- 特殊重要测试:
- 回归测试:改完代码之后,把旧用例重新跑一遍,防止修改引入旧功能 bug
- 接口测试:测 http/rpc 接口,不操作前端页面
- 非渐增测试: 先分别测试每个模块,再把所有模块按设计要求放在一起结合成所要的程序
- 渐增测试: 把下一个要测试的模块同已经测试好的那些模块结合起来进行测试,测试完以后再把下一个应该测试的模块结合进来测试,每次增加一个模块。同时完成单元和集成测试。
- 自顶向下集成:深度优先、宽度优先
- 自底向上集成
- 现代测试框架同一对象可同时完成 stub 打桩与 mock 校验。 ### 单元测试
- 模块内部逻辑功能测试:
- 模块结构:
- 模块接口的数据流是否能正常进出
- 参数的数目、次序、属性或单位系统与变元是否一致
- 是否修改了只作输入用的变元
- 全局变量的定义和用法在各个模块中是否一致
- 局部数据结构:对于模块来说,局部数据结构是常见的错误来源。应该仔细设计测试方案,以便发现局部数据说明、初始化、默认值等方面的错误。
- 模块结构:
- 局部异常、分支、边界测试:
- 重要的执行通路:
- 选择最有代表性、最可能发现错误的执行通路进行测试
- 设计测试方案来发现由于错误计算、不正确的比较或不适当的控制流而造成的错误
- 出错处理通路:
- 对错误的描述是难以理解的
- 记下的错误与实际遇到的错误不同
- 在对错误进行处理之前,错误条件已经引起系统干预
- 对错误的处理不正确
- 描述错误的信息不足以帮助确定造成错误的位置
- 边界条件:边界测试是单元测试中最重要的任务。软件常常在它的边界上失效,例如,处理n元数组的第n个元素时,或做到1次循环中的第1次重复时,往往会发生错误。使用刚好小于、刚好等于和刚好大于最大值或最小值的数据结构、控制量和数据值的测试方案,非常可能发现软件中的错误。
- 重要的执行通路:
- 手段:插桩测试,见测试替身 ### 集成测试
- 接口测试(微服务高频)
- 模块间调用、数据传递测试
- 接口异常、参数错误测试
- 接口时序、消息交互测试
- 模块集成后的异常处理 ### 系统测试
- 功能测试:业务功能是否符合需求
- 性能测试:负载测试、压力测试、并发测试
- 兼容性测试:操作系统、浏览器、设备版本
- 安全测试:越权、注入、XSS、数据泄露
- 可靠性测试:长时间运行、故障恢复、容错
- 易用性测试:UI 交互体验
- 回归测试:修改版本后全套回归 ### 验收测试
- 业务功能确认测试(对照需求规格说明书)
- 用户场景测试,模拟真实业务使用流程
- 配置、文档、操作手册审查
- α 测试
- β 测试 ## 实现方式 ### 白盒测试
- 语句覆盖:让程序每一条可执行语句至少执行 1 次。
- 判定覆盖(分支覆盖):每个判定的取真、取假结果至少各执行 1 次。
- 条件覆盖:判定内部每一个子条件,取真、取假至少各执行 1 次。
- 判定‑条件覆盖:同时满足判定覆盖与条件覆盖。
- 条件组合覆盖:判定内所有子条件的真假组合全部至少执行 1 次。
- 路径覆盖:程序所有独立执行路径至少全部执行 1 次。
- 基本路径测试:依据 McCabe 圈复杂度,导出独立路径,设计最少用例完成覆盖。
- 静态代码审查:不运行程序,人工或工具检查代码逻辑、缺陷、复杂度。 ### 黑盒测试
- 等价类划分:把输入域划分为有效、无效等价类,每类选取代表设计用例。
- 边界值分析:选取输入边界点、临近边界点设计测试用例。
- 错误推测法:凭借经验推测容易出错场景,针对性设计用例。
- 因果图法:分析输入条件组合与输出之间因果关系,生成测试用例。
- 场景法:模拟用户完整业务流程场景设计测试用例。 ### 测试替身
- Driver(驱动): 模拟被测模块的上层,调用被测模块
- Stub(存根 / 桩):替换被测模块调用的下层模块,只返回预设模拟数据,不校验调用行为。
- Mock(模拟对象):返回模拟数据,同时校验调用次数、入参、调用顺序,做行为断言。
- Spy(间谍):包装真实对象,原有逻辑保留,同时记录调用信息,可局部打桩。
- Fake(伪对象):简易可用实现,拥有真实业务逻辑,适合测试,不用于生产。
- Dummy(哑对象):占位参数,不会被实际使用,仅用来补齐函数参数。 ### 测试框架
- GoogleTest (GTest+GMock):工业界主流,提供测试用例、断言,GMock 实现对象 mock,CMake 友好。
- Catch2:轻量级头文件库,语法简洁,支持 BDD 风格,适合中小型项目。
- doctest:极简头文件测试库,编译速度快,API 风格接近 Catch2。
- Boost.Test:Boost 库自带测试组件,多用于 Boost 生态项目,配置较繁琐。
- CppUnit:老旧 JUnit 移植框架,新项目基本不再使用。 ## 调试:
- 断点调试:程序运行过程中暂停执行,查看变量、调用栈,逐行单步执行定位问题。
- 输出打印调试:在关键位置打印日志、变量值,观察运行时状态,简单直接。
- 日志文件调试:程序运行输出持久化日志文件,分析现场运行信息,适合复现偶现 bug。
- 二分调试(少):注释 / 屏蔽部分代码,不断缩小范围,定位引发 bug 的代码片段。
- 条件断点调试:设置触发条件,仅条件满足时程序才暂停,过滤无关执行流程。
- 核心转储 (core dump):程序崩溃时保存内存快照,事后离线分析崩溃现场,C/C++ 常用。
- 远程调试:对部署在远端设备、服务器上的程序,远程附加调试器排查问题。
- 差分调试(少):对比正常版本与异常版本,找出变更点定位引入 bug 的提交。
- 静态分析调试:不运行程序,借助工具 (clang‑tidy、cppcheck) 扫描代码潜在缺陷。
- 内存调试工具:检测内存泄漏、越界访问,C++ 常用 valgrind、asan。 # 维护:
- 改正性维护:诊断和改正错误的过程(17%~21%)
- 适应性维护:为了和变化了的环境适当地配合而进行的修改软件的活动(18%~25%)
- 完善性维护:为了满足用户提出的增加新功能或修改已有功能的要求和一般性改进要求(50%~66%)
- 预防性维护:当为了改进未来的可维护性或可靠性,或为了给未来的改进奠定更好的基础而修改软件(4%)
- 决定软件可维护性的因素:
- 可理解性:软件可理解性表现为外来读者理解软件的结构、功能、接口和内部处理过程的难易程度
- 可测试性:诊断和测试的容易程度取决于软件容易理解的程度
- 可修改性:耦合、内聚、信息隐藏、局部化、控制域与作用域的关系等,都影响软件的可修改性
- 可移植性:软件可移植性是指把程序从一种计算环境(硬件配置和操作系统)转移到另一种计算环境的- 难易程度
- 可重用性:重用是指同一事物不做修改或稍加改动就在不同环境中多次重复使用 # 面向对象(重要):
基础:
- 对象(Object):

- 类:“类"是对具有相同数据和相同操作的一组相似对象的定义,即类是对具有相同属性和行为的一个或多个对象的描述,包括对怎样创建该类的新对象的说明。类是支持继承的抽象数据类型,而对象就是类的实例
- 实例:实例就是由某个特定的类所描述的一个具体的对象。
- 消息:消息就是要求某个对象执行在定义它的那个类中所定义的某个操作的规格说明。
- 方法:方法就是对象所能执行的操作,也就是类中所定义的服务。
- 属性:属性就是类中所定义的数据。
- 封装:封装是把数据和实现操作的代码集中起来放在对象内部。
- 继承:继承是指能够直接获得已有的性质和特征,而不必重复定义它们。
- 多态:不同层次中的每个类各自按自己的需要来实现这个行为。
- 重载:在同一作用域,若干个参数特征不同的函数可以使用相同的函数名。运算符重载:同一个运算符可以施加于不同类型的操作数上面 ### 方法学:
- 面向对象方法学的出发点和基本原则,是尽可能模拟人类习惯的思维方式,使开发软件的方法与过程尽可能接近人类认识世界解决问题的方法与过程,使描述问题的问题空间(问题域)与实现解法的解空间(求解域)在结构上尽可能一致。
- 要点:
- 对象:面向对象的软件系统是由对象组成的,软件中的任何元素都是对象,复杂的软件对象由比较简单的对象组合而成。用对象分解取代了传统方法的功能分解。
- 类:把所有对象都划分成各种对象类,每个对象类都定义了一组数据和一组方法。
- 继承:按子类与父类的关系,把若干个对象类组成一个层次结构的系统。子类自动具有和上层的父类相同的数据和方法。
- 封装:对象彼此之间仅能通过传递消息互相联系。对象是进行处理的主体,必须发消息请求它执行它的某个操作,处理它的私有数据,而不能从外界直接对它的私有数据进行操作。
- 优点:
- 与人类习惯的思维方法一致
- 稳定性好
- 可重用性
- 较易开发大型软件产品
- 可维护性好 #### 面向对象分析
- 抽取和整理用户需求并建立问题域精确模型的过程,包括5个层次:主题、类、结构、属性和服务,建立三个模型对象模型、动态模型、功能模型
- 描述用户的需求而不是提出解决问题的方法
- 哪些是系统必要的性质,哪些是任选的性质
- 不描述系统的内部结构
- 描述系统性能及系统与外界环境交互协议
- 描述采用的软件工程标准、模块构造准则、将来的扩充以及可维护性要求等方面 ##### 对象模型-UML类图
- 表示静态的、结构化的系统的数据性质。它是对模拟客观世界实体的对象以及对象彼此间的关系的映射,描述了系统的静态结构。对象模型为建立动态模型和功能模型,提供了实质性的框架。
- 软件开发过程就是一个多次反复修改、逐步完善的过程。仅仅经过一次建模过程很难得到完全正确的对象模型。
- 划分主题:
- 按问题领域而不是用功能分解方法来确定主题
- 按照使不同主题内的对象相互间依赖和交互最少的原则来确定主题
- 建模步骤:
- 确定对象类和关联(对于大型复杂问题还要进一步划分出若干个主题);
- 给类和关联增添属性,以进一步描述它们;
- 使用适当的继承关系进一步合并和组织类
- 关联类间关系
- 常见对象:
- 可感知的物理实体;
- 人或组织的角色;
- 应该记忆的事件;
- 两个或多个对象的相互作用;
- 类的方法:
- 设计实现服务的算法(算法复杂度、容易理解与容易实现、易修改)
- 选择数据结构
- 算法与数据结构的关系
- 定义内部类和内部操作
- 类的关联:
- 单向关联:简单指针属性实现。
- 双向关联:
##### 动态模型-UML状态图/时序图
- 动态模型表示瞬时的、行为化的系统的控制性质,它规定了对象模型中的对象的合法变化序列。
- 建模步骤:
- 抽象现实中的交互行为,用UML时序图编写典型交互行为的脚本
- 从脚本中提取出事件,确定触发每个事件的动作对象以及接受事件的目标对象
- 排列事件发生的次序,确定每个对象的状态及状态间的转换关系,用状态图描绘
- 比较各个对象的状态图,确保事件之间的匹配 ##### 功能模型-UML用例图/DFD图
- 功能模型表示变化的系统的功能性质,它指明了系统应该做什么,因此更直接地反映了用户对目标系统的需求。 ##### 三种模型的关联
- 针对每个类建立的动态模型,描述了类实例的生命周期或运行周期
- 状态转换驱使行为发生,这些行为在数据流图中被映射成处理,在用例图中被映射成用例,它们同- 时与类图中的服务相对应
- 功能模型中的处理对应于对象模型中的类所提供的服务
- 数据流图中的数据存储,以及数据的源点/终点,通常是对象模型中的对象
- 数据流图中的数据流,往往是对象模型中对象的属性值,也可能是整个对象
- 用例图中的行为者,可能是对象模型中的对象
- 功能模型中的处理可能产生动态模型中的事件
- 对象模型描述了数据流图中的数据流、数据存储以及数据源点/终点的结构 #### 设计模式
- 原则:模块化、抽象、封装、低耦合、高内聚、可重用
- 实现:参见设计模式
- 启发规则:
- 设计结果应该清晰易懂
- 一般一特殊结构的深度适当
- 设计简单的类
- 使用简单的协议
- 使用简单的服务
- 把设计变动减至最小
- 软件重用:
- 代码重用
- 实例重用
- 继承重用
- 多态重用
- 设计结果重用
- 分析结果重用
- 代码重用
- 重用成本:

- 领域分析与建模的成本
- 设计领域体系结构的成本
- 为方便重用而增加的文档的成本
- 维护和完善可重用的软件成分的成本
- 为从外部获取构件所付出的版税和许可证费用
- 创建及运行重用库的费用
- 对设计和实现可重用构件的人员的培训费用
分解面向对象设计模型:

- 子系统间关系:
- 客户-供应商关系
- 平等伙伴关系
- 子系统方案:
- 层次组织
- 块状组织
- 层次和块状的结合
- 拓扑结构(管道形、树形、星形) ##### 人机交互子系统:
- 分类用户
- 描述用户
- 设计命令层次
- 设计人机交互类 ##### 问题域子系统:
- 调整需求
- 重用已有的类
- 把问题域类组合在一起
- 增添一般化类以建立协议
- 调整继承层次 ##### 任务管理子系统(重要):
- 分析并发性
- 确定事件驱动型任务
- 确定时钟驱动型任务
- 确定优先任务
- 确定关键任务
- 确定协调任务
- 尽量减少任务数
- 确定系统资源需求 ##### 数据管理子系统(重要):
- 文件管理系统
- 关系数据库管理系统
- 面向对象数据库管理系统
- 设计数据格式
- 文件系统:
- 定义第一范式表:列出每个类的属性表;把属性表规范成第一范式, 从而得到第一范式表的定义
- 为每个第一范式表定义一个文件
- 测量性能和需要的存储容量
- 修改原设计的第一范式,以满足性能和存储需求
- 关系数据库管理系统:
- 定义第三范式表:列出每个类的属性表;把属性表规范成第三范式,从而得出第三范式表的定义
- 为每个第三范式表定义一个数据库表
- 测量性能和需要的存储容量
- 修改先前设计的第三范式,以满足性能和存储需求
- 面向对象数据库管理系统:
- 扩展的关系数据库途径:使用与关系数据库管理系统相同的方法
- 扩展的面向对象程序设计语言途径:不需要规范化属性的步骤
- 设计相应的服务
- 文件系统:
- 被存储的对象需要知道打开哪个文件,怎样把文件定位到正确的记录上,怎样检索出旧值,以及怎样用现有值更新它们
- 定义一个 ObjectServer类,并创建它的实例
- 关系数据库管理系统:
- 被存储的对象,应该知道访问哪些数据库表,怎样访问所需要的行,怎样检索出旧值,以及怎样用现有值更新它们
- 定义一个 ObjectServer类,并声明它的对象
- 面向对象数据库管理系统:
- 扩展的关系数据库途径:使用与关系数据库管理系统相同的方法
- 扩展的面向对象程序设计语言途径:无须增加服务,只需给长期保存的对象加个标记,然后由面向对象数据库管理系统负责存储和恢复这类对象 ### 程序设计风格:
- 可重用性:
- 提高方法的内聚
- 减小方法的规模
- 保持方法的一致性
- 把策略与实现分开
- 全面覆盖
- 尽量不使用全局信息
- 利用继承机制
- 可扩充性:
- 封装实现策略
- 不要用一个方法遍历多条关联链
- 避免使用多分支语句
- 精心确定公有方法
- 健壮性:
- 预防用户的错误操作
- 检查参数的合法性
- 不要预先确定限制条件
- 先测试后优化 ## UML建模语言:
- 用例图:从用户角度描述系统功能。
- 类图:描述系统中类的静态结构。
- 对象图:系统中的多个对象在某一时刻的状态。
- 状态图:是描述状态到状态控制流,常用于动态特性建模
- 活动图:描述了业务实现用例的工作流程
- 时序图:对象之间的动态合作关系,强调对象发送消息的顺序,同时显示对象之间的交互
- 协作图:描述对象之间的协助关系
- 构件图:一种特殊的UML图来描述系统的静态实现视图
- 部署图:定义系统中软硬件的物理体系结构
- 包图:对构成系统的模型元素进行分组整理的图
- 组合结构图:表示类或者构建内部结构的图
- 交互概览图:用活动图来表示多个交互之间的控制关系的图 ### UML用例图
- 阶段:需求分析
- 构件:
- 参与者 (Actor):与系统交互的外部角色,小人图标,可以是人、外部系统。
- 用例 (Use Case):椭圆,代表系统提供的一项功能。
- 系统边界:矩形框,框住全部用例,表示待开发系统范围。
- 关联:实线,参与者和用例之间连线。
- 包含 (
<<include>>):基用例一定会调用被包含用例;箭头指向被包含用例。 - 扩展 (
<<extend>>):条件满足才会扩展原有用例;箭头指向基用例。 - 泛化:空心三角箭头,子参与者 / 子用例继承父。
- 实例:
UML活动图
- 阶段:需求分析/详细设计
- 描述流程、步骤逻辑,侧重业务 / 算法执行流程,类似流程图。
- 构件:
- 初始节点:实心小黑圆,流程起点。
- 活动动作:圆角矩形,代表一个执行步骤、操作。
- 转移箭头:实线箭头,流程走向。
- 判定分支:菱形,if‑else 分支,写监护条件。
- 合并节点:菱形,多个分支汇合到一处。
- 分叉(并发 fork):粗黑竖线,一条流入,多条流出,开启并行执行。
- 汇合(join):粗黑竖线,多条流入,一条流出,等待全部并行分支完成才往下走。
- 终止节点:同心圆,流程结束。
- 实例:
| |
UML组件图
- 阶段:概要设计
- 描述软件的模块 / 组件,组件对外提供接口、需要依赖别的组件,表达模块之间依赖关系。
- 构件:
- 组件:带小方框标记的矩形,代表独立软件模块。
- 提供接口:组件对外暴露的服务(棒棒糖符号)。
- 需要接口:组件需要外部提供的接口(凹槽符号)。
- 依赖:虚线箭头,A 组件依赖 B 组件。
- 实例:
| |
UML类图
- 阶段:概要/详细设计
- Mermaid代码可以直接渲染成UML类图
- 构件:
- 包:

- 类:
- 组成:
- 第一层:类名
- 第二层:属性(成员变量),格式:
可见性 名称 : 类型 - 第三层:方法(成员函数),格式:
可见性 名称(参数:类型) : 返回值类型
- 可见性:
+public 公开-private 私有#protected 保护
- 特性:
- virtual:方法尾部
{virtual}标记,纯虚函数尾部{abstract}标记或斜体,抽象类标注<<abstract>> - static:变量和方法下划线
- const:变量属性名大写,格式
可见性 名称:类型 = 值,方法尾部加{const}标记
- virtual:方法尾部
- 接口:
- 标注
<<interface>> - 没有属性
- 所有方法全部斜体
- 标注
- 组成:
- 关系:
- 常用的是泛化、实现、关联,次之是组合、依赖
- 泛化(继承):is a,空心三角实线,三角指向父类。子类 —-▷ 父类。
- 实现(接口实现):implement of,空心三角虚线,三角指向接口。实现类 - - -▷ 接口。
- 关联:普通实线;对象之间存在联系,可以当作has a,可以标注多重度 (1、0..* 等)。
- 聚合:空心菱形实线,菱形指向整体;has a,整体包含部分,部分可脱离整体独立存在。
- 组合:实心菱形实线,菱形指向整体;contains a,整体包含部分,部分生命周期跟随整体,整体销毁则部分销毁。
- 依赖:虚线普通箭头;use a,一个类临时使用另一个类(函数参数、局部变量)。
- 包:
- 实例:
| |
UML时序图
- 阶段:详细设计
- 按照时间从上到下顺序,描述多个对象之间消息调用的先后顺序。
- 构件:
- 角色:时序图最上方,代表外部系统 / 人,属于参与者
- 对象:矩形框,格式 对象名:类名,放在图最上方;:前面为空代表匿名对象。
- 生命线:对象下方垂直虚线,代表对象存活时间。
- 执行条:生命线上面的细长矩形,表示对象正在执行。
- 同步发送:实心箭头实线,调用方等待对方执行完成。
- 返回消息:虚线箭头,从被调用方返回调用方,携带返回结果。
- 异步发送:空心箭头实线,发送之后不等待,继续往下执行。
- 组合片段:alt分支判断、loop循环、opt可选执行、par并行执行等等,用来表达分支循环逻辑。
- 实例:
| |
UML状态图
阶段:需求分析、详细设计
只针对单个对象,描述一个对象在生命周期内的各种状态、状态之间的转移、触发事件、监护条件、动作。
构件:
初始伪状态:实心小黑圆,对象起点。
终止状态:实心圆外面套一个空心圆环,对象生命周期结束。
状态:圆角矩形,内部写状态名称;可写 entry /do/exit 动作。
复合状态:大圆角矩形,内部嵌套子状态,包含多个内部子状态。
转移:带箭头实线,从源状态指向目标状态,语法
事件 [监护条件] / 动作状态内部:
- entry / 行为:进入该状态的时候执行
- do / 行为:处于该状态期间持续执行
- exit / 行为:离开该状态的时候执行
转移语法:
- 事件:触发状态切换发生的事情
- 监护条件:方括号,布尔判断,满足才允许跳转
- 动作:跳转发生时执行的行为
1 2 3 4 5 6 7 8stateDiagram-v2 [*] --> 待支付 : 创建订单 待支付 --> 已支付 : 用户支付 [金额正确] / 生成支付记录 待支付 --> 已取消 : 超时 / 自动关闭订单 已支付 --> 已发货 : 发货 已发货 --> 已完成 : 用户确认收货 已完成 --> [*] 已取消 --> [*]UML部署图
阶段:部署方案
描述物理硬件节点 + 运行在节点上的软件组件,表达软件部署位置、服务器之间通信链路
构件:
- 节点 Node:立体方框,硬件 / 服务器 / 设备(应用服务器、DB 服务器、手机客户端)。
- 构件 Artifact:运行在节点内部的软件包、程序、组件。
- 关联连线:实线,节点之间网络通信,标注协议 HTTP/TCP。 # 软件项目管理:
估算软件规模:估算实现一个功能所需要的源代码行数、以功能点(FP)为单位度量软件规模
工作量估算:工作量的单位通常是人月(pm)
- 静态单变量模型(基本的COCOMO模型)
- 静态多变量模型(COCOMO2模型)
- 动态多变量模型(putnam模型)
进度计划:甘特图(Cantt图)
人员组织:
- 民主制程序员组
- 主程序员组
- 现代程序员组
质量保证:
- 基于非执行测试(复审或评审)
- 基于执行测试(软件测试)
- 程序正确性的证明(数学方法)
软件配置管理:
- 基线:通过了正式复审的软件配置项,可以作为进一步开发的基础,只有通过正式的变化控制过程才能改变它
- 标识对象
- 版本控制
- 变化控制
- 配置审计
- 状态报告
能力成熟度模型:
- 初始级
- 可重复级
- 已定义级
- 已管理级
- 优化级 # 个人项目应用: ### 大型项目
生命周期模型:瀑布流模型
可行性研究:
- 文档:软件开发任务书
- 工具:PowerDesigner
- 图表:数据流图DFD、数据字典
需求分析:
- 文档:软件需求规格说明书
- 工具:PowerDesigner
- 图表:E-R图、层次方框图、数据流图DFD、状态转换图
概要设计:
- 图表:层次图
详细设计:
- 图表:PAD图
- 复杂度:McCabe方法计算环形复杂度。
测试:
- 单元测试:代码审计
- 集成测试:自顶向下深度优先渐增式集成测试
- 验收测试:Alpha版本和Beta版本
面向对象:
- 分析阶段:UML类图->UML时序图->UML状态图->数据流图DFD->完善UML类图
- 设计阶段:根据设计模式重新设计UML类图 ### 小型项目 生命周期模型:敏捷编程(scrum)