注释虽然写起来很痛苦,但对保证代码可读性至关重要。下面的规则描述了如何注释以及在哪儿注释,当然也要记住:注释固然很重要,但最好的代码本身应该是自文档化。有意义的类型名和变量名,要远胜过要用注释解释的含糊不清的名字。

注释风格

// 或 /* */ 都可以。对于C++开发者,或许更愿意使用块注释/* */,但对于C开发者或许对行注释 //情有独钟,要在如何注释及注释风格上确保统一。

文件注释

在每一个文件开头加入版权公告,然后是文件内容描述。

法律公告和作者信息

每个文件都应该包含以下项,依次是:

  • 版权声明(比如,Copyright 2008 Google Inc. );
  • 许可证。为项目选择合适的许可证版本 (比如, Apache 2.0, BSD, LGPL, GPL);
  • 作者:标识文件的原始作者。

如果你对原始作者的文件做了重大修改,将你的信息添加到作者信息里。这样当其他人对该文件有疑问时可以知道该联系谁。

文件内容

紧接着版权许可和作者信息之后,每个文件都要用注释描述文件内容。

通常, .h 文件要对所声明的类的功能和用法作简单说明。.cc 文件通常包含了更多的实现细节或算法技巧讨论,如果你感觉这些实现细节或算法技巧讨论对于理解 .h 文件有帮助,可以将该注释挪到 .h,并在 .cc 中指出文档在 .h

不要简单的在 .h 和 .cc 间复制注释, 这种偏离了注释的实际意义。

本人常用下面的有结构的文件注释:

/************************************************************************
*
*    Copyright         Copyright 2008 Google Inc.
*    File Name:        文件名(for example:http.h/http.cpp)
*    Description:    描述
*
*    Version:        V1.00
*    Author:            You_Name
*    Create Time:    2011-05-04
*
*************************************************************************/

类注释

每个类的定义都要附带一份注释,描述类的功能和用法。

// Iterates over the contents of a GargantuanTable.  Sample usage:
//    GargantuanTable_Iterator* iter = table->NewIterator();
//    for (iter->Seek("foo"); !iter->done(); iter->Next()) {
//      process(iter->key(), iter->value());
//    }
//    delete iter;
class GargantuanTable_Iterator {
    ...
};

如果你觉得已经在文件顶部详细描述了该类,想直接简单的来上一句 ”完整描述见文件顶部“ 也不打紧,但务必确保有这类注释。

如果类有任何同步前提,文档说明之。 如果该类的实例可被多线程访问,要特别注意文档说明多线程环境下相关的规则和常量使用。

函数注释

函数声明处注释描述函数功能,定义处描述函数实现。

函数声明

注释位于声明之前,对函数功能及用法进行描述。注释使用叙述式 (“Opens the file”) 而非指令式 (“Open the file”);注释只是为了描述函数,而不是命令函数做什么。通常,注释不会描述函数如何工作,那是函数定义部分的事情。

函数声明处注释的内容:

  • 函数的输入输出;
  • 对类成员函数而言:函数调用期间对象是否需要保持引用参数,是否会释放这些参数;
  • 如果函数分配了空间,需要由调用者释放;
  • 参数是否可以为 NULL
  • 是否存在函数使用上的性能隐患;
  • 如果函数是可重入的,其同步前提是什么?

举例如下:

// Returns an iterator for this table.  It is the client's
// responsibility to delete the iterator when it is done with it,
// and it must not use the iterator once the GargantuanTable object
// on which the iterator was created has been deleted.
//
// The iterator is initially positioned at the beginning of the table.
//
// This method is equivalent to:
//    Iterator* iter = table->NewIterator();
//    iter->Seek("");
//    return iter;
// If you are going to immediately seek to another place in the
// returned iterator, it will be faster to use NewIterator()
// and avoid the extra seek.
Iterator* GetIterator() const;

但也要避免罗罗嗦嗦,或做些显而易见的说明。下面的注释就没有必要加上“returns false otherwise”, 因为已经暗含其中了:

// Returns true if the table cannot hold any more entries.
bool IsTableFull();

注释构造/析构函数时,切记读代码的人知道构造/析构函数是干啥的,所以“destroys this object”这样的注释是没有意义的。注明构造函数对参数做了什么(例如,是否取得指针所有权)以及析构函数清理了什么。如果都是些无关紧要的内容,直接省掉注释。析构函数前没有注释是很正常的。

函数定义

  • 每个函数定义时要用注释说明函数功能和实现要点。比如说说你用的编程技巧、实现的大致步骤、或解释如此实现的理由,为什么前半部分要加锁而后半部分不需要。
  • 不要* 从 .h 文件或其他地方的函数声明处直接复制注释。简要重述函数功能是可以的,但注释重点要放在如何实现上。

本人常用下面的有结构的函数注释:

/************************************************************************
*
*    函 数 名: 函数名
*    函数功能: 功能描述
*    输入参数: void
*    输出参数: void
*    返 回 值:  void
*
*    作    者: You_Name
*    创建时间: 2011-05-09
*    其他说明: 无
*    修改信息: 无
*
************************************************************************/

英文版:

/************************************************************************
*
*    function Name:        函数名
*    Description:        描述
*    input parameters:    void
*    output parameters:    void
*    return value:        void
*
*    Author:                You_Name
*    Create Time:        2011-05-04
*    other instructions:    null
*    modify information:    null
*
*************************************************************************/

变量注释

通常变量名本身足以很好说明变量用途,某些情况下,也需要额外的注释说明。

类数据成员

每个类数据成员(也叫实例变量或成员变量)都应该用注释说明用途。如果变量可以接受 NULL 或-1 等警戒值,须加以说明。比如:

private:
    // Keeps track of the total number of entries in the table.
    // Used to ensure we do not go over the limit. -1 means
    // that we don't yet know how many entries the table has.
    int num_total_entries_;

全局变量

和数据成员一样,所有全局变量也要注释说明含义及用途。比如:

// The total number of tests cases that we run through in this regression test.
const int kNumTestCases = 6;

实现注释

对于代码中巧妙的、晦涩的、有趣的、重要的地方加以注释。

代码前注释

巧妙或复杂的代码段前要加注释。比如:

// Divide result by two, taking into account that x
// contains the carry from the add.
for (int i = 0; i < result->size(); i++) {
    x = (x << 8) + (*result)[i];
    (*result)[i] = x >> 1;
    x &= 1;
}

行注释

比较隐晦的地方要在行尾加入注释,在行尾空两格进行注释。比如:

// If we have enough memory, mmap the data portion too.
mmap_budget = max<int64>(0, mmap_budget - index_->length());
if (mmap_budget >= data_size_ && !MmapData(mmap_chunk_bytes, mlock))
    return;  // Error already logged.

注意,这里用了两段注释分别描述这段代码的作用,和提示函数返回时错误已经被记入日志。

如果你需要连续进行多行注释,可以使之对齐获得更好的可读性:

DoSomething();                  // Comment here so the comments line up.
DoSomethingElseThatIsLonger();  // Comment here so there are two spaces between
                                // the code and the comment.
{ // One space before comment when opening a new scope is allowed,
  // thus the comment lines up with the following comments and code.
  DoSomethingElse();  // Two spaces before line comments normally.
}

NULL, true/false, 1, 2, 3...

向函数传入 NULL ,布尔值或整数时,要注释说明含义,或使用常量让代码望文知意。例如,对比:

bool success = CalculateSomething(interesting_value,
                                  10,
                                  false,
                                  NULL);  // What are these arguments??

bool success = CalculateSomething(interesting_value,
                                  10,     // Default base value.
                                  false,  // Not the first time we're calling this.
                                  NULL);  // No callback.

或使用常量或描述性变量:

const int kDefaultBaseValue = 10;
const bool kFirstTimeCalling = false;
Callback *null_callback = NULL;
bool success = CalculateSomething(interesting_value,
                                  kDefaultBaseValue,
                                  kFirstTimeCalling,
                                  null_callback);

不允许

注意永远不要 用自然语言翻译代码作为注释。要假设读代码的人 C++ 水平比你高,即便他/她可能不知道你的用意:

// 现在, 检查 b 数组并确保 i 是否存在,
// 下一个元素是 i+1.
...        // 天哪. 令人崩溃的注释.

 标点、拼写和语法

注意标点、拼写和语法;写的好的注释比差的要易读的多。

注释的通常写法是包含正确大小写和结尾句号的完整语句。短一点的注释(如代码行尾注释)可以随意点,依然要注意风格的一致性。完整的语句可读性更好,也可以说明该注释是完整的,而不是一些不成熟的想法。

虽然被别人指出该用分号时却用了逗号多少有些尴尬,但清晰易读的代码还是很重要的。正确的标点、拼写和语法对此会有所帮助。

TODO 注释

对那些临时的、短期的解决方案,或已经够好但仍不完美的代码使用 TODO 注释。

TODO 注释要使用全大写的字符串 TODO ,在随后的圆括号里写上你的大名、邮件地址、或其它身份标识,冒号是可选的。主要目的是让添加注释的人(也是可以请求提供更多细节的人)可根据规范的 TODO格式进行查找,添加 TODO 注释并不意味着你要自己来修正。

// TODO(kl@gmail.com): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.

如果加 TODO 是为了在“将来某一天做某事”, 可以附上一个非常明确的时间 ,或者一个明确的事项 。

弃用注释

通过弃用注释(DEPRECATED comments)以标记某接口点(interface points)已弃用。

您可以写上包含全大写的 DEPRECATED 的注释,以标记某接口为弃用状态。注释可以放在接口声明前,或者同一行。

在 DEPRECATED 一词后,留下您的名字,邮箱地址以及括号补充。

仅仅标记接口为 DEPRECATED 并不会让大家不约而同地弃用,您还得亲自主动修正调用点(callsites),或是找个帮手。

修正好的代码应该不会再涉及弃用接口点了,着实改用新接口点。如果您不知从何下手,可以找标记弃用注释的当事人一起商量。

转载自文章 C++ 风格指南 - 内容目录 » 7. 注释 并做了适当的修改。

标签: C++, 风格指南, 注释, 规范, 注释规范

添加新评论