良好名称具有两个属性:精度和一致性。让我们从精度开始。名称最常见的问题是名称太笼统或含糊不清。结果,读者很难说出这个名字指的是什么。读者可能会认为该名称所指的是与现实不符的事物,如上面的代码错误所示。考虑以下方法声明:
/**
* Returns the total number of indexlets this object is managing.
*/
int IndexletManager::getCount() {...}
术语“计数”太笼统了:计数什么?如果有人看到此方法的调用,除非他们阅读了它的文档,否则他们不太可能知道它的作用。像 getActiveIndexlets 或 numIndexlets 这样的更精确的名称会更好:使用这些名称之一,读者可能无需查看其文档就能猜测该方法返回的内容。
以下是来自其他学生项目的一些名称不够精确的示例:
- 建立 GUI 文本编辑器的项目使用名称 x 和 y 来引用字符在文件中的位置。这些名称太笼统了。他们可能意味着很多事情;例如,它们也可能代表屏幕上字符的坐标(以像素为单位)。单独看到名称 x 的人不太可能会认为它是指字符在一行文本中的位置。如果使用诸如 charIndex 和 lineIndex 之类的名称来反映代码实现的特定抽象,该代码将更加清晰。
- 另一个编辑器项目包含以下代码:
// Blink state: true when cursor visible. private boolean blinkStatus = true;
- 名称 blinkStatus 无法传达足够的信息。“状态”一词对于布尔值来说太含糊了:它不提供关于真值或假值含义的任何线索。“闪烁”一词也含糊不清,因为它并不表示闪烁的内容。以下替代方法更好:
// Controls cursor blinking: true means the cursor is visible, // false means the cursor is not displayed. private boolean cursorVisible = true;
- 名称 cursorVisible 传达了更多信息;例如,它允许读者猜测一个真值的含义(通常,布尔变量的名称应始终为谓词)。名称中不再包含“ blink”一词,因此,如果读者想知道为什么光标不总是可见,则必须查阅文档。此信息不太重要。
- 名称 blinkStatus 无法传达足够的信息。“状态”一词对于布尔值来说太含糊了:它不提供关于真值或假值含义的任何线索。“闪烁”一词也含糊不清,因为它并不表示闪烁的内容。以下替代方法更好:
- 一个实施共识协议的项目包含以下代码:
// Value representing that the server has not voted (yet) for // anyone for the current election term. private static final String VOTED_FOR_SENTINEL_VALUE = "null";
- 此值的名称表示它是特殊的,但没有说明特殊含义是什么。使用更具体的名称(例如 NOT_YET_VOTED)会更好。
- 在没有返回值的方法中使用了名为 result 的变量。这个名字有多个问题。首先,它会产生误导性的印象,即它将作为方法的返回值。其次,除了它是一些计算值外,它实际上不提供有关其实际持有内容的任何信息。该名称应提供有关实际结果是什么的信息,例如 mergedLine 或 totalChars。在实际上确实具有返回值的方法中,使用名称结果是合理的。该名称仍然有点通用,但是读者可以查看方法文档以了解其含义,这有助于知道该值最终将成为返回值。
如果变量或方法的名称足够广泛,可以引用许多不同的事物,那么它不会向开发人员传达太多信息,因此底层实体很可能会被滥用。
像所有规则一样,有关选择精确名称的规则也有一些例外。例如,只要循环仅跨越几行代码,就可以将通用名称(如 i 和 j)用作循环迭代变量。如果您可以看到一个变量的整个用法范围,那么该变量的含义在代码中就很明显了,因此您不需要长名称。例如,考虑以下代码:
for (i = 0; i < numLines; i++) {
...
}
从这段代码中很明显,i 正被用来迭代某个实体中的每一行。如果循环太长,以至于您无法一次看到全部内容,或者如果很难从代码中找出迭代变量的含义,那么应该使用更具描述性的名称。
名称也可能太具体,例如在此声明中删除一个文本范围的方法:
void delete(Range selection) {...}
参数名称的选择过于具体,因为它建议始终在用户界面中选择要删除的文本。但是,可以在任意范围的文本(无论是否选中)上调用此方法。因此,参数名称应更通用,例如范围。
如果您发现很难为精确,直观且时间不长的特定变量命名,那么这是一个危险信号。这表明该变量可能没有明确的定义或目的。发生这种情况时,请考虑其他因素。例如,也许您正在尝试使用单个变量来表示几件事;如果是这样,将表示形式分成多个变量可能会导致每个变量的定义更简单。选择好名字的过程可以通过识别弱点来改善您的设计。
如果很难为创建基础对象清晰图像的变量或方法找到简单的名称,则表明基础对象可能没有简洁的设计。
Good names have two properties: precision and consistency. Let’s start with precision. The most common problem with names is that they are too generic or vague; as a result, it’s hard for readers to tell what the name refers to; the reader may assume that the name refers to something different from reality, as in the block bug above. Consider the following method declaration:
/**
* Returns the total number of indexlets this object is managing.
*/
int IndexletManager::getCount() {...}
The term “count” is too generic: count of what? If someone sees an invocation of this method, they are unlikely to know what it does unless they read its documentation. A more precise name like getActiveIndexlets or numIndexlets would be better: with one of these names, readers will probably be able to guess what the method returns without having to look at its documentation.
Here are some other examples of names that aren’t precise enough, taken from various student projects:
- A project building a GUI text editor used the names x and y to refer to the position of a character in the file. These names are too generic. They could mean many things; for example, they might also represent the coordinates (in pixels) of a character on the screen. Someone seeing the name x in isolation is unlikely to think that it refers to the position of a character within a line of text. The code would be clearer if it used names such as charIndex and lineIndex, which reflect the specific abstractions that the code implements.
- Another editor project contained the following code:
The name blinkStatus doesn’t convey enough information. The word “status” is too vague for a boolean value: it gives no clue about what a true or false value means. The word “blink” is also vague, since it doesn’t indicate what is blinking. The following alternative is better:// Blink state: true when cursor visible. private boolean blinkStatus = true;
The name cursorVisible conveys more information; for example, it allows readers to guess what a true value means (as a general rule, names of boolean variables should always be predicates). The word “blink” is no longer in the name, so readers will have to consult the documentation if they want to know why the cursor isn’t always visible; this information is less important.// Controls cursor blinking: true means the cursor is visible, // false means the cursor is not displayed. private boolean cursorVisible = true;
- A project implementing a consensus protocol contained the following code:
The name for this value indicates that it’s special but it doesn’t say what the special meaning is. A more specific name such as NOT_YET_VOTED would be better.// Value representing that the server has not voted (yet) for // anyone for the current election term. private static final String VOTED_FOR_SENTINEL_VALUE = "null";
- A variable named result was used in a method with no return value. This name has multiple problems. First, it creates the misleading impression that it will be the return value of the method. Second, it provides essentially no information about what it actually holds, except that it is some computed value. The name should provide information about what the result actually is, such as mergedLine or totalChars. In methods that do actually have return values, then using the name result is reasonable. This name is still a bit generic, but readers can look at the method documentation to see its meaning, and it’s helpful to know that the value will eventually become the return value.
If a variable or method name is broad enough to refer to many different things, then it doesn’t convey much information to the developer and the underlying entity is more likely to be misused.
Like all rules, the rule about choosing precise names has a few exceptions. For example, it’s fine to use generic names like i and j as loop iteration variables, as long as the loops only span a few lines of code. If you can see the entire range of usage of a variable, then the meaning of the variable will probably be obvious from the code so you don’t need a long name. For example, consider the following code:
for (i = 0; i < numLines; i++) {
...
}
It’s clear from this code that i is being used to iterate over each of the lines in some entity. If the loop gets so long that you can’t see it all at once, or if the meaning of the iteration variable is harder to figure out from the code, then a more descriptive name is in order.
It’s also possible for a name to be too specific, such as in this declaration for a method that deletes a range of text:
void delete(Range selection) {...}
The argument name selection is too specific, since it suggests that the text being deleted is always selected in the user interface. However, this method can be invoked on any range of text, selected or not. Thus, the argument name should be more generic, such as range.
If you find it difficult to come up with a name for a particular variable that is precise, intuitive, and not too long, this is a red flag. It suggests that the variable may not have a clear definition or purpose. When this happens, consider alternative factorings. For example, perhaps you are trying to use a single variable to represent several things; if so, separating the representation into multiple variables may result in a simpler definition for each variable. The process of choosing good names can improve your design by identifying weaknesses.
If it’s hard to find a simple name for a variable or method that creates a clear image of the underlying object, that’s a hint that the underlying object may not have a clean design.