代码大全读书笔记
第1章 欢迎进入软件构建的世界
1.1 什么是软件构建
详细设计, 编码, 调试, 集成, 开发者测试 构建产生源代码
1.2 软件构建为何如此重要
1.3 如何阅读本书
第2章 用隐喻来更充分地理解软件开发
2.1 隐喻的重要性
建模
2.2 如何使用软件隐喻
2.3 常见的软件隐喻
第3章 三思而后行:前期准备
3.1 前期准备的重要性
3.2 辨明你所从事的软件的类型
在序列式开发法和迭代式开发法之间做出选择
以下更适合序列化开发 - 需求稳定 - 设计直截了当 - 团队对此领域熟悉 - 风险小 - 需要长期可预测性 - 后期改变需求代价昂贵
以下更适合迭代方法 - 需求不稳定 - 设计复杂 - 团队对领域不熟悉 - 风险多 - 不需要长期可预测性 - 后期改变需求代价低
3.3 问题定义的先决条件
从客户的角度出发
3.4 需求的先决条件
针对功能需求 - 是否详细定义了输入输出 - 是否定义输出格式 - 是否定义软硬件接口 - 是否定义通信接口协议 - 是否列出用户所有需求 - 是否定于每个任务需要和得到的数据
3.5 架构的先决条件
架构的典型组成部分
- 程序组织
- 主要的类
- 数据设计
- 业务规则
- ui
- 资源管理
- 安全性
- 性能
- 可伸缩性
- 互用性
- 国际化
- 输入输出
- 错误处理
- 容错性
- 可行性
- 过度工程
3.6 花费在前期准备上的时间长度
第4章 关键的“构建”决策
4.1 选择编程语言
4.2 编程约定
4.3 你在技术浪潮中的位置
4.4 选择主要的构建实践方法
第5章 软件构建中的设计
5.1 设计中的挑战
- 设计是一个险恶的问题 险恶(wicked)问题: 只有通过解决或者部分解决才能被明确的问题
- 设计是个了无章法的过程(即使它能得出清爽的成果)
- 设计就是确定取舍和调整顺序的过程
- 设计受到诸多限制
- 设计是不确定的
- 设计是一个启发式过程(探索性)
- 设计是自然而然形成的
5.2 关键的设计概念
软件的首要技术任务:管理复杂度
理想的设计特征
设计的层次
- 软件系统
- 子系统或包
- 类
- 子程序
- 子程序内部
5.3 设计构造块:启发式方法
关于设计启发的总结
- 寻找现实世界的对象
- 形成一致的抽象画
- 封装实现细节
- 在可能得情况下继承
- 信息隐藏
- 找到容易改变的区域
- 松散耦合
- 通用设计模式
5.4 设计实践
5.5 对流行的设计方法的评论
第6章 可以工作的类
6.1 类的基础:抽象数据类型 abstract data types ADTs
6.2 良好的类接口
少暴露信息! 可访问性尽可能低
每个接口都由一个可编程(programmatic)的部分和 一个语义(sernantic)部分组成。可编程的部分由接又中的数据 类 型 和 其 他 属 性 构 成 , 编 译 器 能 强 制 性 地 要 求 它 们 ( 在 编 译 时 检 查 错 误 )。 而 语 义 部分则由“本接口将会被怎样使用” 的假定组成,而这些是无法通过编译器来强 制实施的。
Scott Meyers在《EffectiveC++》一书第2版中的第34条里介绍了可以解决 这个问题的一个惯用技法(Meyers 1998)。他建议你把类的接又与类的实现隔离开,并在类的声明中包含一个指针,让该指针指向类的实现,但不能包含任何其 他实现细节。
class Employee{ |
6.3 有关设计和实现的问题
包含(“有一个……”的关系)
警惕有超过约7个数据成员的类 研究表明,人们在做其他事情时能记住的 离 散 项 目 的 个 数 是 7 士 2 (Miller 1956)。
继承(“是一个……”关系)
什么时候继承
- 如果多个类共享数据而非行为,应该创建这些类可以包含的共用对象。
- 如果多个类共享行为而非数据,应该让它们从共同的基类继承而来,并在基 类里定义共用的子程序。
- 如果多个类既共享数据也共享行为,应该让它们从一 个共同的基类继承而 来,并在基类里定义共用的数据和子程序。
- 当你想由基类控制接口时,使用继承;当你想自己控制接口时,使用包含。
6.4 创建类的原因
- 为现实世界中的对象建模
- 为抽象的对象建模
- 降低复杂度
- 隔离复杂度
- 隐藏实现细节
- 限制变动的影响范围
- 建立中心控制点
- 代码重用
6.5 与具体编程语言相关的问题
virtual
6.6 超越类:包
第7章 高质量的子程序
7.1 创建子程序的正当理由
- 降低复杂度, 特别是嵌套层次很深时
- 引入中间抽象, 用子程序说明代码用意
- 避免代码重复
- 隐藏执行顺序
- 简化 bool 判断
似乎过于简单而没必要写成子程序的操作
可以增加可读性
7.2 在子程序层上设计
内聚性
最好的内聚性: 功能内聚性
7.3 好的子程序名字
好的子程序名字应该描述所有的输出结果和副作用
使用准确的动词和动宾短语
不要有数字(1, 2, 3)
9~15 字符
对仗
add/remove
begin/end
create/destroy
first/last
get/put
get/set
increment/decrement
insert/delete
lock/unlock
min/max
next/previous
old/new
open/close
show/hide
source/target
start/stop
up/down
7.4 子程序可以写多长
50 rows
7.5 如何使用子程序参数
- 按照输入-修改-输出排列参数
- 不同子程序的类似参数要顺序一致
- 状态变量放在最后
- 不要把子程序参数用作 工作变量
- 断言判断参数假定
- 参数<=7 个
7.6 使用函数时要特别考虑的问题
不要返回局部数据的引用和指针
7.7 宏子程序和内联子程序
第8章 防范式编程
8.1 保护程序免遭无效输入数据的破坏
- 检查来源于外部的数据的值
- 检查子程序输入参数的值
- 决定如何处理错误的输出数据
8.2 断言
- 用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状 况
- 避免把需要执行的代码放到断言中
- 用断言来注解并验证前条件和后条件
8.3 错误处理技术
8.4 异常
8.5 隔离程序以免遭由错误造成的损害
8.6 辅助调试代码
不要自动地把产品版本的限制强加于开发版本之上
尽早引入辅助调试的手段
采用冒进式编程
计划移除调试辅助代码
8.7 确定在产品代码中该保留多少防范式代码
8.8 防范式编程时保持防范
第9章 伪代码编程过程
9.1 创建类和子程序的步骤概述
9.2 伪代码
9.3 通过伪代码编程过程创建子程序
9.4 伪代码编程过程之外的其他方案
第10章 使用变量的一般事项
10.1 数据认知
10.2 轻松掌握变量定义
10.3 变量初始化原则
声明时初始化
靠近时初始化
10.4 作用域
使变量引用局部化
尽可能缩短变量的“存活”时间
10.5 持续性
10.6 绑定时间
10.7 数据类型和控制结构之间的关系
10.8 为变量指定单一用途
第11章 变量名的力量
11.1 选择好变量名的注意事项
最适当的名字长度
10~16
短的变量名用于临时变量
变量名字中的计算值限定词
限定词放在最后
(把计算的量放在名字最后的这条规则也有例外,那就是 Num限定词的位置已经是约定俗成的。Num放在变量名的开始位置代表 一个总数)
变量名字中的常用对仗词
begin/end |
11.2 为特定类型的数据命名
为状态变量命名
少用flag
多用 bool
为临时变量命名
为布尔变量命名
- done
- error
- found
- success/ok
- not
为枚举类型命名
Color_Red
11.3 命名规则的力量
11.4 非正式命名规则
语言无关规则的指导原则
区分变量和子程序
区分类和对象
标识全局变量
标识成员变量
标识类型
标识具名常量
标识枚举
标识只读
语言相关规则的指导原则
混合语言编程的注意事项
11.5 标准前缀
用户自定义类型缩写
比如, ch, doc, scr, wn
语义前缀
c, m, g
11.6 创建具备可读性的短名称
一般的缩写指导原则
去掉非前置元音
语音缩写
不提倡
11.7 应该避免的名称
第12章 基本数据类型
12.1 使用数的普遍规则
magic number: 没有经过解释的数值文字量
预防除 0
12.2 整数
除法, 溢出
12.3 浮点数
避免数量级相差太大的加减
避免等量判断
舍入
12.4 字符和字符串
避免 magic str
国际化
unicode
12.5 布尔变量
用布尔变量说明代码含义
12.6 枚举类型
枚举提高可靠性
检查非法数值
定义第一项和最后一项用于循环边界
将第一个元素预留作为非法值
12.7 命名常量
避免使用文字量
12.8 数组
越界, 边界
用容器替代
12.9 创建你自己的类型(类型别名)
- 减少信息分发(隐藏信息)
- 用于代码说明
- 便于移植
第13章 不常见的数据类型
13.1 结构
用结构体简化参数列表
13.2 指针
从概念上看,每一个指针都包舍两个部分:内存中的某处位置,以及如何解 释该位置中的内容。
- 把指针操作限制在子程序或者类中
- 同时声明和定义
- 分配和释放
- 使用前检查
- 狗牌(tag, 增加荣誉)
- 删除后置空
13.3 全局数据
用访问子程序来取代全局数据
第14章 组织直线型代码
14.1 必须有明确顺序的语句
- 使子程序名能突显依赖关系
- 利用子程序参数明确依赖关系
14.2 顺序无关的语句
把相关的语句组织在一起
第15章 使用条件语句
15.1 if语句
- 先写正常代码路径, 再写不正常
- 正常情况放在 if 后而不是 else 后
- if 后跟一个有意义的语句
15.2 case语句
为case选择最有效的排序
最常执行的放在最前面
不要刻意使用 case, 如果处理复杂请使用 if
第16章 控制循环
16.1 选择循环的种类
score = 0; |
- 把退出条件写在一起
16.2 循环控制
进入循环
初始化代码紧跟循环开始之后
处理好循环体
循环内务放在最后(计数器等)
退出循环
检查端点
off-by-one
使用循环变量
循环应该有多长
同子程序
16.3 轻松创建循环——由内而外
16.4 循环和数组的关系
第17章 不常见的控制结构
17.1 子程序中的多个返回
及时循环避免多层 if 嵌套
17.2 递归
- 安全计数器防止无限递归
17.3 goto
17.4 对不常见控制结构的看法
第18章 表驱动方法
18.1 表驱动方法使用总则
18.2 直接访问表
示例:一个月中的天数(Days-in-Month)
daysPerMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } |
18.3 索引访问表
18.4 阶梯访问表
rangeLimit[] = {50.0, 65.0, 75.0, 90.0, 100.0}; |
18.5 表查询的其他示例
--- 未完待续
第19章 一般控制问题
19.1 布尔表达式
注意短路(惰性求值)
按照数轴的方式写表达式 MIN_ELEMENTS <= i and i <= MAX_ELEMENTS
i < MIN ELEMENTS or MAX_ ELEMENTS < i
19.2 复合语句(块)
19.3 空语句
19.4 驯服危险的深层嵌套
避免使用超过三层嵌套
减少嵌套的方法
重复测试
if (inputStatus == InputStatus_Success) {
// lots of code
if (printerRoutine != NULL) {
// lots of code
}
}
if ((inputstatus == InputStatus_Success) && (printerRoutine != NULL) &&
SetupPage()) {
// lots of code
if (AllocMem(&printData)) {
// lots of code
}
}break
do { // begin break block
if (inputStatus != InputStatus_Success) {
break;
// break out of block
}
// lots of code
if (printerRoutine == NULL) {
break; // break out of block
}
// lots of code
if (!SetupPage()) {
break; // break out of block
}
// lots of code
if (!AllocMem(sprintData)) {
break; // break out of block
}
// lots of code
} while (FALSE);
// end break blockcase
深层嵌套放入子程序
面向对象
TransactionData transactionData;
Transaction* transaction;
while (!TransactionsComplete()) {
// read transaction record and complete transaction
transactionData = ReadTransaction();
transaction = TransactionFactory.Create(transactionData);
transaction->Complete();
delete transaction;
}
19.5 编程基础:结构化编程
结构化编程的核心思想很简单,那就是一个应用程序应该只采用一些单入单 出 的 控 制 结 构 ( 也 称 为 单 一 入 又 、 单 一 出 又 的 控 制 结 构 )。 单 入 单 出 的 控 制 结 构 指 的就是一个代码块,它只能从一个位置开始执行,并且只能结束于一个位置。