软件构建(十四)——代码布局与自说明代码

本文关注的是计算机编程的美学话题——程序源代码的布局和自说明性。尽管这些技巧并不影响执行速度、内存使用量等方面的程序性能,但它却会让你日后理解、检查以及修改代码变得更容易,也使其他人在你缺位的时候更容易阅读、理解和修改你的代码。

需要注意的是,形成良好的代码布局,并让代码具备自说明性,需要始终贯穿项目的生命期,晚了就很难做好了。要想完全实现这些细节规定,在最初建构时就要着手去做。如果你干的是合作项目,更是要在开始编码之前统一大家的风格。

1. 代码布局

1.1 良好布局的目标

  • 准确表现代码的逻辑结构:例如利用缩进、空行等空白来表达逻辑关系
  • 始终如一地表现代码的逻辑结构
  • 改善可读性:如果有一种缩进策略合乎逻辑,但却令程序更难看懂,那么它就毫无用处
  • 经得起修改:好的布局应该在修改某行时不必连带修改其他行的代码

1.2 常用布局技术与风格

  • 空白:包括空格、制表符、换行、空行等等。程序中的空白就像书籍中划分章节、段落、句子一样向读者展示组织主题的思路
    • 分组:确保相关的语句成组放在一起,用空行分隔,就像文章中的一段话一样
    • 空行:将不相关的语句分隔开,就像文章中段与段的分隔一样
    • 缩进:显示程序的逻辑结构
  • 括号:对包含两个以上的项的表达式,应该用括号去澄清
  • 控制结构布局
    • 别让begin和end对两次缩进
    • 单条语句的代码块的格式要前后统一
    • 对于复杂的表达式,将条件分隔放在几行上
    • case语句不要有行尾布局的例外
  • 单条语句布局
    • 各种标点符号两侧使用空格会让逻辑表达式更易读
    • 使用空格让数组引用,子程序参数更易读
    • 单条语句过长时,应分行,并让续行显得更明显,通常在行尾以&&||+-*/等符号为结尾
    • 续行要以标准的空格数缩进
    • 不要将赋值语句按等号对齐
    • 每行仅写一条语句(C++中不要让一行里有多个副作用操作(如++a))
  • 数据声明布局
    • 每行只声明一个变量
    • 合理组织声明顺序:建议按类型分类
  • 注释布局
    • 注释的缩进要与相应代码一致
    • 每行注释用至少一个空行分开
  • 子程序布局
    • 用空行分隔子程序的各部分
    • 将子程序参数按标准缩进,如果参数过多,可以考虑一行放一个参数
  • 类布局
    • 类的内容按以下顺序排布:说明类及其完整用法的头部注释、类数据、构造函数与析构函数、public子程序、protected子程序、private子程序
    • 一个文件应只有一个类,文件的命名应与类名有关
    • 在文件中清晰地分隔各子程序

关于块结构和花括号指定的块边界,不同语言有不一样的规范。书中所提规范放在某些语言也不适用,实际使用应遵循相应的规范,此处不赘述书中所提的布局与风格。

2. 自说明代码

2.1 编程风格的重要性

自说明代码代表了易读性的最高水平,而且一般是由良好的编码风格决定,很大程度与注释无关。对于精心编写的代码而言, 注释不过是美丽衣裳上的小饰物而已。关于自说明代码所体现出的编码风格,请参见核对表:自说明代码

2.2 高效注释

注释的作用分为六种:重复代码(用文字把代码的工作又描述一次,这种注释是废话,应杜绝)、解释代码(通常是因为代码含糊不清,此时应改进代码而不是添加注释)、代码标记(提示工作未做完,待修复等等)、概述代码(用一两句话把若干行代码的意思说出来)、代码意图说明(指出一段代码要解决的问题,而非解决问题的方法)、传达代码无法表述的信息(如版权声明、保密要求、doc注释符号等等)。对于完工的代码,只允许有的三种注释类型:概述代码、代码说明意图和代码无法表达的信息。

注释占用太多时间通常归因于两点:

  1. 注释的风格可能耗时或枯燥乏味。如果这样,请另谋新的风格。需要庞大工作量的注释风格维护起来也会令人头痛。如果注释不便修改,人们就不愿意修改。于是注释就会变得不准确,起到误导作用,反而还不如没有注释
  2. 说明程序干什么的话不好想出来。这通常是你没有真正理解程序的信号。“写注释”所占用的时间其实都用在了更好地理解程序上面,而不管你写不写注释,这些时间注定是得花的

下面是高效注释的几条指导原则。

  • 采用不会打断或抑制修改的注释凤格:不要为了美观而使用大量的.-*符号排版的注释,例如用星号围成一个矩形的注释,每加入一行都得小心维护右边的星号。如果花大量时间增删符号只是为了对齐,你就不是在编程,而是浪费时光
  • 用伪代码编程法减少注释时间
  • 将注释集成到你的开发过程中:不要项目快结束时才开始写注释,也不要把注释当做专门的任务,而应该边写代码边注释。
  • 性能不是逃避注释的好借口:像Javascript的注释会增大网络传输的流量,但这不是理由,解决方案应该是构建不包含注释的发布版代码,区别于开发版代码

2.3 注释技术

  • 注释单行
    • 不要随意添加无关的注释
    • 尽量不要使用行尾注释,因为写行尾难以格式化和维护,也经常是重复说明本行代码的废话。这几种例外情况可以使用行尾注释:用于数据声明,或标记代码块尾部(如end while,end if)
  • 注释代码段
    • 注释应表达代码段的意图,指出代码本身说不清的本意。TIPS:在给代码段写意图注释时,想象这段代码转换成一个子程序,应该命名什么名字,这个名字很有可能就是意图
    • 注释应注重“为何做(why)”而不是“怎么做(how)”
    • 用注释为后面的内容做铺垫:好的注释会让读者只要浏览注释就能了解代码在做什么,去哪找特定的操作
    • 说明非常规做法(trick)
    • 代码错误或语言环境独特点都要加注释
    • 给出不得不故意违背良好编程风格的理由
    • 不要为投机取巧的代码加注释说明,应重写之
  • 注释数据声明
    • 注释数据的单位(包括单位所处的背景环境)和取值范围(包括输入数据的限制)
    • 注释枚举类型各个值的含义,注释bit位标识的含义
    • 注释全局数据:指出该数据的目的,为何必须是全局数据
  • 注释控制结构
    • 应在每个if、case、循环前加上注释,阐明控制结构的意图
    • 在长控制结构或嵌套控制结构结尾处加上注释
    • 如果用到了上一条规则,那么应将控制结构结束处的注释看成是代码太复杂的征兆:最好办法就是重写代码,使之不再复杂到需要费劲注释的程度
  • 注释子程序
    • 在子程序上部用一两句话说明其意图,如果参数很复杂要在声明参数处注释这些参数(如果参数涉及输出,还要特别说明)
    • 利用诸如Javadoc之类的代码说明工具,则可以替代上一条
    • 注释接口假设:例如假设传入的数组是有序的,传入的对象是初始化过的等等,都应该描述清楚
    • 对子程序的局限性作注释:例如指出结果的精确度,子程序碰到麻烦时的默认行为,对程序做什么修改会损坏此子程序等等
    • 说明子程序对全局数据的操作,如果有的话
    • 记录所用算法的来源
  • 注释类和文件
    • 说明类的设计方法,局限性、用法假设,不要在类接口处说明实现细节
    • 在文件开头处用注释块说明文件的意图和内容
    • 在文件注释块中包含作者姓名、电子邮件、电话号码、版本控制标记、法律通告等

参考文献:电子工业出版社《代码大全(第2版)》第31、32章

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器