第 1 章 · 软件危机与软件工程

1968 年,北约在西德加米施开了一场会,会议主题匪夷所思:软件。不是因为软件多成功要办庆功会,而是因为大规模软件项目大面积失控:超支、延期、质量失控,IBM 的 OS/360(370 人年)延期数年,IBM 总裁公开承认「我们造的房子比卖出去的多」。会议借用建筑工程的词,造了 software engineering 这个术语,试图把「建大软件」从手工艺变成工程。本章讲清三件事:危机到底危机在哪、传统工程的解法为什么不完全适用、以及软件工程真正的核心问题是什么。

1.1 危机的三个病灶

OS/360 时代的问题清单,今天的项目照样复发,值得逐一认识:

复杂度。硬件的复杂度是「算术级」的(加零件,部件间关系线性),软件的复杂度是「指数级」的:模块间的交互关系随模块数平方级增长。一个 10 万行代码的系统,没有任何人能完全理解它。Brooks 在《没有银弹》里把这叫本质复杂度(essence),它源自问题域本身,不是实现失误。

不可见性。软件没有空间实体,画成图都是抽象投影(流程图、UML,第 6 章),而盖楼有图纸有进度有实体墙。人脑对「看不见的东西」的进度估算天生不行。

易变性。客户的需求会变(而且理直气壮地变),代码改起来比改楼容易得多,于是「需求变更」成了软件世界的常态而非异常。盖楼没人说「改一版,窗户全朝北吧」,软件天天被这么要求。

这三个病灶合起来解释了一个反直觉的现象:加人不能加快延期的项目(Brooks 定律:「往延期项目里加人只会更延期」):新人要培训、沟通路径 n(n-1)/2 增长。人月神话的书名即此。

1.2 为什么不能照搬传统工程

土木工程的解法(详尽图纸 → 按图施工 → 验收)映射到软件就是瀑布模型(第 2 章),它在需求稳定时有效。但软件的需求天然易变,而代码的可变性恰恰是优势:与其抵制变更,不如组织过程拥抱变更。这是敏捷运动(2001 敏捷宣言)的出发点,也是本课第 2 章的主线。

另一个照搬失败的是「测试在末端」:盖楼封顶后验收,软件这么做等于把 80% 的缺陷留到最后修,修复成本按阶段指数上涨(需求期的错到上线期修,贵 100 倍,这是软件工程里少数有可靠数据支撑的结论)。所以现代实践把验证往前挪:需求期评审、开发期持续测试(第 8 章)、提交前 CI(第 10 章)。

1.3 软件工程的真正核心:管理复杂度

排除掉「像盖楼一样盖软件」的幻想,这门课剩下的核心命题只有一条:控制复杂度。所有具体手段都能挂到这个母题下:

  • 分层与模块化(第 4 章):把平方级的交互关系切成局部问题;
  • 抽象与接口(第 5 章):隐藏细节,让每层只关心自己的事;
  • 测试与持续集成(第 8、10 章):让「改动」的风险可测量、可回滚;
  • 版本控制与协作流程(第 9 章):让多人改动不互相摧毁;
  • 需求管理(第 3 章):在变更进入代码之前过滤它。

Java 课的面向对象、数据库课的范式、本课的设计模式,本质都是复杂度管理在某层的具体形态。把这个母题记住,后面每章你都知道「这个手段在管什么复杂度」。

1.4 一个课程设计的预演

用一个真实场景结束本章:大一课设,两个人合作,三个月。没有软件工程思维的做法:开闷头写,git 用得稀碎(整周一个 commit),最后三天通宵集成,merge 冲突三百行,演示前一晚发现两个人对「用户登录」的理解完全不同。有软件工程思维的做法:第一周对齐需求(写一页纸的需求清单,第 3 章)、按模块分界(第 4 章)、每天小步提交(第 9 章)、核心逻辑带测试(第 8 章)。同样的两个人,工时一样,结局天差地别,这门课的全部价值就在这类对比里。

常见错误

  • 把软件工程当「文档课」。写一堆没人看的文档交差;文档的判定标准是「三个月后还有人查吗」。
  • 把敏捷当「不写文档、直接开干」。恰恰相反,敏捷对「可工作的软件」和「持续沟通」的要求更严(第 2 章)。
  • 以为工具能救烂流程。上了最贵的 CI 也救不了「需求没人对齐」;工具是流程的放大器。

练习

  1. 思考:回忆你(或你听说过)最失控的一次项目/作业,用 1.1 的三个病灶分类它的病因,想想当时的什么决定让它恶化。
  2. 手算:5 人团队改成 9 人,沟通路径从几条变几条?如果每条路径每周平均耗 1 小时对齐,每周多花多少人时?用这个数字检验「加人更快」的直觉。
  3. 动手:找你最近一个项目,统计 git log:平均提交间隔、单次提交的改动行数、提交信息里有多少能看懂「为什么」。给自己打分,写两条改进。
  4. 思考:Brooks 说「没有银弹」,但三十年间软件工程确实进步了(git、CI、云)。这些进步解决的是本质复杂度还是偶然复杂度(accidental complexity,工具差导致的)?列三个你用过的工具,判断它们消除的是哪类复杂度。