《代码大全》笔记 —— 模块四:语句

直线型代码的组织

  • 主要基于顺序依赖关系对直线型代码进行组织。
  • 可使用良好的子程序名称、参数列表、注释以及(如果代码至关重要的话)用内务处理变量使依赖关系变得明显。
  • 如果代码之间没有顺序依赖关系,就设法使相关语句尽可能放在一起。

组织代码,使依赖关系显而易见。

链式编程的方式使得代码依赖顺序关系显而易见。

用断言或错误处理代码来检查依赖关系。(断言可以保证各种需要保证入参的情况)

使用条件语句

  • 对于简单 if-else 语句,要注意 if 和 else 子句的顺序,尤其是它们在处理大量错误的时候。确保正常情况清晰可见。
  • 对于 if-then-else 链和 case 语句,选一个最有利于可读性的顺序。
  • 捕捉错误时,要用 case 语句中的 default 子句或在 if-then-else 链中使用最后一个 else。
  • 所有控制结构的创建方式都不一样。要为每部分代码选择最合适的。

if 语句

先写代码的正常路径;再写不寻常的情况。

基于对相等性的测试正确地分支。

将正常情况放在 if 而非 else 之后。(因决策而执行的代码,尽可能放在决策附近。阅读性更好。)

检查 if 和 else 子句是否颠倒。

用布尔函数调用简化复杂测试。(条件过于复杂,则需要提取为函数,方便阅读。)

最常见的情况放在最前面。

确保覆盖所有情况。

case 语句

在 C++ 和 Java 这两种语言中,避免直通到 case 语句的结尾。(一定要带上 Break)

控制循环

  • 循环笔记复杂。保持简单有助于别人看懂你的代码。
  • 保持循环简单的技巧包括避免使用怪异的循环、减少嵌套、提供清晰的入口和出口、将内务处理代码集中在一个地方。
  • 循环索引很容易被滥用。要清楚地命名并限定单一用途。
  • 像清楚循环结构,确定在每种情况下都能正常运行,而且在所有可能的情况下都能终止。

选择循环类型

避免一种计算在两个地方同时存在。

将所有退出条件都放在一个地方。

使用注释来澄清。特殊情况注释注明。

带退出条件的循环是一种单进单出的结构化控制结构,是首选的循环控制方式。

控制循环

  • 尽量减少影响循环的因素的数量。将循环内部当作一个子程序来处理,尽量把控制留在循环外部。
  • 只从一个位置进入循环。
  • 将初始化代码直接放在循环之前。
  • while(true) 用于无限循环。避免伪无限循环。
  • 如果有可能,就首选 for 循环。
  • 若 while 循环更合适,就不要使用 for 循环。
  • 避免空循环。空循环比较难以理解。
  • 只在循环开头或末尾进行循环内务处理。(内部变量的更改尽量在循环开始或结尾)
  • 每个循环只执行一个功能。只要没有性能问题,就不要统一。
  • 确保循环会终止。
  • 使循环结束条件显而易见。
  • 乱动 for 循环索引,试图以这种方式来终止循环。这是不可以的。(避免中途改变内部索引变量,引起非正常变化。)
  • 避免依赖于循环索引终值的代码。(在循环之后使用循环索引的值,并不是一个值得推荐的好做法。内部变量不应外部使用,可以有更有意义目的的变量。)
  • 考虑使用安全计数器。避免进入死循环。
  • 警惕其中散布大量 break 的循环。大量 break 则需要进行收敛。
  • 使用有意义的变量名来改善嵌套循环的可读性。
  • 使用有意义的名称来避免循环索引串扰。(多层循环使用更有意义的名称。)
  • 将循环索引变量的作用域限制在循环内部。
  • 限制循环的长度。太长循环则改为子函数。

不常见的控制结构

  • 多个返回点(多个 return)可以提高子程序的可读性和可维护性,并有助于防止深度嵌套逻辑。尽管如此,还是要谨慎使用。
  • 递归为一小部分问题提供了优雅的解决方案。但使用也须谨慎。
  • 少数情况下,goto 是编写可读性和可维护性良好的代码的最佳方式。这种情况极为罕见。不到万不得已的时候,不要用 goto 语句。

子程序中的多个返回点

使用防卫子句(提前返回或退出)来简化复杂的错误处理。(正常 if 主干是主逻辑,有时异常则需反向逻辑,使得阅读性更好。

尽量减少每个子程序中的返回语句的数量。(分叉越少越好,面向测试编程的思想也是其一。)

递归

选择性地使用递归。

确保递归可以停止。

使用安全计数器来防止无限递归。

将递归限制在一个子程序中。

留意栈以防溢出。

goto 语句

try-finally 的优点在于,它比 goto 语句更简单,而且用不到 goto 语句。它还避免了深度嵌套的 if-then-else 结构。

goto 的使用原则总结如下:

  • 若编程语句不直接支持结构化控制结构,就用 goto 来模拟。模拟需要准确,不要滥用 goto 额外带来的灵活性。
  • 若有等同的内置结构,就不要用 goto。
  • 选择用 goto 来提高效率时,就实际度量一下性能。
  • 除非是在模拟结构化结构,否则每个子程序都应当只能有一个 goto 标签,goto 语句通过标签来实现代码之间的无条件跳转。
  • 除非是在模拟结构化结构,否则应当限制 goto 只能向前,不要向后。
  • 确保所有 goto 标签都要用到。未用到则一定要删除。
  • 确保 goto 不会产生不可抵达的代码。
  • 如果你是管理人员,要记得有大局观,不值得为区区一个 goto 浪费自己太多时间。

表驱动法

表驱动法是这样一种允许在表中查询信息,不必用逻辑语句(if 和 case)来查询。(使用数组来解 if、case 等。)

  • 表提供了复杂逻辑和继承结构的一种替代方案。如果发现自己对某个应用程序的逻辑或继承树感到困惑,不妨好好想一想是否可以通过查询表来进行简化。
  • 如果使用表,一项关键的考虑因素就是决定如何访问表。可以采用直接访问、索引访问或阶梯访问的方式。
  • 使用表时,另一个关键考虑因素是决定要把哪些内容放入表中。

直接访问表

枚举也是一种表驱动法。

使用一个经过深思熟虑的查询表。

索引访问表

阶梯访问表

  • 注意端点:确保覆盖了每个阶梯区间的上界。
  • 考虑使用二分搜索而不是顺序搜索。
  • 考虑使用索引访问而不是阶梯技术。

常规控制问题

  • 使用布尔表达式简单易懂将非常有助于提升代码的质量。
  • 深层嵌套导致子程序变得难以理解。幸好,这种情况可以相对容易地加以避免。
  • 结构化编程是一种简单且仍然使用的理念:可以通过组合顺序、选择和迭代这三种基础结构来构建出任何程序。
  • 要想最小化复杂度,写出高质量的代码是关键。

核心点:关注对象尽可能少;决策点尽可能少。

布尔表达式

  • 用新的布尔变量将复杂的测试拆开的部分测试
  • 将复杂的表达式转移到布尔函数中
  • 使用决策表替代复杂条件
  • 在 if 语句中,将否定转为肯定并在 if 子句和 else 子句中翻转代码(取反一般情况下都不太易于阅读)
  • 使用一个简单的计数技巧使圆括号对称(复杂一点的表达式,用括号来帮助理解。)
  • 按数轴顺序来写数值表达式:思路是使元素从左向右排序,从最小到最大。按照顺序编写,方便思考,不遗漏。
  • 禁止隐式比较逻辑转换。

空语句

尽量不有空语句存在。

驾驭深层嵌套

嵌套不要超过三或四层。

复杂代码是一个信号,表明你对程序不够了解,因而不能把它变得更简单。深层嵌套是一个警告信号,表明需要把复杂的代码分解成子程序或重新设计代码中复杂的部分。

编程基础:结构化编程

结构化编程的核心思想很简单,即程序应该只使用单进单出的控制结构。

结构化编程的核心论点是,任何控制流程都可以运用顺序、选择和迭代这三种结构来创建。

控制结构和复杂度

编程复杂度的一个衡量标准是为理解程序而必须同时关注多少心智对象。(很多极限编程都是对于心智的考验。)

最多 10 个决策点并不是一个绝对的限制。将决策点数量作为一种警告标志,用它来表明程序可能需要重新设计。