架构师 - 5.软件工程
软件工程:一条主线串起
本文按软件生命周期重新梳理,主线只有 6 步:
认识软件工程 → 选过程模型 → 做需求 → 建模与设计 → 测试 → 交付与维护
另有两个特殊专题,各自挂在主线的对应环节下(净室软件工程 → 过程模型,逆向工程 → 交付与维护)。
先建立全局观:全局主线
认识软件工程 —— 定义 / 三要素 / 生命周期
|
v
选过程模型 —— 瀑布、V、原型、螺旋、增量、CBSE、RAD、RUP、敏捷
| 专题:净室软件工程 属于这一环节
v
做需求 —— 获取 / 分析 / 定义(SRS) / 验证 / 变更管理
|
v
建模与设计 —— UML、4+1 视图、高内聚低耦合
|
v
测试 —— 单元 -> 集成 -> 系统 -> 验收
|
v
交付与维护 —— 系统切换、数据迁移、四类维护
专题:逆向工程 / 再工程 属于这一环节
一句话概括:软件工程就是把"需求"工程化地变成"可靠系统",并在交付后持续演进的一整套方法、工具和过程。
一、软件工程概述
| 要点 | 内容 |
|---|---|
| 定义 | 用工程化 + 系统化的方法开发、运行和维护软件 |
| 目标 | 高质量、可维护、按期交付、成本可控 |
| 三要素 | 方法(开发技术)、工具(支撑环境)、过程(管理与活动集合)— 缺一不可 |
| 生命周期 | 问题定义 → 可行性研究 → 需求分析 → 总体设计 → 详细设计 → 编码与测试 → 运行维护 |
三要素是"方法—工具—过程",三者缺一不可。
类比:盖房子要先有设计图、工具和施工流程,软件工程就是避免"边盖边改"的施工规范。
二、软件过程模型:怎么组织开发活动
2.1 一表看懂所有模型
| 模型 | 核心思想 | 适用场景 | 主要缺点 |
|---|---|---|---|
| 瀑布 | 线性顺序,上一阶段输出是下一阶段输入 | 需求明确且稳定 | 难以应对变更,用户反馈滞后 |
| V 模型 | 开发阶段与测试阶段一一对应 | 需求明确、重质量验证 | 同瀑布,灵活性差 |
| 原型 | 快速构建原型澄清需求 | 需求模糊、交互复杂 | 原型易被误当成成品 |
| 演化模型 | 在原型基础上持续迭代完善 | 需求逐步明确 | 过程难管控 |
| 螺旋 | 风险驱动,每轮含计划/风险分析/实施/评估 | 大型、复杂、高风险 | 成本高,依赖风险专家 |
| 增量 | 分批交付功能模块 | 需早期交付核心功能 | 需良好架构支撑 |
| 迭代 | 反复精化同一范围 | 技术风险高 | 范围不扩大 |
| 构件组装 / CBSE | 复用现有构件快速拼装 | 有成熟构件库与标准接口 | 依赖构件质量 |
| RAD | 短周期 + 组件 + 用户深度参与 | 需求明确、需快速上线 | 不适合大型复杂系统 |
| 统一过程 RUP | 用例驱动、架构为中心、迭代增量 | 中大型、需架构治理 | 过程较重 |
2.2 三组关键区分
① 迭代 vs 增量
- 迭代 = 反复打磨同一块(做精)
- 增量 = 一块块往上加(做多)
- 实际项目两者结合:既降低技术风险,又逐步扩大功能。
② 瀑布 vs V 模型
- 瀑布只定义开发阶段;V 模型额外给每个开发阶段配了"验收尺子":
| 开发(左) | 验证(右) |
|---|---|
| 需求分析 | 验收测试 |
| 概要设计 | 系统测试 |
| 详细设计 | 集成测试 |
| 编码 | 单元测试 |
③ 原型 vs 演化
- 抛弃型原型:为了搞清楚模糊的需求,先用最快的方式搭一个能跑的演示版本给用户看,等需求确认清楚了,就把这个原型整个丢掉,再按正规的工程方法从头开发真正可用的系统。之所以要"抛弃",是因为快速拼出来的原型往往代码糙、架构乱、没考虑性能与安全,不适合直接当成品继续做。适合需求极度模糊、用户自己都说不清要什么的场景。
- 演化型原型:在原型基础上不断加功能、改毛病,原型最终长成交付产品,不会被丢弃。适合需求逐步明确、希望边做边完善的场景。
类比:抛弃型原型像设计师先用硬纸板做个模型确认尺寸和样式,满意后扔掉纸板再用真材实料正式做;演化型原型则是从草模开始,一遍遍打磨,最后那件成品就是它自己。
2.3 螺旋模型的四个象限
制定计划 ---> 风险分析 ---> 实施工程 ---> 客户评估
^ |
+------------- 进入下一轮螺旋 -----------+
每轮都有风险分析 —— 这是螺旋区别于其他迭代模型的独特标志。
2.4 RUP 的四个阶段
初始(定范围与可行性)→ 细化(分析风险、建立基线架构)→ 构建(完成主要功能)→ 移交(交付用户)
三大特征:用例驱动、架构为中心、迭代增量。
类比:拍连续剧 —— 写大纲、搭主场景、集中拍摄、上映。
2.5 模型选型决策树
按场景选型
需求明确、稳定 --> 瀑布 / V 模型
需求不明确、说不清 --> 原型
大型、复杂、高风险 --> 螺旋
有构件库、可复用 --> 构件组装 / CBSE
分批交付、先上核心 --> 增量
需求变化快、规模适中 --> 敏捷
用例驱动、架构为中心 --> RUP
三、敏捷开发
3.1 敏捷开发
| 更重视 | 相对次要 |
|---|---|
| 个体与交互 | 过程与工具 |
| 可工作的软件 | 详尽的文档 |
| 客户协作 | 合同谈判 |
| 响应变化 | 遵循计划 |
需注意:敏捷 ≠ 无文档、无计划,而是"刚好足够"和"及时响应"。
3.2 Scrum 与 XP 的对比
| 维度 | Scrum(重流程) | XP 极限编程(重工程实践) |
|---|---|---|
| 核心 | 迭代 Sprint、时间盒 | 结对编程、测试先行 |
| 角色 | 产品负责人、Scrum Master、开发团队 | — |
| 工件 | 产品待办列表、Sprint 待办列表、燃尽图 | 用户故事 |
| 会议 | Sprint 计划会、每日站会、评审会、回顾会 | — |
| 实践 | — | TDD、重构、持续集成、小版本发布 |
区分方法:Sprint / 燃尽图 / 三个角色是 Scrum 的典型特征;结对编程 / TDD / 持续集成是 XP 的典型特征。
四、两个特殊专题:逆向工程与净室软件工程
4.1 逆向工程与再工程
| 概念 | 含义 |
|---|---|
| 逆向工程 | 分析已有系统,恢复其设计、结构、需求(重"理解") |
| 再工程 | 在理解基础上改造系统(重"改造") |
| 正向工程 | 从需求重新实现 |
| 重构 | 结构优化,不改变外部行为 |
抽象级别(由低到高):实现级 → 结构级 → 功能级 → 领域级
级别越高,抽象程度越高,信息损失越大。
4.2 净室软件工程
- 核心理念:预防缺陷,而非事后调试。
- 三大方法:盒式规约(行为盒 / 状态盒 / 清晰盒)、统计测试、增量开发。
- 特点:不做单元测试后的调试,靠严格规格说明 + 统计抽样保证质量。
类比:无菌车间 —— 用数学证明和统计抽样预防缺陷,而不是等产品出来后修修补补。
五、需求工程:搞清楚"做什么"
5.1 五个活动构成闭环
需求获取 -> 需求分析 -> 需求定义(SRS) -> 需求验证 -> 需求管理/变更控制
^ |
+--------------- 变更反馈 / 缺陷修订 ----------------+
5.2 需求的三层分类
- 业务需求:组织为什么要做(目标)
- 用户需求:用户要完成什么任务
- 系统需求:功能需求 + 非功能需求(质量属性:性能、可用性、安全性、可维护性、可移植性、易用性)
质量属性常藏在"系统要跑得稳"这类模糊表述里,需显式提取。
5.3 需求获取技术
访谈、问卷调查、观察、原型、联合应用开发(JAD)、头脑风暴、文档分析。
注意:用户的口头需求 ≠ 最终需求,必须经过分析与确认。
需求验证(确认 SRS 正确、完整且符合用户期望):需求评审、原型验证、测试用例生成、模型检测、用户确认。
通过标志:用户签字确认;发现缺陷需跟踪到文档修订闭环。
5.4 结构化分析三件套
| 工具 | 作用 |
|---|---|
| 数据流图 DFD | 描述"数据从哪来、经过什么加工、到哪去" |
| 数据字典 DD | 定义数据流/数据存储的构成 |
| 状态转换图 STD | 描述状态变迁 |
| 判定表 / 判定树 | 描述复杂条件组合逻辑 |
DFD 四元素:外部实体、加工、数据存储、数据流。
5.5 SRS 的六大特性
完整性、一致性、可修改性、可追踪性、无二义性、可验证性
类比:签正式合同 —— 把口头约定写成《软件需求规格说明书》,避免日后扯皮。
5.6 需求跟踪与变更
- 需求跟踪矩阵 RTM:双向跟踪
- 前向:需求 → 设计 → 代码 → 测试用例
- 后向:测试用例 / 缺陷 → 需求
- 变更流程:提出变更 → 影响分析 → CCB 审批 → 实施 → 验证 → 更新基线与文档
- CCB(变更控制委员会) 负责重大变更审批。
六、建模与系统设计:解决"怎么做"
6.1 UML 13 种图(UML 2.x)
| 分类 | 图 |
|---|---|
| 结构图(静态) | 类图、对象图、构件图、部署图、包图、组合结构图、制品图 |
| 行为图(动态) | 用例图、活动图、状态图、序列图、通信图、定时图、交互概览图 |
基本构造块:事物(Things)、关系(Relationships)、图(Diagrams)。
6.2 "4+1"视图(Kruchten)
| 视图 | 关注点 | 面向对象 |
|---|---|---|
| 逻辑视图 | 系统功能 | 设计人员 |
| 进程视图 | 并发与同步 | 集成人员 |
| 物理视图 | 部署与硬件映射 | 系统工程师 |
| 开发视图 | 模块划分与编译依赖 | 开发人员 |
| 用例视图(+1) | 场景,驱动其余四个视图 | 最终用户 |
6.3 软件模型的四类
功能模型(DFD)、行为模型(状态机/活动图)、结构模型(类图/E-R 图)、动态模型(序列图/通信图)。
6.4 结构化设计:高内聚、低耦合
内聚七级(由低到高,越高越好)
| 级别 | 含义 | 例子 |
|---|---|---|
| 偶然内聚 | 模块内各部分毫无逻辑关联,只是碰巧放一起 | 一个模块里既有"算工资"又有"打日志",二者毫无关系,只因同一程序员顺手合在一起 |
| 逻辑内聚 | 逻辑上相似但功能不同,靠参数决定执行哪个 | 一个"输入模块"靠 type 参数决定读键盘 / 读文件 / 读网络 |
| 时间内聚 | 因在同一时间段执行而聚合,彼此无功能关联 | 系统启动初始化:连数据库、载配置、开日志——都只为"启动"这一时刻,步骤间互不相关 |
| 过程内聚 | 按固定流程顺序执行,但各步之间无数据传递 | 日终处理:先备份 → 再汇总 → 后关账,三个步骤按顺序走但彼此独立 |
| 通信内聚 | 各部分操作同一份数据或同一 I/O | 一个模块既"读取订单文件"又"校验订单文件",都针对同一个订单文件 |
| 顺序内聚 | 前一步的输出是后一步的输入,环环相扣 | 读用户 → 算折扣 → 生成账单,上一步结果直接喂给下一步 |
| 功能内聚 | 所有成分只完成单一、明确的功能(最高级) | "计算个人所得税"模块只做这一件事,不夹带任何其他逻辑 |
目标:设计时追求功能内聚,坚决避免偶然内聚、逻辑内聚。
耦合七级(由低到高,越低越好)
| 级别 | 含义 | 例子 |
|---|---|---|
| 非直接耦合 | 模块间无直接关联,仅通过上层统一调用(最低,最理想) | 模块 A 和模块 B 都只被主程序调用,彼此"互不认识" |
| 数据耦合 | 只通过简单数据值(基本类型)传递 | computeTax(income, rate),只传数值型参数 |
| 标记耦合 | 传递整个数据结构,但只用其中部分字段 | 传一个 Employee 结构体给打印模块,但只用姓名、工号两个字段 |
| 控制耦合 | 传递控制标志,被调模块据以选择执行分支 | process(data, isBatch),用 isBatch 决定走批量还是单条逻辑 |
| 外部耦合 | 多个模块都访问同一全局简单变量 / 外部设备 | 多个模块直接读写同一个全局配置文件或同一台打印机 |
| 公共耦合 | 多个模块共享同一公共数据区(全局数据结构) | 多模块共享同一个全局用户会话对象,一处修改会影响所有使用者 |
| 内容耦合 | 一个模块直接访问 / 修改另一模块内部数据,或跳转进其体内(最高,最差) | 模块 A 直接改写模块 B 的局部变量,或用 goto 跳到模块 B 中间 |
目标:优先数据耦合,尽量压低到标记 / 控制耦合以下,严禁内容耦合。
模块四要素:输入、输出、处理功能、内部数据。
类比:像函数签名,四要素定义清楚,别人才好调用。
6.5 界面设计要点
原则:一致性、反馈、容错、简洁、可访问性;减少用户记忆负担、提供清晰导航、保持操作可逆、及时反馈。
6.6 面向对象设计
类的三分法 BCE:
- 边界类(Boundary):与外部参与者交互的接口
- 控制类(Control):封装业务逻辑与控制流
- 实体类(Entity):封装持久化信息
SOLID 五原则:
| 原则 | 含义 |
|---|---|
| S 单一职责 | 一个类只负责一项职责 |
| O 开闭原则 | 对扩展开放,对修改关闭 |
| L 里氏替换 | 子类可替换父类 |
| I 接口隔离 | 客户端不依赖不需要的接口 |
| D 依赖倒置 | 依赖抽象,而非具体实现 |
七、软件测试:验证"做得对不对"
7.1 测试分类维度
| 维度 | 类型 |
|---|---|
| 是否运行程序 | 静态测试(评审、走查、静态分析) / 动态测试(运行程序) |
| 是否看内部结构 | 黑盒(看功能规格) / 白盒(看代码结构) |
| 测试阶段 | 单元 → 集成 → 系统 → 验收 |
7.2 白盒覆盖标准(由弱到强)
语句覆盖 -> 判定覆盖 -> 条件覆盖 -> 判定条件覆盖 -> 条件组合覆盖 -> 路径覆盖
弱 --------------------------------------------------------------> 强
7.3 黑盒常用方法
等价类划分、边界值分析、错误推测、因果图、判定表、场景法。
最常用:等价类划分 + 边界值分析。
7.4 四个测试阶段
| 阶段 | 对象 | 依据 | 执行者 |
|---|---|---|---|
| 单元测试 | 最小可测试单元(模块/类) | 详细设计 | 开发人员 |
| 集成测试 | 模块间接口 | 概要设计 | 开发/测试 |
| 系统测试 | 完整系统(含非功能需求) | SRS | 测试团队 |
| 验收测试 | 用户确认可交付 | 用户需求 | 用户 |
7.5 集成测试策略
| 策略 | 做法 | 辅助模块 |
|---|---|---|
| 大爆炸 | 一次性全部集成 | 无 |
| 自顶向下 | 先主控,逐步下层 | 桩模块 |
| 自底向上 | 先底层,逐步上层 | 驱动模块 |
| 三明治 | 上下结合 | 桩 + 驱动 |
| 冒烟测试 | 每日构建的基本功能验证 | — |
自顶向下需要桩,自底向上需要驱动。
7.6 性能相关测试区分
| 类型 | 关注点 |
|---|---|
| 性能测试 | 响应时间、吞吐量、资源利用率是否达标 |
| 负载测试 | 逐步加压,找系统正常工作的上限 |
| 压力测试 | 在极限/超负荷下的表现与恢复能力 |
| 容量测试 | 系统能承载的最大数据量/用户数 |
八、交付、转换与维护
8.1 遗留系统处置策略
继续使用 → 包装(封装旧系统、暴露新接口) → 再工程 → 替换 → 退役
决策依据:业务价值、技术债务、维护成本、战略重要性、迁移风险。
8.2 新老系统切换策略
| 策略 | 成本 | 风险 |
|---|---|---|
| 直接切换 | 低 | 高 |
| 并行运行 | 高(双系统并行) | 低 |
| 逐步切换(分阶段) | 中 | 中 |
| 试点运行 | 中 | 中 |
选择关键:业务连续性、数据一致性、回退机制。
8.3 数据迁移 ETL
抽取(Extract) → 转换(Transform) → 加载(Load)
迁移前清洗与校验,迁移后验证一致性,必须制定回退方案。
8.4 软件维护四类型
| 类型 | 目的 | 类比 |
|---|---|---|
| 改正性 | 修复缺陷 | 修故障灯 |
| 适应性 | 适应环境变化(新系统/新法规) | 换雪地胎 |
| 完善性 | 新增功能或改进(占比最高) | 加装导航 |
| 预防性 | 防患未然,为将来可维护性做准备 | 做全面保养 |
判断方法:看修改目的。完善性维护占比最高,改正性维护最紧迫。
维护后必须做回归测试。
结尾:架构是取舍的艺术
内核其实只有一句话——软件工程的所有决策都是权衡。
- 选瀑布还是敏捷,是"计划确定性"与"变化响应速度"的取舍;
- 要高内聚低耦合,是"当下开发效率"与"长期维护成本"的取舍;
- 遗留系统是包装还是重建,是"迁移风险"与"技术债务"的取舍。
对应到架构实践,就是要识别系统的三类关键点:
- 敏感点:某个质量属性对某个架构决策特别敏感(如加密强度直接影响安全性);
- 权衡点:同时影响多个质量属性,且彼此冲突(如缓存提升性能却降低一致性);
- 风险点:可能造成严重后果的架构决策。
过程模型决定了你什么时候知道真相,设计原则决定了你改真相要付多少代价,而维护策略决定了这套系统还能活多久。


浙公网安备 33010602011771号