梳理软件工程的基本概念、生命周期与过程模型,说明软件危机的根源以及敏捷开发的原则和落地方式。
# 软件的定义与特点
软件不是单纯的代码,而是程序、数据结构、文档的集合。
| 要素 | 含义 |
|---|---|
| 程序 | 运行时提供预期功能和性能的指令序列 |
| 数据结构 | 支撑程序处理信息的数据组织方式 |
| 文档 | 描述设计、使用、维护和操作的资料 |
# 软件的典型特点
- 抽象性:软件是逻辑实体,必须借助模型、文档、图表理解。
- 设计开发而非制造:复制成本低,主要成本在设计、开发、验证和维护。
- 不会磨损但会退化:环境变化、需求变化和维护修改会让软件逐渐难以适应。
- 复杂性高:大型软件包含大量模块、接口、状态和并发逻辑。
- 维护困难:修改影响难以评估,维护成本常高于初始开发成本。
- 依赖环境:软件行为受硬件、操作系统、网络、数据库和外部系统影响。
- 持续演化:业务和技术变化要求软件不断升级,否则会逐渐失效。
# 软件危机与软件工程
# 软件危机
软件危机指软件开发中普遍存在的超期、超预算、质量差、维护困难等问题。1968 年 NATO 软件工程会议正式提出 “软件工程” 概念。
主要表现:
- 项目进度和成本失控。
- 软件质量低,错误多,可靠性差。
- 维护困难,每次修改都可能引入新问题。
- 文档缺失,人员流动后项目难以继续。
- 可移植性差,依赖特定环境。
主要原因:
- 软件抽象且复杂,难以直观把控。
- 早期开发依赖个人技巧,缺乏工程化过程。
- 用户需求不明确且频繁变化。
- 缺乏有效的项目管理、质量管理和风险控制。
# 软件工程
IEEE 对软件工程的定义可概括为:
将系统化、规范化、可量化的方法应用于软件开发、运行和维护,并研究这些方法。
软件工程的目标是提高软件质量与生产率,控制成本,使软件开发从 “手工作坊” 转向可管理、可复用、可改进的工程活动。
“没有银弹” 的核心含义:不存在某一种技术或管理方法能单独解决软件开发的复杂性。真正有效的是持续改进过程、工具、设计和团队协作。
# 软件生命周期
软件生命周期是软件从概念提出到停止使用的全过程。它为开发管理提供阶段划分、任务边界和交付物。
1 | flowchart LR |
| 阶段 | 核心问题 | 主要产出 |
|---|---|---|
| 规划 | 值不值得做?范围是什么? | 项目方案、可行性研究报告、项目计划 |
| 需求分析 | 系统必须做什么? | 软件需求规格说明书、用户确认报告 |
| 设计 | 系统应该怎么做? | 概要设计、详细设计、原型、数据库设计 |
| 编码 | 如何把设计变成程序? | 源代码、可执行程序、单元测试报告 |
| 测试 | 软件是否满足需求? | 测试计划、测试用例、测试报告、缺陷记录 |
| 运行维护 | 如何长期稳定运行? | 维护记录、变更申请、版本更新、退役报告 |
# 主要开发过程模型
# 模型总览
| 模型 | 核心特征 | 优势 | 局限 | 适用场景 |
|---|---|---|---|---|
| 瀑布模型 | 顺序执行、文档驱动 | 结构清晰,易管理 | 难以应对需求变化 | 需求明确、变更少、合规要求高 |
| 原型模型 | 快速构建原型,获取反馈 | 帮助澄清需求 | 可能陷入反复修改 | 需求模糊、交互复杂 |
| 增量模型 | 分块开发,逐步交付 | 早期交付核心价值 | 依赖良好架构和增量划分 | 大型系统、产品线 |
| 螺旋模型 | 风险驱动、迭代演进 | 风险管理强 | 管理复杂、成本较高 | 大型、高风险、创新项目 |
| V 模型 | 开发阶段与测试阶段对应 | 强调验证与确认 | 灵活性不足 | 安全关键系统、医疗、汽车电子 |
| RUP | 用例驱动、架构为中心、迭代增量 | 规范全面 | 框架庞大 | 大型企业级系统 |
| 敏捷开发 | 迭代、协作、响应变化 | 适应性强、交付快 | 依赖团队成熟度 | 互联网产品、需求变化频繁 |
# 典型模型理解
1 | flowchart LR |
1 | flowchart LR |
1 | flowchart LR |
选择模型时要综合考虑:
- 需求是否稳定。
- 技术风险是否高。
- 项目规模是否大。
- 用户是否能持续参与。
- 团队是否有成熟工程实践。
- 是否存在强监管、强文档、强质量要求。
# 敏捷开发的产生
传统重型过程强调前期计划和文档,但在需求快速变化的互联网环境中容易出现响应慢、交付晚、用户参与不足等问题。敏捷开发由此产生。
2001 年,17 位软件工程专家发布《敏捷软件开发宣言》。
在软件工程工作这个环境下,什么是敏捷?
Ivar Jacobson 给出一个非常有用的论述:
敏捷已经成为当今描述现代软件过程的时髦用词。每个人都是敏捷的。敏捷团队是能够适当响应变更的灵活团队。变更就是软件开发本身,软件构建有变更、团队成员在变更、使用新技术会带来变更,各种变更都会对开发的软件产品以及项目本身造成影响。我们必须接受 “支持变更” 的思想,它应当根植于软件开发中的每一件事中,因为它是软件的心脏与灵魂。敏捷团队意识到软件是团队中所有人共同开发完成的,这些人的个人技能和合作能力是项目成功的关键所在。
敏捷方法有时候也被称为轻量级方法或精简方法。
# 敏捷宣言四大价值
| 更重视 | 胜过 | 理解 |
|---|---|---|
| 个体和互动 | 过程和工具 | 沟通与协作比僵化流程更重要 |
| 可工作的软件 | 详尽的文档 | 能运行的软件是最直接的进度证明 |
| 客户合作 | 合同谈判 | 持续反馈比一次性约定更有效 |
| 响应变化 | 遵循计划 | 计划要服务于目标,而不是压制变化 |
右项仍有价值,但敏捷更重视左项。
# 敏捷十二原则的核心压缩
- 尽早并持续交付有价值的软件。
- 欢迎需求变化,即使在开发后期。
- 频繁交付可工作的软件。
- 业务人员与开发人员持续协作。
- 围绕有动力的个体构建团队,并信任他们。
- 面对面沟通效率最高。
- 可工作的软件是进度主要度量。
- 保持可持续开发节奏。
- 持续关注技术卓越和良好设计。
- 简单性很重要。
- 最好的架构、需求和设计来自自组织团队。
- 团队定期反思并调整行为。
# 主流敏捷方法
# Scrum
Scrum 是轻量级敏捷框架,强调固定周期迭代、自组织团队和持续交付。
1 | flowchart LR |
| 类型 | 内容 |
|---|---|
| 角色 | 产品负责人、Scrum Master、开发团队 |
| 工件 | 产品待办列表、冲刺待办列表、增量 |
| 事件 | 冲刺、冲刺计划、每日站会、冲刺评审、冲刺回顾 |
核心流程:产品负责人维护产品待办列表,团队在冲刺计划中选择工作项,在固定冲刺内完成可工作的产品增量,并通过评审和回顾持续改进。
# 极限编程 XP
XP 更强调工程实践,用技术纪律保障快速变化下的软件质量。
常见实践:
- 测试驱动开发 TDD:先写测试,再写实现。
- 结对编程:两人协作开发,实时审查与共享知识。
- 持续集成:频繁合并主干,自动构建和测试。
- 简单设计:只实现当前需要的设计。
- 重构:持续改善代码结构。
- 小型发布:频繁交付可工作的版本。
- 集体代码所有权:团队共同维护全部代码。
- 编码规范:统一风格,降低协作成本。
# Kanban
Kanban 强调可视化工作流、限制在制品数量和持续流动。
1 | flowchart LR |
核心要素:
- 可视化所有任务。
- 给每个阶段设置 WIP 限制。
- 完成当前任务后再拉取新任务。
- 通过周期时间、吞吐量等指标持续优化。
# 敏捷与传统开发对比
| 维度 | 瀑布模型 | 敏捷开发 |
|---|---|---|
| 需求 | 前期尽量完全确定 | 持续演进,欢迎变化 |
| 交付 | 一次性交付最终产品 | 频繁交付可工作的软件 |
| 用户参与 | 主要在需求和验收阶段 | 贯穿全过程 |
| 团队结构 | 按职能划分 | 跨职能自组织团队 |
| 文档 | 文档详尽 | 轻文档,重沟通 |
| 计划 | 前期详细计划 | 滚动式计划 |
| 质量保证 | 后期集中测试 | 全程测试、持续集成 |
| 成功度量 | 按计划完成 | 交付客户满意的软件 |
# 适用场景与挑战
# 适合敏捷的场景
- 需求不明确或变化频繁。
- 需要快速推向市场验证。
- 客户能深度参与。
- 团队具备较强自组织能力。
- 项目可拆成可持续交付的小增量。
# 敏捷的挑战
- 团队成熟度要求高。
- 文档不足可能影响长期维护。
- 客户参与不足会削弱反馈闭环。
- 大规模、多地点团队实施成本高。
- 传统层级组织和敏捷文化可能冲突。
# 本专题考点提炼
- 软件由程序、数据结构、文档组成,不等于代码。
- 软件危机推动软件工程产生,核心问题是复杂性、不可见性和管理失控。
- 软件工程的目标是系统化、规范化、可量化地开发、运行和维护软件。
- 生命周期典型阶段:规划、需求分析、设计、编码、测试、运行维护。
- 开发模型没有绝对最优,必须按需求稳定性、风险、规模和团队能力选择。
- 敏捷不是不要文档和计划,而是更重视可工作的软件、协作和响应变化。
- Scrum 管过程,XP 管工程实践,Kanban 管工作流。
# 快速自测
- 软件危机有哪些典型表现?
- 软件工程为什么强调 “工程化”?
- 瀑布模型和敏捷开发的根本差异是什么?
- 原型模型适合什么场景?
- Scrum 的三类核心内容分别是什么?
- XP 中 TDD、结对编程、持续集成分别解决什么问题?
# 扩展阅读
拉布雷阿的焦油坑:大型软件项目为什么会越陷越深
《人月神话》开篇用 “拉布雷阿焦油坑” 比喻大型系统开发:史前巨兽陷入焦油后越挣扎陷得越深,最后沉入坑底。
大型软件项目也常如此。单个问题看起来都不致命:需求不清、接口变更、进度压力、人员沟通、技术债务、测试不足、文档缺失、管理失控。每个问题似乎都能处理,但它们相互缠绕后,会让团队行动越来越慢。
这个比喻强调:
- 软件危机通常不是由一个单点问题造成,而是复杂因素累积。
- 项目规模越大,沟通、协调和变更成本越高。
- “加人”“加班”“补文档” 不一定能解决根因,甚至可能让系统更混乱。
- 软件工程的价值在于用过程、架构、质量保证和项目管理降低复杂性。
复习时可把它理解为:软件工程不是为了制造流程感,而是为了防止复杂系统开发滑入不可控状态。
软件重大质量事故案例:软件错误的代价
软件错误可能直接造成生命、财产和公共服务损失,因此软件质量保证不是形式主义。
典型案例:
- 爱国者导弹拦截失败:1991 年海湾战争期间,爱国者导弹系统因时间计算误差未能拦截飞毛腿导弹,造成 28 名美军士兵死亡、约 100 人受伤。启示是数值精度、时间累积误差和长时间运行测试必须被重视。
- 阿丽亚娜 5 型火箭首飞失败:1996 年阿丽亚娜 5 型火箭发射 39 秒后自毁,原因包括复用阿丽亚娜 4 的惯性导航代码,但新火箭飞行条件不同,导致转换溢出。启示是代码复用必须重新验证上下文假设。
- 巴拿马放疗软件事故:治疗规划软件剂量计算错误,导致部分患者接受过量辐射,造成严重伤亡。启示是安全关键软件必须有严格验证、独立复核和风险控制。
- 温州动车事故:2011 年甬温线动车追尾事故中,信号设备设计缺陷在雷击故障后导致区间信号显示异常。启示是安全关键系统要考虑故障模式、冗余设计和异常状态处理。
- 波音 737 MAX 事故:迎角传感器错误触发 MCAS 防失速系统,导致机头持续下压并引发坠机。启示是自动控制软件必须关注传感器可靠性、人机交互、冗余与可解释性。
- 火星气候探测者号丢失:NASA 探测器因公制和英制单位混用,以错误轨迹进入火星大气层。启示是接口契约、单位规范和跨团队协作验证非常关键。
- CrowdStrike 全球蓝屏事件:2024 年终端安全产品更新导致大量 Windows 主机蓝屏,影响交通、金融、医疗、零售和云服务。启示是自动更新、灰度发布、回滚机制和生产前验证是基础质量工程。
这些案例共同说明:
- 软件缺陷可能不只造成 “程序不好用”,还可能造成系统级灾难。
- 需求、设计、编码、测试、部署、运维任一环节失守都可能引发事故。
- 安全关键系统必须重视形式化审查、边界条件、异常流程、独立测试和变更控制。
- 软件工程的核心目标之一是用系统方法降低失败概率和事故影响范围。
人月神话与没有银弹
“人月” 是工作量单位,表示一个人工作一个月的工作量。直觉上,10 人月似乎可以由 1 个人做 10 个月,也可以由 10 个人做 1 个月,但软件项目并不完全满足这种线性换算。
《人月神话》的核心观点之一是:
向已经延期的软件项目增加人手,只会让项目更加延期。
原因:
- 新成员需要学习系统、需求、代码和团队规范。
- 老成员要花时间培训新成员,短期产出反而下降。
- 人数增加会带来更多沟通路径,协调成本快速上升。
- 任务之间存在依赖关系,不能无限并行。
布鲁克斯在《没有银弹》中进一步指出:未来十年内,不论技术还是管理方法,都很难单独带来数量级的软件生产率、可靠性和简洁性提升。
这里的 “银弹” 指一招制胜的万能方案。软件开发的本质困难来自:
- 复杂性:软件需要表达大量状态、规则和交互。
- 一致性:软件必须适配业务、硬件、系统、接口和历史约束。
- 可变性:需求和环境会持续变化。
- 不可见性:软件结构抽象,难以像建筑图纸那样直接观察。
因此,软件工程不能寄希望于某个工具或方法一次性解决所有问题。有效改进通常来自组合拳:
- 更清晰的需求分析。
- 更好的架构设计。
- 更严格的测试与评审。
- 自动化构建和持续集成。
- 合理的项目计划和风险管理。
- 高质量沟通和团队协作。
复习结论:软件工程没有万能药,核心是承认复杂性,并用系统化、规范化、可量化的方法持续降低复杂性带来的风险。
