对整本书中提到的优化手段做了一系列总结
17.1 注释
17.1.1 不恰当的信息
- 注释只应该描述对应代码和设计的技术信息,不要说废话
17.1.2 废弃的注释
- 废弃的注释会远离它们曾经描述的代码,更有可能这些代码早已经不存在了
17.1.3 冗余注释
- 注释应该描述代码本身无法描述清楚的信息,不要重复的描述
17.1.4 糟糕的注释
- 如果要编写一条注释,就应该花时间保证写出最好的注释
17.1.5 注释掉的代码
- 如果你现在认为这段代码没用了,请直接删掉
- 版本管理工具会帮你记住这段代码,所以完全没必要让它留下,这只会带来混淆
17.2 环境
17.2.1 需要多步才能实现的构建
- 构建系统应该是单步的小操作
- 应该能够用单个命令导出系统,并用单个指令进行构建
17.2.2 需要多步才能做到的测试
- 应该能够用单个操作就可以运行全部的单元测试
17.3 函数
17.3.1 过多的参数
- 函数的参数数量应该尽可能少
- 三个以上的参数应该尽可能避免
- 这个我目前还很难做到
17.3.2 输出参数
- 输出参数是一种违法直觉的行为
- 参数应该只用来输入,而不是输出
- 如果函数一定要改变输入参数的值,用来给后续步骤使用,那么应该改为用返回值
17.3.3 标识参数
- 一个函数应该只做一件事,而布尔值参数的传入就表明这个函数做了不止一件事
17.3.4 死函数
- 永远不被调用的方法就应该被丢弃
17.4 一般性问题
17.4.1 一个源文件中存在多种语言
- 理想的源文件应该只包括一种语言
17.4.2 明显的行为未被实现
- 如果明显应该实现的行为没有被实现,那么函数的名称就相当于没有真实表达代码的含义
17.4.3 不正确的边界行为
- 不要依赖直觉,追踪每种边界条件,并编写对应的测试
17.4.4 忽视安全
- 很多程序员习惯性的忽视 IDE 给出的 WARNING 警告,这非常危险,应该尽其所能消灭所有文件中的警告信息
17.4.5 重复
- 依据 DRY 原则( Don’t Repeat Yourself )
17.4.6 在错误的抽象层级上的代码
- 将较低层级的概念放在派生类中,将较高层级的概念放在基类中
- 良好的软件设计要求分离位于不同层级的概念,并将它们放在不同的容器中
17.4.7 基类依赖于派生类
- 基类需要依赖派生类,这显然是本末倒置的
17.4.8 信息过多
- 设计良好的接口提供的函数不会需要依赖其他函数
17.4.9 死代码
- 在设计改变时,死代码不会随之更新,虽然它还是能通过编写,但这段代码对程序本身已经没有用处,应该毫不犹豫的删除
17.4.10 垂直分隔
- 变量和函数定义的位置应该离它们被使用的位置尽可能的近一些
17.4.11 前后不一致
- 遵循 最小惊异原则 ,做到从一而终
17.4.12 混淆视听
- 没有用到的变量,从不调用的函数,没有信息量的注释,都应该被删除
17.4.13 人为耦合
- 不互相依赖的东西不该产生耦合
- 两个没有直接逻辑关联的模块不应该存在耦合
17.4.14 特性依恋
- 类的方法只应该对自身类中的变量和函数感兴趣
17.4.15 选择算子参数
- 使用多个函数,比向单个函数中传递布尔参数来选择函数行为要更清晰
17.4.16 晦涩的意图
- 代码要尽可能具有表达力
17.4.17 位置错误的权责
- 软件开发者做出的最重要决定之一:这段代码应该放在哪
17.4.18 不恰当的静态方法
- 通常应该倾向于选用非静态方法
- 如果的确要使用静态方法,请确保这个方法后期不会有多态的需求
17.4.19 使用解释性变量
- 只要把计算过程打散成一系列良好命名的中间值,就算是不透明的模块也会变得透明
- 但在这之前需要考虑为什么这段计算过程会不透明
17.4.20 函数名称应该表达其行为
- 如果必须查看函数的实现才知道其用途,说明应该换一个更好的函数名
17.4.21 理解算法
- 编程是一种探险,在开始使用某个工具实现功能之前,先尽可能了解这个工具能够做到的事情
- 不要盲目的用自己当前掌握的知识直接解决问题,请先阅读文档
- 通常工具中的实现会比你自己想的要更好
17.4.22 把逻辑依赖改为物理依赖
- 如果某个模块依赖于另一个模块,这种依赖应该是物理的,而不是逻辑的
17.4.23 用多态替代 if / else 或 switch / case
- 对于给定的选择类型,不应该有多个 switch 语句
- 如果有多个,请使用多态
17.4.24 遵循标准约定
- 每个团队都应该遵循基于通用行业规范的编码标准
17.4.25 用命名常量代替魔术数
- 魔术数不仅仅是说数字,这是泛指任何不能自我描述的符号
17.4.26 准确
- 表达意思含糊或不准确的代码,要么优化,要么删除
17.4.27 结构甚于约定
- 使用强制的结构太替换脆弱的命名约定
17.4.28 封装条件
- 应该把条件判断语句中的复杂表达式,抽离成方法
17.4.29 避免否定性条件
- 否定式比肯定式难理解,尽可能将条件表示为肯定形式
17.4.30 函数只该做一件事
- 遵循单一权责原则
17.4.31 掩蔽时序耦合
- 译者又开始文绉绉的让我受不了了,什么掩蔽?直接说隐蔽不好吗???
- 如果代码存在调用顺序上的耦合,就应该明确表示,放着出现因为调用顺序出错而导致的报错
17.4.32 别随意
- 构建代码需要理由,而理由应该和代码结构相吻合
17.4.33 封装边界条件
- 边界条件难以追踪,所以应该把处理边界条件的代码集中到一起,不要散落在代码中
17.4.34 函数应该只在一个抽象层级上
- 当根据抽象界限对函数进行拆解时,经常会发现隐蔽在之前的结构中的新抽象界限
17.4.35 在较高层级放置可配置数据
- 可配置的数据通常具有通用性,放置在较高层级则可以保证其通用性
17.4.36 避免传递浏览
- 模块本身不要了解太多协作者的信息,而应该让协作者提供给模块所需要的全部服务
17.5 Java
17.5.1 通过使用通配符避免过长的导入清单
- 现代 IDE ,例如 IDEA ,已经可以做到自动优化导入清单
17.5.2 不要继承常量
- 别利用继承来使用常量,例如将常量放置在基类中
- 应该直接使用常量类实现静态导入
17.5.3 常量 vs 枚举
- 如果你感觉某个地方需要一个常量,请使用枚举
17.6 名称
17.6.1 采用描述性名称
- 事物的意义随着软件的演化而发生变化,所以要经常对变量、方法的名称进行重新评估
17.6.2 名称应与抽象层级相符
- 类和方法的名称应该能体现当前的抽象层级
17.6.3 尽可能使用标准命名法
- 使用名称基于现存的约定,会更容易理解
- 与项目直接相关的名称越多,后来接收代码的人就可以更快的理解
17.6.4 无歧义的名称
- 选用不会混淆方法或变量的名称
17.6.5 为较大作用范围选用较长名称
- 名称的长度应该和其作用范围成正比
17.6.6 避免编码
- 不应该在名称中包含类型或作用范围信息,例如 m_score
17.6.7 名称应该说明副作用
- 名称应该说明函数、变量或类的一切信息
17.7 测试
17.7.1 测试不足
- 一套测试应该测到所有可能失败的情况
- 只要还有没被测试过的条件,或是还有没被验证过的计算,就说明测试不足
17.7.2 使用覆盖率工具
- 覆盖率工具可以直观的汇报测试不足的模块、类和方法
17.7.3 别略过小测试
- 小测试易于编写,其文档上的价值高于编写成本
17.7.4 被忽略的测试就是对不确定事物的疑问
- 如果因为需求不明确而不能确定某个行为细节,可以用注释掉的测试来表达疑问
17.7.5 测试边界条件
- 特别注意测试边界条件,算法的中间部分正确,但边界判断错误的情况很常见
17.7.6 全面测试相近的缺陷
- 缺陷趋向于扎堆,在某个方法中发现一个缺陷时,最好全面测试那个方法
17.7.7 测试失败的模式有启发性
- 通过找到测试用例失败的模式来诊断问题所在
17.7.8 测试覆盖率的模式有启发性
- 查看没有被测试过的代码,通常可以发现其他测试代码失败的线索
17.7.9 测试应该快速
- 慢速的测试是不会被运行的测试
17.8
- 专业性和技艺来自于驱动规程的价值观