读《编程匠艺—编写卓越的代码》:编写代码注释

查尔斯.普雷斯特.斯科特(Charles Prestwich Scott):
评论是自由的,但事实是神圣的

注释可以将优秀的代码和糟糕的代码区分开来,将艰涩难懂的逻辑与清晰友好的算法区分开。但我们也无须过分夸大注释的作用。如果说你的代码是蛋糕,那么你的注释就如同蛋糕上糖衣,它被精巧地涂抹以增加蛋糕的美感和价值,但不能掩饰蛋糕的裂缝和瑕疵。

一、什么是代码注释

  • 从语法角度看,注释是编译器将忽略不计的源代码。
  • 从语义角度看,注释是对其所处位置的代码的注解。
    注释的作用有多种:
    1) 强调某个特殊的问题领域;
    2)在头文件中记录媒介;
    3)描述某个算法,协助维护程序;
    4)分割各个函数以帮组快速浏览整个源文件。
  • 注释是内部的文档化机制

二、注释标记

  • C语言的注/*开头,*/结束,可以跨多行。
  • C++、C99、C#和Java语言增加了以//作为开头的单行注释。
  • 其他语言也提供了类似的块注释和行注释功能,语法可能不同。

三、多少注释是恰当的

小威廉.斯特伦克(William Strunk Jr.):
铿锵有力之文必简洁

  • 把重点放在注释的质量上,而不是数量上;比编写多少注释更重要的是注释的内容。
  • 只在能为代码增加色彩时才编写注释。优秀的演奏家追求用最少的演奏创造出最美的声音
  • 真正好的代码并不需要注释,它们能自我解释。像f()g()这样的函数名才会大声嚷嚷,要求注释说明它们,但是像someGoodExample()这样的函数名根本不需要注释。

四、注释中应该有什么

贺瑞斯(Horace):
写好源头和本源是明智的做法

不好的注释比没有注释更糟糕,它们歪曲事实进而误导读者。

  1. 解释为什么,而不是怎么样
    (a).注释不是用来描述程序是怎样运行的,代码自己是其最权威的描述。
    (b).注释应该描述代码块起什么作用
    /* update WidgetList structure from GibWLRegistry*/这样的注释,不如写/* cache widget information for later*/。注释同一段代码,后者表达代码目的,而前者只告诉你这段代码做了什么。
    (c).注释可用来解释为什么代码要这么写。如有两种可供选择的实现方式,你决定采用其中一种,也许你该使用注释来解释一下,为什么你选择了这种实现方式。

  2. 不要取代或重复代码
    (a).如果注释描述的内容可以由编程语言本身实现,那么就试着用具体语句表达它。
    (b).如果你发现你用大量注释解释某个算法如何运行,请赶快停止,然后考虑是否需要重构代码或算法。
    (c).不要用注释描述变量的用法,重命名变量。
    (d).如果你要文档化一个应当总是成立的条件,也许你该写一个断言

  3. 确保注释有用
    注释的作用是标注和解释代码。注释需要满足:
    (a).记录意想不到的内容。如果代码有一部分是不常见、意想不到的,用注释记录下来;如果有特定问题需要规避,如一个操作系统问题,应该在注释中说明。
    (b).讲真话。不要写不准确的注释。
    (c).清晰明了。不要让注释模棱两可,内容尽量具体;不一定是完整的、语法正确的句子,但必须是可读的;避免缩写。
    (d).只关注当前代码,避免分心。不要记录过去我们怎么做;不要把应当剔除的代码(如旧代码)包含在注释中;避免使用ASCII艺术;不要在代码块结尾添加多余的注释,如在if语句的后括号后注释//end if

每当写完注释后,在代码上下文中回顾一遍这些注释,考虑一下:它们是否都是正确的信息,它们是否简洁,它们是否容易理解

五、实践

看看下面一小段C++代码:

for (int i = 0; i < wlst.sz(); ++i)
k(wlst[i])

很明显,你根本搞不懂这是在干吗。应用合理排版规则和添加注释说明对它进行改进:

// Iterate over all widgets in  the widget list
for (int i = 0; i < wlst.sz(); ++i)
{
        // print out this widget
        k(wlst[i]);
}

现在好多了,起码这段代码的目的清晰了。但它还是不太令人满足。使用恰当的函数名和变量重写代码:

for (int i = 0; i < widgets.size(); ++i)
       printWidget(widgets[i]);

再看代码,发现根本不需要注释,代码本身能自我描述了。

这印证了:

不要文档化差劲的代码——重写这些代码

六、注释的一些细节及其作用

根据你自己的品味,把下面的建议当作指导性的原则:

  • 一致性
    所有的注释应该清晰明了,前后一致。为你的注释选择一种特定的布局方式,始终坚持使用。

  • 清晰的块注释
    将首位标记(如C/C++中/**/)各自放在一行,凸显它们;让块注释左侧多缩进一个字符,让注释显的像一个整体。如

/* 
   * This is 
   * a block
   * comment.
   */

而不是这种

  /* 
This is 
        a block
comment.
  */
  • 缩进的注释
    注释不应该截断代码或打乱逻辑流程。让注释与对应的代码的缩进一样。
    看到下面这样的代码,无疑更费劲:
void strangeCommentStyle()
{
        for (int i = 0; i < JUST_ENOUGH; ++i)
        {
        //This is a meaning comment about the next line.
                  doSomethingMeaningful(i);
   // good comment 
                  doOtherOperation(i);
        }
}     

在不带括号的循环中,不要把注释放在单循环体语句之前,这会导致灾难性后果。

  • 行尾注释
    大多数注释都是放在各自的行上,但有时较短的单行注释可以跟在代码语句后头。这种情况下,在注释与代码之间留白是一种好习惯。例如:
Class Point
{
public:
        ........
private:
        int x;                 //X轴坐标值
        int y;                 //Y轴坐标值
        int z;                 //Z轴坐标值
};

如果行尾注释直接跟在声明后面,代码看起来会显得拥挤,读起来更费劲。

  • 选择一种维护成本较低的风格
    可以把更多时间放在编写代码而不是摆弄注释上。
  • 帮组阅读代码
    a).注释通常在它描述的代码上方,而不是下方。读者一般都是从上向下阅读,这样注释可以让读者为即将读到的内容做好准备。
    b).注释后面紧跟代码,代码后面放一个空白行,接下来是下一个注释代码段。这种代码与空白一起使用的方式,可以将代码分隔成"段落",益于阅读。
  • 分隔板
    注释经常被用作不同代码部分之间的“分隔板”。如在一个若干个类或函数的源文件中,可能有以下注释:
/******************************************
 * class foo implementation
******************************************/

////////////////////////////////////////////////////

我们应当避免大量使用这种"分隔板"类的注释。将代码分组的应该是良好的缩进和结构,而不是生动的ASCII艺术。

  • 标志
    注释还可以用作代码中内嵌的标志。如:
    a).//XXX用来标识棘手的代码或需要重新编写的代码;
    b).//FIXME表示已知已经损坏的代码;
    c).//TODO用来标识缺少一些功能,需要日后返工。

  • 文件头注释
    a).所有源文件都应该以描述其内容的注释块作为开头。这个头注释是一个概述前言,提供了文件的关键信息。
    b).头注释应该包含文件的目的、版本、所有权及版权声明;
    c).头注释不需要把文件中定义的所有函数、类、全局变量等内容罗列,太累赘。

  • 帮组编写程序
    a).一种常见的编码方式是首先用注释构造代码结构,然后在各行注释下面填写代码。这种方式要注意及时删除一些无用的注释和修改一些不恰当的注释。
    b).另一种常见的编码方式是徒手编写代码,再添加注释。可能常常忘记某项工作或不能写出好的注释。更好的方式是边写代码边写注释,实践会告诉你写多少注释。

  • 注释过时
    要明白注释会过时。当你修正、添加或修改任何代码时,请同时修正、添加或修改相应的注释。

八、总结

德尔莫.施瓦茨(Delmore Schwartz):
重要的写作是要讲眼之所见,这样就不必一遍又一遍的口口相传了

写高质量的注释比写高数量的注释更好,然而,最好的是写出高质量的代码——那些无需注释的自文档化代码。

你可能感兴趣的:(读《编程匠艺—编写卓越的代码》:编写代码注释)