本章重点讲述的是代码如何自说明以及如何注释。

编码时要把维护你程序的人想象成知道你住址且有暴力倾向的精神病人。 —— 佚名

32.1 外部文档

外部结构文档通常比编码的层次更高,但比问题定义、需求和架构活动层次低一些。常见的外部文档形式有单位开发文件夹或者详细设计文档。

单元开发文件夹

“单元开发文件夹(unit-development folder,UDF)”又称“软件开发文件夹(software-development folder,SDF)”,是一种非正式文档,其中包含了供开发者在编程期间使用的记录。“单元”没有严格的定义,一般指类,也可指程序包或者组件。UDF主要用途是提供在其他地方没有说明的设计决策踪迹。很多项目都有标准来指定UDF最少该有什么内容,例如相关需求的复本、某个单元实现的顶层设计的组成部分、开发标准的复本、当前代码清单,以及单元开发者的设计记录。尽管用户有时会要求软件开发者提供项目的UDF,但它通常仅限于内部使用。

详细设计文档

详细设计文档是低层次的设计文档,描述在类层或子程序层的设计决定,曾考虑过的其他方案,以及采用所选方案的理由。有时候这些信息含在某个正式文档中,在这种情况下,通常的考虑是将详细设计和构建过程分开。有时它主要包含收集于UDF的开发者记录;在另一些时候(经常如此),设计细节说明就存在于代码本身中。

32.2 编程风格作文档

在代码层文档中起主要作用的因素并非注释,而是好的编程风格。编程风格包括良好的程序结构、直率易懂的方法、有意义的变量名和子程序名、具名常量(而非文字量)、清晰的布局,以及最低复杂度的控制流及数据结构。

这里有一糟糕风格的代码段:
微信截图_20210508182042.png
微信截图_20210508182131.png
这段代码的文档作用之所以差劲不在于没有注释,而在于缺乏好的编程风格。变量名没有传达任何有价值的信息,布局粗糙。只要改进了编程风格,就能使其含义清楚得多。

微信截图_20210508182338.png
这两段代码的差异与注释毫不相干——它们都没有注释。然而第二段代码更容易理解,接近了易读性的最高水平:自说明代码。这些代码通过好的编程风格承担文档说明的很大一部分任务。对于精心编写的代码而言,注释不过是美丽衣裳上的小饰物而已。

自说明代码核对表

微信截图_20210508183129.png
微信截图_20210508183212.png

32.3 注释或不注释

注释写得糟糕很容易,写得出色就难了。注释不好只会帮倒忙。

然后原文中吧啦吧啦一堆讨论。。。。。。。
大致是有说不需要注释的,因为注释浪费时间,不如代码清晰。 有的人认为需要注释。

最后讨论的结果是:

应该提倡注释,但不能滥用注释。同时还要审查代码,这样每个人都将了解有益的注释该是什么样的。如果理解别人的代码时遇到麻烦,要告知如何改进。

32.4 高效注释之关键

注释种类

注释的作用可以分为6种

1、重复代码

重复性注释只是用不同的文字把代码的工作又描述了一次。除了增加阅读量,毫无意义。

2、解释代码

解释性注释通常用于解释复杂、有巧、敏感的代码块。在这些场合它们能派上用场,但通常正是因为代码含混不清,才体现出这类注释的价值。如果代码过于复杂而需要解释,最好是改进代码,而不是添加注释。使代码清晰后再用概述性注释或者意图性注释。

3、代码标记

标记性注释并非有意留在代码中,它提醒开发者某处的工作未做完。有的开发者在程序中打上句法不对的标记(例如*),以便编译器就会标记之,并提醒还有更多工作要做。还有的开发者在注释中放入特定的字符串,既不影响编译又能搜索到它们。并且需要规范标记型注释。

4、概述代码 (概述性注释)

概述性注释是这么做的:将若干代码行的意思以一两句话说出来。这种注释比重复性注释强多了,因为读者读注释能比读代码更快。概述性注释对于要修改你代码的其他人来说尤其有用。

5、代码意图说明 (目的性注释)

目的性注释用来指明一段代码的意图,它指出要解决的问题,而非解决的方法。
比如:
—get current employee information
就是一条目的性注释,然而

—update employeeRecord object
则是有关解决方案的概述性注释。

目的性注释和概述性注释并没有很明显的界限,其差异也无关紧要。

6、代码无法传达的信息

某些信息不能通过代码来表达,但又必须包含在源代码中。这种注释包括版权声明、保密要求、版本号等杂项信息:与代码设计有关的注意事项;相关要求或者架构文件的索引;联机参考链接;优化注记等等。

对于完工的代码,只允许有的三种注释类型:代码无法表述的信息、目的性注释和概述性注释。

高效注释

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

1、采用不会打断或抑制修改的注释风格

任何太具想象力的注释风格维护起来都会很烦人。
微信截图_20210510101913.png
这段注释看上去很好看,头尾很明显。但是修改起来就比较麻烦。

2、用伪代码编程法减少注释时间

写代码前以注释先勾勒出代码,完成代码的时候,注释也就写好了。同时可以获得所有设计方面的好处。

3、将注释集成到你的开发风格中

把注释集成到开发风格的相反做法是,项目临结束才开始写注释,这么做缺点太多了。写注释成了专门的任务,工作量看起来会比点滴积累的方式更大。事后再写注释将花费较长时间,因为你还得回忆或思考某行代码干什么,不能像边编程边注释那样把正在考虑的内容写下来。

由于容易忘掉设计中的假设或细节问题,写出来的注释也不够准确反对注释的最常见理由是“当精力集中在代码上时,不该分心去写注释。”对此的适当回答是,如果你编程时集中精力这么难,以至于写写注释就会打断思路,那么你就该先用伪代码写,之后再把伪码转换成注释。

要求注意力太过集中就是个警告信号。如果你的设计很难进行编程,请在你担忧注释或代码之前先着手去简化设计。若你用伪代码理清自己的思路,编码就容易了,注释也自然而然就做好了。

4、性能不能逃避注释的好借口

解决方案并不是不用注释,而是区分开发版与发布版代码。通常由专门对代码运行一次的工具完成,由它将注释全部清除,作为构建的一部分。

最佳注释量

IBM的研究发现,约每十条语句有一个注释,这样的密度时程序清晰度最高。比这小的注释密度会让代码难以理解;更多的注释也会导致同样效果。

32.5 注释技术

注释单行

关于单行注释,有如下几条原则。

1、不要随意添加无关注释。(感觉所有注释都应该如此)

2、行尾注释及其问题。

1)不要对单行代码做行尾注释。
行尾注释的系统性问题是,难以对每行代码都写一句有意义的注释。多数行尾注释只是重复本行代码。

2)不要对多行代码做行尾注释。
如果使用行尾注释说明多行代码,其格式将难以说明它究竟应用于哪几行代码。
微信截图_20210515182227.png
即便注释的内容是对的,但是位置也合适。

3)行尾注释用于数据声明。

4)用行尾注释标记快尾。
用行尾注释标记块尾用行尾注释标记大的代码块的结束位置是很合适的。例如对于 while循环或者if语句。本章后面会对此详细叙述。除了这几种特殊情况外,行尾注释有着概念性的问题,而且常常用于很复杂
的代码,带来编排和维护困难。总之,最好不要用行尾注释。

注释代码段

1、注释应表达代码的意图。

微信截图_20210515183015.png
读了代码后,可以得知循环在查找“$”(注释要是这样写就会有帮助意义)。这个注释只不过重复了代码内容,并没有明了代码想干什么。下面的注释则好一些。
// find ‘$’ in inputString

这个注释之所以好一点,是因为它指明了循环的目标是找到“$”。不过还是没有给出再深层次的信息——为啥要找“$”,然后更进一步的注释:
// find the command-word terminator($)

该注释实际包含了代码中没有的信息,即“$”是命令字的结束符。这样的注释才有帮助。

另一种在意图层写注释的办法,就是想象如果将这段代码换成同样功能的子程序,会怎样命名它。
如果各段代码目的单一,这件事就不难做到。
前面示例代码段里的注释是说明问题的很好实例。“FindcommandWordTerminator(”就是相当好的子程序名。其他诸如“ FindsInInputstring()”、“ CheckEachCharacterInInputStrUntilADollarsignIsFoundorAllCharactersHaveBeenChecked()””(检查输入字符串中的每个字符直到找到“$”或者所有字符都检查过)显然都不像样(甚至没法用)。
应该把它以未经缩写或简写地描述出来,就像为子程序命名一样。这种说明就是注释,很可能描述的正是意图。

2、代码本身应尽力做好说明。

代码自身总是应首要检查的说明记录。前面例子中,字符“$”应以具名常量代替,变量取名也应提供正在干什么的更多提示。
如果想再提高可读性,请添加一个存放搜索结果的变量。这样能将循环变量与循环结果清楚地区分开。下面是采用好的风格和注释重写这段代码的结果:
微信截图_20210515183958.png

改进代码的另一步骤是创建名为 FinacommandwordTerminator() 之类的子程序,将上面示例中的代码放进去。
用注释说明思路是有用,但随着软件的进展,注释比子程序名更容易变得不准确。

3、注释代码段时应注重“为何做(why)”而不是“怎么做(how)”。

说明“怎么做”的注释一般停留在编程语言的层次,而不是为了说明问题。尽力阐述“怎么做”的注释不大可能会告诉我们操作的意图,而且指明“怎么做”的注释是冗余的。
下面注释给出的信息有哪些是代码没有提到的呢?
微信截图_20210515184405.png

4、用注释为后面的内容做铺垫。

5、让每个注释都有用。

6、说明非常规做法。

假如代码本身有些含义未表达明显,就把这些含义放在注释里。若是用了拐弯抹角的技巧而非直截了当的办法,以求获得性能的提升,请用注释指出直接方法该怎样做,并量化出你那个花招到底能提高多少性能。

7、别用缩略语(类似btn,desc这种)。

8、错误或者语言环境独特点都要加注释。

9、不要注释投机取巧的代码,应重写之。

注释数据声明

1、注释数值单位。

声明数值变量时应当说明其单位,如长度单位是英寸、英尺、米还是千米等。
替代做法是将单位写进变量名中。

2、对数值的允许范围给出注释。

如果变量值有一个期望范围,就应该注释说明此范围。

3、注释编码含义。

如果语言支持枚举类型,请用枚举来表达编码含义;如果不支持枚举,请用具名常量表示。

4、注释对输入数据限制。

5、注释“位标志”。

若变量用作位域,就应该对每个数据位的含义做出说明。

6、将与变量有关的注释通过变量名关联起来。

想要提高一致性修改的可能性,就应将变量名放在注释中,方便ctrl+f。

7、注释全局数据。

如果使用全局数据,要加以注释。注释要指出该数据的目的,为何必须是全局数据。

注释控制结构

控制结构前面的位置通常是放置注释的一个天然空间。如果是一个 if 或者 case 语句,你可以提供为什么要进行判断的理由,以及执行结果的一个总结。如果是一个循环结构,你可以指出这个循环的目的是什么。

1、应在每个 if、case、循环或者代码段前面加上注释。

2、应在每个控制结构后加上注释。

用以注释说明结局如何。在长循环或者嵌套循环结尾处的注释尤其有用。

3、将循环结束处的注释看成是代码太复杂的征兆。

注释子程序

1、注释应靠近其说明的代码。

程序不该有庞大的注释头,原因之一是注释头太大会把注释距其说明的对象较远,不便于随代码一起维护,从而注释和代码开始不一致,然后某个时候注释就失去了存在的价值。相反,遵循靠近原尽可能地拉近注释和其说明的代码之间的距离就容易得到维护,从而持久发挥其作用。

2、在子程序上部用一两句话说明之。

如果不能用一两句话说清楚,有必要考虑你到底想让子程序做什么。要是不便做出简短的说明,就意味着设计还嫌不足。请回到设计提纲再试试。原则上所有子程序都应附概述性说明。除非已有很简单的 Get/Set访问器子程序。

3、在声明参数处注释这些参数。

说明输入输出变量的最简便方式,就是把注释放在各参数声明的后面。

4、利用代码说明工具。

5、分清输入和输出数据。

知道哪个数据作为输入,哪个作为输出是很有用的。visual basic中区分相对容易,因为输出数据以 By Ref关键字打头,输入数据则以Byva1关键字打头。若所用语言不支持自动区分,就将区分说明放入注释。
微信截图_20210515195414.png

6、注释接口假设。

注释接口假设可被看作其他注释原则的一部分。假如你做了变量状态的假设—合法和不合法的值、排过序的数组、初始化过的或只包含正确值的数据成员,等等——应在子程序前面或数据声明的地方说明。这种注释理
应表现在所有的子程序中。

7、说明子程序的全局效果。

如果子程序会修改全局数据,那么要加以进行注释说明, 要确切描述它对全局数据做了什么。

8、记录所用算法来源。

注释类、文件和程序

类、文件和程序的共同特征是它们都包含多个子程序。文件或类应包含彼此相关的一组子程序;程序包含了所有的子程序。各情况下注释的任务就是要对文件、类或程序内容提供有意义的概括说明。

1、标注类的一般原则。

对每个类使用注释块来说明类的一般属性。

说明该类的设计方法
有的信息通过编码细节“逆向工程”是难以获得的,如果概述性注释能提供这些信息,将会特别有用。在其中应说明类的设计思路、总体设计方法、曾考虑过后又放弃的其他方案,等等。

说明局限性、用法假设等
类似于子程序,应确保说明类设计的局限性,还应说明输入输出数据的假设、出错处理的责任划分、全局效果和算法来源,等等。

注释类接口
其他程序员不看类实现就能明白类的用法吗?如果不是,类的封装就有严重问题。类接口应该包含别人使用该类时所需的全部信息。

不要在类接口处说明实现细节
封装最重要的一个规则是仅公开那些“需要知道”的信息。若拿不定是否应公开某项信息,就隐藏之。因此,类接口文件应当只有类的用法信息,而不应有需要实现或维护类内操作的信息。

2、注释文件的一般原则。

说明各文件的意图和内容

将姓名、电子邮件和电话号码放在注释块中

包含版本控制标记

请在注释块中包含法律通告

将文件命名为与其内容相关的名字

3、程序注释以书本为范例

将代码看作是一类特殊的书籍,并据此进行编排,就能够实现其目标。按照书籍的风范,代码及其说明应组织成若干组件,就像一本书的内容分为几个部分,可以帮助程序员获得对程序的高层次认识。

“序”为一组常见于文件头的注释,起到介绍程序的作用。它和书的序功能相通,提供有关程序的概况。

“目录”给出顶层文件、类和子程序(即“章”)。它们可以是清单形式,就如同书本列出章节那样,也可以结构图的形式表示。

“节”则是子程序内的各单位,例如子程序声明、数据声明和可执行语句。

“交叉引用”是代码的“参阅。。。”映射,其中含有行号。

32.6 IEEE标准

微信截图_20210515200914.png

微信截图_20210515200924.png

要点总结

该不该注释是个需要认真对待的问题。差劲的注释只会浪费时间,帮倒忙;好的注释才有价值。

源代码应当含有程序大部分的关键信息。只要程序依然在用,源代码比其他资料都能保持更新,故而将重要信息融入代码是很有用处的。

好代码本身就是最好的说明。如果代码太糟,需要大量注释,应先试着改进代码,直至无须过多注释为止。

注释应说出代码无法说出的东西——例如概述或用意等信息。

有的注释风格需要许多重复性劳动,应舍弃之,改用易于维护的注释风格。