Skip to content

第五章 该写什么样的注释

关键思想

注释的目的是尽量帮助读者了解的和作者一样多

当你写代码时,你的脑海里会有很多有价值的信息。当其他人读你的代码时,这些信息已经丢失了——他们所见的只是眼前的代码。

什么时候不需要注释

阅读注释会占用阅读真实代码的时间,并且每条注释都会占用屏幕上的空间。那么,它最好是物有所值的。那么如何来分辨什么是好的注释,什么是没有价值的注释呢?

下面代码中所有的注释都是没有价值的:

java
// Hhe class definition for Account
class Account {
  public: 
    // Constructor
    Account();

    // Set the profit member to a new value 
    void SetProfit(double prifit);

    // Return the profit from this Account
    double GetProfit();
};

这些注释设有价值是因为它们并没有提供任何新的信息,也不能帮助读者更好地理解代码。

关键思想

不要为那些从代码本身就能快速推断的事实写注释。

不要为了注释而注释

有些教授要求他们的学生在他们的代码作业中为每个函数都加上注释。结果是,有些程序员会对没有注释的函数有负罪感,以至于他们把函数的名字和参数用句子的形式重写了一遍。

cpp
// Find the Mode in the given subtree,with the given name,using the given depth。
Node* FindkodeInsubtree(Node*subtree,stringname,intdepth);

这种情况属于“没有价值的注释“一类,国数的声明与其注释实际上是一样的。对于这条注释要么删除它,要么改进它。

如果你想要在这里写条注释,它最好也能给出更多重要的细节:

cpp
// Find a Node with the given name or return NULL
// If depth <= 0, only subtree is inspected.
// If depth == N, on1y subtree and N Levels below are inspected.
Node* FindNodeInsubtree(Node+subtree,stringname,intdepth)3

不要给不好的名字加注释——应该把名字改好

记录你的思想

现在你知道了什么不需要注释,下面讨论什么需要注释(但往往没有注释)。很多好的注释仅通过“记录你的想泠“就能得到,也就是那些你在写代码时有过的重要想法。

加入“导演评论”

电影中常有“导演评论“部分,电影制作者在其中给出自己的见解并昆通过讲故事来帮助你理解这部电影是如何制作的。同样,你应该在代码中也加入注释来记录你对代码有价值的见解。

下面是一个例子:

js
// 出乎意料的是、对于这些数据用二叉树比用哈希表快40%
// 哈希运算的代价比左/右比较大得多

这段注释教会读者一些事情,并且防止他们为无谓的优化而浪费时间。

为代码中的瑕疵写注释

代码始终在演进,并且在这过程中肯定会有瑕疵。不要不好意思扭这些琛疲记录下来。

例如,当代码需要改进时:

js
//TODO: 采用更快算法

或者当代码没有完成时:

js
// TODO(dustin): 处理除JPEG以外的图像格式

有几种标记在程序员中很流行:

标记通常的含义
TODO我还没有处理的事情
FIXME已知的无法运行的代码
HACK对一个问题不得不采用的比较粗糙的解决方案
XXX危险!这里有重要的问题

你的团队可能对于是否可以使用及何时使用这些标记有具体的规范。重要的是你应该可以随时把代码将来应该如何改动的想注用注释记录下来。这种注释给读者带来对代码质量和当前状态的宝贵见解,甚至可能会给他们指出如何改进代码的方向。

给常量加注释

当定义常量时,通常在常量背后都有一个关于它是什么或者为什么它是这个值的“故 事“。例如,你可能会在代码中看到如下常量:

python
NUNTHREADS = 8

这一行看上去可能不需要注释,但很可能选择用这个值的程序员知道得比这个要多:

python
NUM_THREADS = 8 # as long as it's >= 2 * num_processors,that's good enough。

现在,读代码的人就有了调整这个值的指南了(比如,设置成1可能就太低了,设置成50又太夸张了)。

或者有时常量的值本身并不重要。达到这种效果的注释也会有用:

cpp
// Impose a reasonable limit - no human can read that much anyway.
const int MAX_RSS_SUBSCRIPTIONS = 1000;

还有这样的情况,它是一个高度精细调整过的值,可能不应该大幅改动。

c
image_quality = 0.72; // users though t0.72 gave the best size quality trade off

在上述所有例子中,你可能不会想到要加注释,但它们的确很有帮助。

有些常量不需要注释,因为它们的名字本身已经很清楚〔例如SECONDS_PERDAY)。但是在我们的经验中,很多常量可以通过加注释得以改进。这不过是匆匆记下你在决定这个常量值时的想法而已。

站在读者的角度

我们在本书中所用的一个通用的技术是想象你的代码对于外人来讲看起来是什么样的,这个人并不像你那样煌悉你的项目。这个技术对于发现什么地方需要注释尤其有用。

意料之中的提问

当别人读你的代码时,有些部分更可能让他们有这样的想法:“什么?为什么会这样?”

你的工作就是要给这些部分加上注释。

例如,看看下面CLear()的定义:

cpp
struct Recorder {
  vector<float> data;
  Clear(){
    vector<float>().smap(data); //Huh ? why not Just aata.clear()
  }
}

大多数 C++ 程序员看到这段代码时都会想:“为什么他不直接用 data.clear()而是与一个空的向量交换?”

实际上只有这样才能强制使向量真正地把内存归还给内存分配器。

这不是一个众所周知的C++细节。起码要加上这样的注释:

cpp
// Force vector to relinquish its memory (lookup "STL swap trick")
vectorkflLoat>(),Swap(data)

公布可能的陷阱

TODO

全局观注释

TODO

总结性注释

TODO

最后的思考——克服心里阻碍

很多程序员不喜欢写注释,因为要写出好的注释感觉好像要花很多功夫。当作者有了这种心态,最好的办法就是现在就开始写。因此下次当你对写注释犹豫不决的时候,就直接把你心里想的写下来就好了,虽然这种注释可能是不成熟的。

总结

注释的目的是帮助读者了解作者在写代码时就已经知道的那些事情。本章介绍了如何发现所有的并不那么明显的信息块并且把它们写下来。

什么地方不需要注释:

  • 能从代码本身迅速地推断的事实。
  • 用来粉饰烂代码的“蹩脚注释”——应该把代码改好

你应该记录下来的想法包括:

  • 对于为什么代码写成这样而不是那样的内在理由
  • 代码中的缺陷使用 TODO 或者 FIXME 这样的标记
  • 常量背后的故事,为什么是这个值

站在读者的立场上思考:

  • 预料到代码中的那些部分会让读者说:“啊?”并且给它们加上注释。
  • 为普通读者意料之外的行为加上注释
  • 在文件、类的级别上使用“全局观”注释来解释所有的部分是如何一起工作的。
  • 用注释来总结代码块,使读者不至于迷失在细节中。