现在您知道了不应该做的事情,让我们讨论应该在注释中添加哪些信息。注释通过提供不同详细程度的信息来增强代码。一些注释提供了比代码更低,更详细的信息。这些注释通过阐明代码的确切含 义来增加精度。其他注释提供了比代码更高,更抽象的信息。这些注释提供了直觉,例如代码背后的推理,或者更简单,更抽象的代码思考方式。与代码处于同一级别的注释可能会重复该代码。本节将更详细地讨论下层方法,而下一节将讨论上层方法。
在注释变量声明(例如类实例变量,方法参数和返回值)时,精度最有用。变量声明中的名称和类型通常不是很精确。注释可以填写缺少的详细信息,例如:
- 此变量的单位是什么?
- 边界条件是包容性还是排他性?
- 如果允许使用空值,则意味着什么?
- 如果变量引用了最终必须释放或关闭的资源,那么谁负责释放或关闭该资源?
- 是否存在某些对于变量始终不变的属性(不变量),例如“此列表始终包含至少一个条目”?
通过检查使用该变量的所有代码,可以潜在地了解某些信息。但是,这很耗时且容易出错。声明的注释应清晰,完整,以免不必要。当我说声明的注释应描述代码中不明显的内容时,“代码”是指注释(声明)旁边的代码,而不是“应用程序中的所有代码”。
变量注释最常见的问题是注释太模糊。这是两个不够精确的注释示例:
// Current offset in resp Buffer
uint32_t offset;
// Contains all line-widths inside the document and
// number of appearances.
private TreeMap<Integer, Integer> lineWidths;
在第一个示例中,尚不清楚“当前”的含义。在第二个示例中,尚不清楚 TreeMap 中的键是线宽,值是出现次数。另外,宽度是以像素或字符为单位测量的吗?以下修订后的注释提供了更多详细信息:
// Position in this buffer of the first object that hasn't
// been returned to the client.
uint32_t offset;
// Holds statistics about line lengths of the form <length, count>
// where length is the number of characters in a line (including
// the newline), and count is the number of lines with
// exactly that many characters. If there are no lines with
// a particular length, then there is no entry for that length.
private TreeMap<Integer, Integer> numLinesWithLength;
第二个声明使用一个较长的名称来传达更多信息。它还将“宽度”更改为“长度”,因为该术语更可能使人们认为单位是字符而不是像素。请注意,第二条注释不仅记录了每个条目的详细信息,还记录了缺少条目的含义。
在记录变量时,请考虑名词而不是动词。换句话说,关注变量代表什么,而不是如何操纵变量。考虑以下注释:
/* FOLLOWER VARIABLE: indicator variable that allows the Receiver and the
* PeriodicTasks thread to communicate about whether a heartbeat has been
* received within the follower's election timeout window.
* Toggled to TRUE when a valid heartbeat is received.
* Toggled to FALSE when the election timeout window is reset. */
private boolean receivedValidHeartbeat;
本文档描述了如何通过类中的几段代码来修改变量。如果注释描述变量代表什么而不是镜像代码结构,则注释将更短且更有用:
/* True means that a heartbeat has been received since the last time
* the election timer was reset. Used for communication between the
* Receiver and PeriodicTasks threads. */
private boolean receivedValidHeartbeat;
根据本文档,很容易推断出,当接收到心跳信号时,变量必须设置为 true;而当重置选举计时器时,则必须将变量设置为 false。
Now that you know what not to do, let’s discuss what information you should put in comments. Comments augment the code by providing information at a different level of detail. Some comments provide information at a lower, more detailed, level than the code; these comments add precision by clarifying the exact meaning of the code. Other comments provide information at a higher, more abstract, level than the code; these comments offer intuition, such as the reasoning behind the code, or a simpler and more abstract way of thinking about the code. Comments at the same level as the code are likely to repeat the code. This section discusses the lower-level approach in more detail, and the next section discusses the higher-level approach.
Precision is most useful when commenting variable declarations such as class instance variables, method arguments, and return values. The name and type in a variable declaration are typically not very precise. Comments can fill in missing details such as:
- What are the units for this variable?
- Are the boundary conditions inclusive or exclusive?
- If a null value is permitted, what does it imply?
- If a variable refers to a resource that must eventually be freed or closed, who is responsible for freeing or closing it?
- Are there certain properties that are always true for the variable (invariants), such as “this list always contains at least one entry”?
Some of this information could potentially be figured out by examining all of the code where the variable is used. However, this is time-consuming and error-prone; the declaration’s comment should be clear and complete enough to make this unnecessary. When I say that the comment for a declaration should describe things that aren’t obvious from the code, “the code” refers to the code next to the comment (the declaration), not “all of the code in the application.”
The most common problem with comments for variables is that the comments are too vague. Here are two examples of comments that aren’t precise enough:
// Current offset in resp Buffer
uint32_t offset;
// Contains all line-widths inside the document and
// number of appearances.
private TreeMap<Integer, Integer> lineWidths;
In the first example, it’s not clear what “current” means. In the second example, it’s not clear that the keys in the TreeMap are line widths and values are occurrence counts. Also, are widths measured in pixels or characters? The revised comments below provide additional details:
// Position in this buffer of the first object that hasn't
// been returned to the client.
uint32_t offset;
// Holds statistics about line lengths of the form <length, count>
// where length is the number of characters in a line (including
// the newline), and count is the number of lines with
// exactly that many characters. If there are no lines with
// a particular length, then there is no entry for that length.
private TreeMap<Integer, Integer> numLinesWithLength;
The second declaration uses a longer name that conveys more information. It also changes “width” to “length”, because this term is more likely to make people think that the units are characters rather than pixels. Notice that the second comment documents not only the details of each entry, but also what it means if an entry is missing.
When documenting a variable, think nouns, not verbs. In other words, focus on what the variable represents, not how it is manipulated. Consider the following comment:
/* FOLLOWER VARIABLE: indicator variable that allows the Receiver and the
* PeriodicTasks thread to communicate about whether a heartbeat has been
* received within the follower's election timeout window.
* Toggled to TRUE when a valid heartbeat is received.
* Toggled to FALSE when the election timeout window is reset. */
private boolean receivedValidHeartbeat;
This documentation describes how the variable is modified by several pieces of code in the class. The comment will be both shorter and more useful if it describes what the variable represents rather than mirroring the code structure:
/* True means that a heartbeat has been received since the last time
* the election timer was reset. Used for communication between the
* Receiver and PeriodicTasks threads. */
private boolean receivedValidHeartbeat;
Given this documentation, it’s easy to infer that the variable must be set to true when a heartbeat is received and false when the election timer is reset.