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

查尔斯.普雷斯特.斯科特(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):
重要的写作是要讲眼之所见,这样就不必一遍又一遍的口口相传了

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容