第1章 欢迎进入软件构建的世界 3
图1-1 构建活动用灰色的椭圆表示。构建活动主要关注于编码与调试,但也包含详细设计、单元测试、集成测试以及其他一些活动 4
图1-2 本书大致以图示的比例关注编码与调试、详细设计、规划构建、单元测试、集成、集成测试以及其他活动 5
第2章 用隐喻来更充分地理解软件开发 9
图2-1 文字写作这一隐喻暗示着软件开发过程是一种代价昂贵的试错(trial and error)过程,而非仔细的规划和设计 14
图2-2 很难将耕作这一隐喻恰当地引申到软件开发领域 15
图2-3 在简单结构上犯下错误,其惩罚也不过是一点时间,或是些许尴尬 17
图2-4 更复杂的结构需要更加仔细地规划 18
第3章 三思而后行:前期准备 23
表3-1 修复缺陷的平均成本与引入缺陷的时间和检测到该缺陷的时间之间的关系 29
图3-1 修复缺陷的成本随着“从引入缺陷到检测该缺陷之间的时间”变长而急剧增加。无论项目是高度序列化(sequential)的(预先完成100%的需求和设计),还是高度迭代型(预先完成5%的需求和设计)的,这些都成立 30
表3-2 三种常见的软件项目种类,及其典型的良好实践 31
表3-3 跳过前期准备对于采用序列式开发法的项目和迭代式开发法的项目的(不同)影响 33
表3-4 关注前期准备工作对于采用序列式开发法的项目和迭代式开发法的项目的影响 34
图3-2 对于绝大部分的项目(即便是高度序列化的项目)来说,各种活动会在一定程度上有所重叠 35
图3-3 对于其他的项目,各种活动在项目开发期间会重叠起来。成功“构建”的关键之一,就是理解前期准备工作的完成程度,并据此调整你的开发方法 35
图3-4 “问题定义”为随后的开发过程打下基础 37
图3-5 在射击之前,确信你瞄准了正确的目标 38
图3-6 如果没有好的需求,你可能对问题有总体的把握,但却没有击中问题的特定方面 39
核对表:需求 42
图3-7 离开了良好的软件架构,你可能瞄准了正确的问题,但却使用了错误的解决方案。也许完全不可能有成功的构建 44
核对表:架构 54
核对表:前期准备 59
第4章 关键的“构建”决策 61
表4-1 高级语言的语句与等效的C代码语句行数之比 62
核对表:主要的构建实践 69
第5章 软件构建中的设计 73
图5-1 Tacoma Narrows大桥——一个险恶问题的实例 75
图5-2 一个程序中的设计层次。系统①首先被组织为子系统②。子系统被进一步分解为类③,然后类又被分解为子程序和数据④。每个子程序的内部也需要进行设计⑤ 82
图5-4 当子系统之间的通信没有任何限制时就会像这个样子 83
图5-3 一个有六个子系统的系统示例 83
图5-5 施加若干通信规则后,子系统之间的交互得以显著地简化 84
图5-6 收费系统由四种主要的对象构成,这些对象在本例中进行了一定的简化 88
图5-7 抽象可以让你用一种简化的观点来考虑复杂的概念 90
图5-8 封装是说,不只是让你能用简化的视图来看复杂的概念,同时还不能让你看到复杂概念的任何细节。你能看得到的就是你能——全部——得到的 91
图5-9 好的类接口就像是冰山的尖儿一样,让类的大部分内容都不会暴露出来 93
表5-1 常见设计模式 104
图5-10 G波利亚在数学领域发展的一套解决问题的方法,它同样可以用于解决软件设计中的问题(Polya 1957) 109
表5-2 设计文档的正规化以及所需的细节层次 116
核对表:软件构造中的设计 122
第6章 可以工作的类 125
表6-1 继承而来的子程序的几种形式 145
核对表:类的质量 157
第7章 高质量的子程序 161
核对表:高质量的子程序 185
第8章 防御式编程 187
图8-1 西雅图90号州际浮桥的一部分在一场风暴中沉没了,原因是未遮盖浮箱,而在风暴中进水,使得桥体过重而无法继续漂浮。在建设时要防范一些小事情,它们的严重性往往会超过你的预期 189
表8-1 支持几种流行的编程语言的表达式 198
图8-2 让软件的某些部分处理“不干净的”数据,而让另一些部分处理“干净的”数据,即可让大部分代码无须再担负检查错误数据的职责 204
核对表:防御式编程 211
第9章 伪代码编程过程 215
图9-1 一个类的创建过程可以千变万化,但基本上会以本图所示的顺序发生 216
图9-2 这些是创建一个子程序所需经历的主要活动,常是以图示的顺序进行 217
图9-3 在构建程序的时候,你将实施所有这些步骤,但不一定要按照任何特定的顺序 225
核对表:伪代码编程过程 233
第10章 使用变量的一般事项 237
图10-1 “长存活时间”意味着一个变量历经了许多语句,而“短存活时间”意味着它只历经很少的语句。“跨度”则表明了对一个变量引用的集中程度 246
图10-2 序列型数据就是按照一种确定顺序处理的数据 254
图10-4 迭代型数据是重复性的 255
图10-3 选择型数据允许你使用这一项或者那一项,但不会同时使用两者 255
核对表:使用数据的一般事项 257
第11章 变量名的力量 259
表11-1 更多变量名的例子,其中有好的也有差的 261
表11-2 变量名太长、太短或刚好合适的示例 262
表11-3 C++和Java的命名规则示例 277
表11-5 Visual Basic的命名规则示例 278
表114 C的命名规则示例 278
表11-6 用于文字处理程序的UDT示例 280
表11-7 语义前缀 280
核对表:变量命名 288
第12章 基本数据类型 291
表12-1 不同类型整数的取值范围 294
核对表:基本数据类型 316
第13章 不常见的数据类型 319
图13-1 各数据类型所用的内存量用双线框表示 324
图13-2 能帮助我们考虑指针链接步骤的示例图 329
表13-1 直接访问全局数据和通过访问器子程序访问全局数据 341
表13-2 对复杂数据一致和不一致的操作 342
核对表:使用不常见数据类型的注意事项 343
第14章 组织直线型代码 347
图14-1 如果代码组织良好,那么围绕各段的方框就不应该交叠,但有可能嵌套 352
图14-2 如果代码组织不良好,那么围绕各段代码的方框就会交叠 353
核对表:组织直线型代码 353
第15章 使用条件语句 355
核对表:使用条件语句 365
第16章 控制循环 367
表16-1 循环的种类 368
核对表:循环 388
第17章 不常见的控制结构 391
图17-1 递归式是对付复杂事物的很有价值的工具——在用于对付适当问题的时候 394
核对表:不常见的控制结构 410
第18章 表驱动法 411
图18-1 如图名所示,直接访问表允许你访问感兴趣的表元素 413
图18-2 信息并不是按照特定顺序存储的,每条消息用ID标识 417
图18-3 除了消息ID之外,每种消息有其自己的格式 418
图18-4 索引表不是直接访问,而是经过居间的索引去访问 425
图18-5 阶梯方法通过确定每项命中的阶梯层次确定其归类,它命中的“台阶”确定其类属 426
核对表:表驱动法 429
第19章 一般控制问题 431
表19-1 狄摩根定理的逻辑表达式的转换法则 436
图19-1 一个用数轴顺序做布尔判断的例子 440
表19-2 计算子程序中决策点数量的技术 458
核对表:控制结构相关事宜 459
第20章 软件质量概述 463
图20-1 强调软件的某个外在特性,可能会对另一些特性产生正面或者负面的影响,也可能没有任何影响 466
表20-1 各个小组在每个目标上的排名 469
表20-2 缺陷检测率 470
表20-3 极限编程的缺陷检出率评估值 472
图20-2 既不是最快的,也不是最慢的开发方法生产出的软件缺陷最多 475
核对表:质量保证计划 476
第21章 协同构建 479
核对表:有效的结对编程 484
核对表:有效的详查 491
表21-1 协同构建技术的比较 495
第22章 开发者测试 499
图22-1 随着项目规模的增大,开发者测试所耗费的开发时间百分比会更少。程序规模对测试的影响将在第27章“程序规模对构建的影响”做出详细描述 502
图22-2 随着项目规模的增长,在构建期间产生的错误所占的比例会下降,然而即使是在巨型项目里面,构建错误也会占全部错误的45%至75% 521
核对表:测试用例 532
第23章 调试 535
图23-1 尝试用多种方法重现错误以准确判定错误原因 545
表23-1 变量名之间的心理距离示例 556
核对表:关于调试的建议 559
第24章 重构 563
核对表:重构的理由 570
核对表:重构总结 577
图24-1 相对于大规模修改,小的改动更容易出错(Weinberg 1983) 581
图24-2 真实世界混乱不堪并不等于你的代码也得同样糟糕。将你的系统看做理想代码、混乱的真实世界,以及从前者到后者的接口的结合 583
图24-3 改善产品代码的策略之一就是在拿到拙劣的遗产代码时对其重构,由此使其告别混乱不堪的真实世界 584
核对表:安全的重构 584
第25章 代码调整策略 587
表25-1 编程语言的相对执行时间 600
表25-2 常见操作所用时间 601
核对表:代码调整策略 607
第26章 代码调整技术 609
核对表:代码调整方法 642
第27章 程序规模对构建的影响 649
图27-1 交流路径的数量与项目成员数量的平方大致 650
表27-1 项目规模和典型的错误密度 652
图27-2 随着项目规模的增大,通常需求和设计犯的错误会更多。有些时候,错误仍然主要来自构建(Boehm 1981,Grady 1987,Jones 1998) 652
表27-2 项目规模和生产率 653
图27-3 小项目以构建活动为主。更大的项目需要做更多的架构、集成工作和系统测试工作才能成功。图中并未显示“需求工作”,因为其工作量并不(像其他活动那样)直接是程序大小的函数(Albrecht 1979;Glass 1982;Boehm、Gray and Seewaldt 1984;Boddie 1987;Card 1987;M 654
图27-4 软件构建的工作量与项目大小呈近似线性的关系。其他活动的工作量随项目规模扩大而非线性地增加 655
图28-1 本章讲述与构建相关的软件管理话题 661
第28章 管理构建 661
核对表:配置管理 669
图28-2 项目早期的评估结果注定不会很准确。随着项目推进,评估的准确度会越来越高。在项目进行过程中要定期地重新评估,用你在每一项活动中学到的知识去改进你对下一项活动的评估 673
表28-1 影响软件项目工作量的因素 674
表28-2 有用的软件开发的度量环节 678
表28-3 有关程序员如何分配时间的一种观点 681
第29章 集成 689
图29-1 华盛顿大学的露天足球场坍塌了,因为它在建造时不能支撑自己的重量。很可能在完工后它会足够牢固,但是它的建造顺序是错的——这是一个“集成”错误 690
图29-2 阶段式集成也称为大爆炸集成,其理由很充分 691
图29-3 增量集成有助于项目增长,就像雪球从山上滚下来时那样 692
图29-4 在阶段式集成中,你一次集成许多组件,很难知道错误在哪。错误既可能位于其中任何一个组件,也可能位于组件之间的连接处。在增量集成中,错误通常要么是在新的组件中,要么是位于新组件和系统之间的连接中 693
图29-5 在自顶向下的集成中,首先加入顶部的类,最后加入底部的类 695
图29-6 除了严格的自顶向下进行集成,你也可以在各个竖直划分的功能块中自上而下地进行集成 696
图29-7 在自底向上集成中,先集成底部的类,后集成顶部的类 697
图29-8 除了按纯粹的自底向上的步骤进行集成,你也可以分块进行这种集成。这样做模糊了自底向上集成和功能导向的集成(本章稍后将描述)之间的界线 698
图29-9 在三明治集成中,首先集成顶层类和广泛使用的底层类,然后集成中间层类 698
图29-10 在风险导向集成中,首先集成你认为最棘手的类,然后实现较容易的类 699
图29-11 在功能导向的集成中,以一组“构成一项‘可确认的功能’”的类为单位进行集成——通常(但不总是)一次集成多个类 700
图29-12 在T-型集成中,你建造并集成系统的一个直插到底层的块,以验证架构的假设,然后建造并集成系统的挑大梁部件,为开发余下的功能提供一个框架 701
核对表:集成 707
第30章 编程工具 709
核对表:编程工具 724
第31章 布局与风格 729
核对表:布局 773
第32章 自说明代码 777
核对表:自说明代码 780
核对表:好的注释技术 816
第33章 个人性格 819
第34章 软件工艺的话题 837
图34-1 程序可划分为多个抽象层。好的设计使你可以把很多时间集中在较高层,而忽略较低层的细节 846
第35章 何处有更多信息 855
参考文献 863
索引 885