注释可以增加代码的第二种方法是提供直觉。这些注释是在比代码更高的级别上编写的。它们忽略了细节,并帮助读者理解了代码的整体意图和结构。此方法通常用于方法内部的注释以及接口注释。例如,考虑以下代码:
// If there is a LOADING readRpc using the same session
// as PKHash pointed to by assignPos, and the last PKHash
// in that readRPC is smaller than current assigning
// PKHash, then we put assigning PKHash into that readRPC.
int readActiveRpcId = RPC_ID_NOT_ASSIGNED;
for (int i = 0; i < NUM_READ_RPC; i++) {
if (session == readRpc[i].session
&& readRpc[i].status == LOADING
&& readRpc[i].maxPos < assignPos
&& readRpc[i].numHashes < MAX_PKHASHES_PERRPC) {
readActiveRpcId = i;
break;
}
}
该注释太底层和太详细。一方面,它部分重复了代码:“如果有 LOADING readRPC”仅重复测试 readRpc[i].status == LOADING
。另一方面,注释不能解释此代码的总体目的,也不能解释其如何适合包含此代码的方法。如此一来注释不能帮助读者理解代码。
这是一个更好的注释:
// Try to append the current key hash onto an existing
// RPC to the desired server that hasn't been sent yet.
此注释不包含任何详细信息。相反,它在更高级别上描述了代码的整体功能。有了这些高级信息,读者就可以解释代码中几乎发生的所有事情:循环必须遍历所有现有的远程过程调用(RPC);会话测试可能用于查看特定的 RPC 是否发往正确的服务器;LOADING 测试表明 RPC 可以具有多个状态,在某些状态下添加更多的哈希值是不安全的;MAX-PKHASHES_PERRPC 测试表明在单个 RPC 中可以发送多少个哈希值是有限制的。注释中唯一没有解释的是 maxPos 测试。此外,新注释为读者判断代码提供了基础:它可以完成将密钥哈希添加到现有 RPC 所需的一切吗?原始注释并未描述代码的整体意图,因此,读者很难确定代码是否行为正确。
高级别的注释比低级别的注释更难编写,因为您必须以不同的方式考虑代码。问问自己:这段代码要做什么?您能以何种最简单方式来解释代码中的所有内容?这段代码最重要的是什么?
工程师往往非常注重细节。我们喜欢细节,善于管理其中的许多细节;这对于成为一名优秀的工程师至关重要。但是,优秀的软件设计师也可以从细节退后一步,从更高层次考虑系统。这意味着要确定系统的哪些方面最重要,并且能够忽略底层细节,仅根据系统的最基本特征来考虑系统。这是抽象的本质(找到一种思考复杂实体的简单方法),这也是编写高级注释时必须执行的操作。一个好的高层注释表达了一个或几个简单的想法,这些想法提供了一个概念框架,例如“附加到现有的 RPC”。使用该框架,可以很容易地看到特定的代码语句与总体目标之间的关系。
这是另一个代码示例,具有较高层次的注释:
if (numProcessedPKHashes < readRpc[i].numHashes) {
// Some of the key hashes couldn't be looked up in
// this request (either because they aren't stored
// on the server, the server crashed, or there
// wasn't enough space in the response message).
// Mark the unprocessed hashes so they will get
// reassigned to new RPCs.
for (size_t p = removePos; p < insertPos; p++) {
if (activeRpcId[p] == i) {
if (numProcessedPKHashes > 0) {
numProcessedPKHashes--;
} else {
if (p < assignPos)
assignPos = p;
activeRpcId[p] = RPC_ID_NOT_ASSIGNED;
}
}
}
}
此注释有两件事。第二句话提供了代码功能的抽象描述。第一句话是不同的:它以高级的方式解释了为什么执行代码。“如何到达这里”形式的注释对于帮助人们理解代码非常有用。例如,在记录方法时,描述最有可能在什么情况下调用该方法的条件(特别是仅在异常情况下调用该方法的情况)会非常有帮助。
The second way in which comments can augment code is by providing intuition. These comments are written at a higher level than the code. They omit details and help the reader to understand the overall intent and structure of the code. This approach is commonly used for comments inside methods, and for interface comments. For example, consider the following code:
// If there is a LOADING readRpc using the same session
// as PKHash pointed to by assignPos, and the last PKHash
// in that readRPC is smaller than current assigning
// PKHash, then we put assigning PKHash into that readRPC.
int readActiveRpcId = RPC_ID_NOT_ASSIGNED;
for (int i = 0; i < NUM_READ_RPC; i++) {
if (session == readRpc[i].session
&& readRpc[i].status == LOADING
&& readRpc[i].maxPos < assignPos
&& readRpc[i].numHashes < MAX_PKHASHES_PERRPC) {
readActiveRpcId = i;
break;
}
}
The comment is too low-level and detailed. On the one hand, it partially repeats the code: “if there is a LOADING readRPC” just duplicates the test readRpc[i].status == LOADING
. On the other hand, the comment doesn’t explain the overall purpose of this code, or how it fits into the method that contains it. As a result, the comment doesn’t help the reader to understand the code.
Here is a better comment:
This comment doesn’t contain any details; instead, it describes the code’s overall function at a higher level. With this high-level information, a reader can explain almost everything that happens in the code: the loop must be iterating over all the existing remote procedure calls (RPCs); the session test is probably used to see if a particular RPC is destined for the right server; the LOADING test suggests that RPCs can have multiple states, and in some states it isn’t safe to add more hashes; the MAX - PKHASHES_PERRPC test suggests that there is a limit to how many hashes can be sent in a single RPC. The only thing not explained by the comment is the maxPos test. Furthermore, the new comment provides a basis for readers to judge the code: does it do everything that is needed to add the key hash to an existing RPC? The original comment didn’t describe the overall intent of the code, so it’s hard for a reader to decide whether the code is behaving correctly.
Higher-level comments are more difficult to write than lower-level comments because you must think about the code in a different way. Ask yourself: What is this code trying to do? What is the simplest thing you can say that explains everything in the code? What is the most important thing about this code?
Engineers tend to be very detail-oriented. We love details and are good at managing lots of them; this is essential for being a good engineer. But, great software designers can also step back from the details and think about a system at a higher level. This means deciding which aspects of the system are most important, and being able to ignore the low-level details and think about the system only in terms of its most fundamental characteristics. This is the essence of abstraction (finding a simple way to think about a complex entity), and it’s also what you must do when writing higher-level comments. A good higher-level comment expresses one or a few simple ideas that provide a conceptual framework, such as “append to an existing RPC.” Given the framework, it becomes easy to see how specific code statements relate to the overall goal.
Here is another code sample, which has a good higher-level comment:
if (numProcessedPKHashes < readRpc[i].numHashes) {
// Some of the key hashes couldn't be looked up in
// this request (either because they aren't stored
// on the server, the server crashed, or there
// wasn't enough space in the response message).
// Mark the unprocessed hashes so they will get
// reassigned to new RPCs.
for (size_t p = removePos; p < insertPos; p++) {
if (activeRpcId[p] == i) {
if (numProcessedPKHashes > 0) {
numProcessedPKHashes--;
} else {
if (p < assignPos)
assignPos = p;
activeRpcId[p] = RPC_ID_NOT_ASSIGNED;
}
}
}
}
This comment does two things. The second sentence provides an abstract description of what the code does. The first sentence is different: it explains (in high level terms) why the code is executed. Comments of the form “how we get here” are very useful for helping people to understand code. For example, when documenting a method, it can be very helpful to describe the conditions under which the method is most likely to be invoked (especially if the method is only invoked in unusual situations).