1软件工程概述
1.1 软件危机
20 世纪 60 年代,随着软件规模迅速增大,软件开发陷入严重困境:项目经常延期、预算超支、质量低劣、维护困难, 1968 年北大西洋公约组织(NATO)的学术会议上把这种现象称为"软件危机"。其典型表现:
- 开发成本和进度估计不准,经费预算一再突破;
- 用户对已完成的软件系统不满意(需求理解偏差);
- 产品质量差、可靠性低,常常出错甚至崩溃;
- 缺少文档,软件难以维护,维护成本占比越来越高(常占全部成本的一半以上)。
危机的根源:一方面软件本身是逻辑产品、复杂且易变、规模庞大;另一方面是开发方法不当——忽视需求分析、缺乏工程规范、过分依赖个人技巧。
1.2 软件工程的定义
软件工程的三要素:方法(完成各项任务的技术方法,如需求分析方法、测试方法)、 工具(支持方法的自动化/半自动化工具,即 CASE 工具)、过程(为获得高质量软件所执行的一系列任务框架)。
1.3 软件质量特性(ISO/IEC 25010)
评价软件好坏不只看"能不能用",主要质量特性包括:功能性、可靠性、易用性、效率、可维护性、可移植性(经典 McCall/ISO 模型), 以及现代的安全性、兼容性等。好软件是在质量、成本、进度"铁三角"间取得平衡。
2软件生命周期
- 问题定义:明确"要解决的问题是什么",写出问题定义报告。
- 可行性研究:从技术、经济、操作、法律等角度判断项目是否值得做、能否做成。
- 需求分析:详细弄清楚目标系统"必须做什么",产出软件需求规格说明书(SRS)。
- 概要设计(总体设计):确定系统体系结构、划分模块、定义模块间接口。
- 详细设计:确定每个模块的具体算法和数据结构,回答"怎样具体实现"。
- 编码(实现):用程序设计语言把设计翻译成可执行的程序。
- 测试:通过各种测试发现并排除错误,包括单元测试、集成测试、系统测试、验收测试。
- 运行与维护:软件交付使用后持续排错、适应环境变化、扩充功能,直到退役。
前三阶段属于软件定义时期(决定做什么、值不值得做),中间阶段属于软件开发时期,最后是运行维护时期。 一个重要规律:错误发现得越晚,修复代价呈指数级上升——需求阶段的疏漏若到测试期才发现,修复成本可能高达几十上百倍,所以前期分析设计至关重要。
3软件过程模型
软件过程模型(软件开发模型)是软件生命周期各项活动的组织框架,规定活动的顺序、交付物和过渡条件。常见模型:
3.1 瀑布模型
各阶段自上而下、像瀑布一样依次衔接:定义 → 可行性 → 需求 → 设计 → 编码 → 测试 → 维护,每个阶段有文档和评审,上一阶段确认后才进入下一阶段。
- 优点:过程规范、文档齐全、便于管理,适合需求明确且稳定的项目。
- 缺点:缺乏灵活性,用户要到很晚才能看到可运行软件,无法应对需求变更。
3.2 快速原型模型
先快速搭建一个可运行的原型给用户试用,根据反馈反复修改,逐步明确需求后再正式开发。优点是能有效减少需求误解;原型可能被丢弃或演进为最终系统。
3.3 增量模型与螺旋模型
- 增量模型:把软件分成若干构件,分批设计实现交付,用户先用到核心功能,后续版本逐步增加功能。
- 螺旋模型:将瀑布与原型结合,每一圈螺旋都包含制定计划 → 风险分析 → 实施工程 → 客户评估,特别强调风险分析,适合大型、高风险项目。
3.4 喷泉模型与敏捷开发
- 喷泉模型:面向对象的开发模型,各阶段无明显边界、可反复迭代(如水花交叠),自下而上迭代、无缝。
- 敏捷开发(Agile):应对需求快速变化的一系列轻量方法的总称,核心价值观强调"个体与互动、可工作的软件、客户合作、响应变化"。 典型实践 Scrum:把开发分成 2~4 周的冲刺(Sprint),通过产品待办列表(Product Backlog)、每日站会、评审会迭代交付增量;XP(极限编程)强调测试驱动、结对编程、持续集成。
| 模型 | 最适合的场景 | 关键特征 |
|---|---|---|
| 瀑布模型 | 需求稳定、类似已有系统 | 线性顺序、文档驱动 |
| 原型模型 | 需求模糊、人机交互多 | 快速原型、用户反馈 |
| 增量模型 | 需尽早交付核心功能 | 分批交付、逐步增强 |
| 螺旋模型 | 大型复杂高风险项目 | 风险驱动、迭代+原型 |
| 敏捷 Scrum | 需求多变的互联网产品 | 短迭代、快速响应、拥抱变化 |
4可行性研究与需求分析
4.1 可行性研究
在项目正式立项前,用最小代价在尽可能短的时间内确定问题是否能够解决、是否值得解决,通常从四个方面论证:
- 技术可行性:现有技术、人员、设备能否实现?
- 经济可行性:成本—效益分析(开发成本 vs 预期收益、投资回收期)。
- 操作(运行)可行性:系统在用户环境中能否被接受和顺利运行?
- 法律与社会可行性:是否侵权、违反法规,对组织和社会有何影响?
产出可行性研究报告和粗略的项目开发计划,供决策者拍板。
4.2 需求分析
需求分析要准确回答"系统必须做什么",而不是"怎么做"。包括:
- 功能需求:系统必须提供的功能(如登录、下单、查询成绩)。
- 非功能需求:性能(响应时间)、安全性、可靠性、易用性、可扩展性等质量约束。
- 通过访谈、问卷、跟班作业、用例分析等方式获取需求,经用户确认形成 软件需求规格说明书 SRS——这是开发方与用户之间的"合同",也是后续设计、测试和验收的依据。
4.3 结构化分析方法(SA)
面向过程的经典需求分析方法,思想是"自顶向下、逐层分解",主要工具:
- 数据流图(DFD):用四种图形元素描述数据流动与加工—— 外部实体(方框)、加工/处理(圆或圆角矩形)、数据存储(双杠/开口矩形)、数据流(带箭头的线)。
- 数据字典(DD):对 DFD 中所有数据流、数据项、数据存储、加工给出精确定义,是 DFD 的补充说明。
- 加工逻辑可用判定表、判定树、结构化语言描述。
面向对象的需求分析则使用用例图描述参与者与系统功能的关系(UML 的一部分,见第 9 节)。
需求阶段最忌开发人员"想当然"。需求必须完整、一致、无歧义、可验证,并由用户签字确认;含糊的需求(如"界面要好看")应转化为可度量的指标。
5软件设计
设计阶段把需求模型转化为可实现的解决方案,分概要设计与详细设计两步。
5.1 概要设计(总体设计)
- 确定系统体系结构:如分层架构(表现层—业务层—数据层)、C/S、B/S、MVC 模式、微服务架构。
- 模块划分与接口设计:把系统划分为若干模块,规定模块间调用和数据传递关系。
- 设计数据结构、数据库(配合 E-R 模型)、出错处理和部署方案。
5.2 核心设计原则:高内聚、低耦合
内聚(模块内部)
一个模块内部各元素彼此结合的紧密程度。内聚越高越好:模块只做好一件事(功能内聚最佳),避免把毫不相干的功能塞在一起(偶然内聚最差)。
耦合(模块之间)
模块间相互依赖、连接的程度。耦合越低越好:接口简单清晰、少用全局变量,尽量使用数据耦合(只传必要参数),避免内容耦合(直接访问对方内部代码)。
面向对象设计还有著名的 SOLID 原则(单一职责、开闭原则、里氏替换、接口隔离、依赖倒置)以及"信息隐藏"思想。
5.3 详细设计工具
详细设计确定每个模块的算法与控制流程,但不写具体代码,常用描述工具:
- 程序流程图:直观但容易随意跳转;N-S 图(盒图):用嵌套方框强制结构化;
- PAD 图(问题分析图):二维树形结构,支持结构化;判定表/判定树:描述复杂条件组合;伪代码(PDL):类语言描述算法。
6软件编码
编码是把详细设计翻译为用程序设计语言书写的源程序的过程。编码质量直接影响可维护性,关键在于规范而非炫技。
6.1 语言选择与编码规范
- 语言选择考虑应用领域、团队熟悉度、性能要求、生态与可维护性(系统底层选 C/C++,企业应用选 Java,AI 与脚本选 Python,前端选 JavaScript)。
- 良好的编码风格:标识符见名知义;统一缩进与括号风格;关键逻辑写注释(解释"为什么"而非复述"做什么"); 函数短小、单一职责;避免深层嵌套和魔法数字;规范文件组织。
6.2 代码审查与版本管理
- 代码审查(Code Review):同行阅读评审代码,是发现缺陷、统一风格、知识共享的有效手段,优于单纯依赖测试。
- 版本控制:用 Git 等工具管理代码历史与分支,支持多人协作、回退和并行开发;配合 GitHub/GitLab 进行 Pull Request/Merge Request 流程。
- 持续集成 CI:频繁地把代码合入主干并自动构建、跑测试,尽早发现集成问题。
7软件测试
7.1 测试的基本原则
- 测试应尽早、不断地进行;程序员应避免测试自己的程序;
- 妥善保存测试用例;注意错误的"群集现象"(错误常扎堆出现);
- 严格按测试计划执行,既要有合法输入,也要有非法和边界输入。
7.2 黑盒测试与白盒测试
黑盒测试(功能测试)
把程序看成不能打开的黑盒子,不考虑内部结构,只根据需求规格检查输入输出是否正确。常用方法:
- 等价类划分:把输入域分成若干等价类,每类取代表值
- 边界值分析:重点测边界(如范围 1~100 测 0、1、100、101)
- 因果图、错误推测法
白盒测试(结构测试)
把程序盒子打开,基于内部逻辑结构设计用例,追求对程序路径的覆盖。常用方法:
- 逻辑覆盖:语句覆盖、判定(分支)覆盖、条件覆盖、判定/条件覆盖、路径覆盖(覆盖程度由弱到强)
- 基本路径测试:根据程序图的环复杂度确定独立路径集合
7.3 测试的四个阶段
| 阶段 | 对象 | 依据 / 说明 |
|---|---|---|
| 单元测试 | 单个模块/函数 | 详细设计;多采用白盒,由开发者完成,用桩模块/驱动模块配合 |
| 集成测试 | 组装后的模块群 | 概要设计;检查接口,策略有一次性组装与渐增式(自顶向下/自底向上) |
| 系统测试 | 整个系统(软硬件环境) | 需求规格;黑盒为主,含功能、性能、压力、安全、兼容性测试 |
| 验收测试 | 用户参与确认 | α 测试(开发者在场,内部环境)与 β 测试(用户在实际使用环境试用) |
调试(Debug)与测试不同:测试是发现错误,调试是确定错误根源并改正,常用方法有蛮干法、回溯法、原因排除法(演绎、归纳、二分)。
8软件维护
软件交付用户使用之后进入运行维护阶段。维护是生命周期中持续时间最长、花费最大的阶段。按目的分为四类:
| 维护类型 | 目的 | 占比(经验值) |
|---|---|---|
| 改正性维护 | 改正在开发期未发现、运行后暴露的错误 | 约 20% |
| 适应性维护 | 使软件适应新的软硬件环境、法规政策变化 | 约 25% |
| 完善性维护 | 根据用户反馈增加新功能、改进性能与体验(占比最大) | 约 50% |
| 预防性维护 | 主动改进,为将来的可维护性和可靠性打基础 | 约 5% |
维护中要经过提出维护申请 → 评价与审批 → 修改程序及相关文档 → 回归测试 → 交付的受控流程, 不能拿到问题就直接改代码。可维护性主要取决于软件的可理解性、可测试性和可修改性——它们其实由前期的设计质量和文档质量决定。
维护与开发一样必须"文档先行、同步更新文档"。只改代码不改文档,会让系统在下一次维护时变成无人能懂的"祖传代码"。
9软件项目管理与 UML
9.1 项目管理的核心内容
软件项目管理在人员、产品、过程、项目四个维度上展开,核心是"四控":进度控制、成本控制、质量控制、风险控制。
- 成本估算:代码行估算法、功能点估算法、COCOMO 模型等;估算应基于工作量(人月)而非简单按工期线性加人——《人月神话》指出"向进度落后的项目增加人手只会使其更落后"(Brooks 定律)。
- 进度管理:
- Gantt 图(甘特图):用水平条形图表示各任务的起止时间,直观易懂,是最常用的进度工具,但不易表达任务间依赖。
- PERT 图/工程网络:用箭头和节点表示任务及依赖,可计算关键路径——关键路径上的活动决定项目最短工期,其延期会导致整个项目延期。
- 人员组织:主程序员组、民主制小组等;配置管理:管理软件的各种版本和变更(基线、版本库、变更控制委员会 CCB)。
- 风险管理:识别、评估、监控风险(人员流失、需求变更、技术不成熟)并制定应对预案。
9.2 软件能力成熟度模型(CMM/CMMI)
用于评价软件组织的过程成熟度,从低到高分五级:初始级 → 可重复级 → 已定义级 → 已管理级 → 优化级。 等级越高,开发过程越规范、项目结果越可预期。
9.3 UML 统一建模语言
UML(Unified Modeling Language)是面向对象软件开发的标准可视化建模语言(不是开发方法),常用图包括:
| 图 | 作用 |
|---|---|
| 用例图 | 从用户角度描述系统功能与参与者(需求分析) |
| 类图 | 描述类、接口及其静态关系(关联、继承、依赖) |
| 对象图、组件图、部署图 | 对象实例、软件构件、系统物理部署结构 |
| 顺序图、协作图 | 对象间动态消息交互的时序/合作关系 |
| 状态图、活动图 | 对象状态转移、业务流程与并发行为 |
本章小结:软件工程用工程化方法应对软件危机;软件生命周期"定义—开发—维护"由不同过程模型(瀑布、原型、增量、螺旋、敏捷)组织; 需求分析用 DFD/用例把"做什么"说清楚,设计追求高内聚低耦合,编码讲究规范与评审,测试用黑盒/白盒在四个阶段层层把关,交付后在受控流程中持续维护,项目管理用甘特图、关键路径和成熟度模型保障进度成本质量。最后一章,让我们展望正在改变世界的计算机新技术。