软件中到处都是名字。文件、目录、变量、函数等。因为我们起了很多名字,所以我们最好做得更好。
使用表达意图的名称
很容易说名称揭示了意图。选择好的名称需要时间,但它节省的时间比花费的多。所以要小心选择你的名称,并在找到更好的名称时更改它们。
一个变量、函数或类的名称,应该回答所有大问题。它应该告诉你它为什么存在,它做了什么,以及如何使用它。如果一个名称需要注释,那么这个名称就没有揭示它的意图。
不揭示意图 | 揭示意图 |
---|---|
int d; // elapsed time in days |
int elapsedTimeInDays |
选择揭示意图的名称可以使理解和更改代码变得更加容易。例如:
public List<int[]> getThem() {
List<int[]> list1 = new ArrayList<int[]>();
for (int[] x : theList)
if (x[0] == 4)
list1.add(x);
return list1;
}
这段代码很简单,但产生了许多问题:
theList
的内容是什么?- 列表中
x[0]
的项目有什么意义? - 为什么我们比较
x[0]
和4
? - 我该如何使用返回的列表?
这些问题的答案不在代码示例中,但本可以有。假设我们正在开发一个扫雷游戏。我们可以按以下方式重构之前的代码:
public List<int[]> getFlaggedCells() {
List<int[]> flaggedCells = new ArrayList<int[]>();
for (int[] cell : gameBoard)
if (cell[STATUS_VALUE] == FLAGGED)
flaggedCells.add(cell);
return flaggedCells;
}
现在我们知道了以下信息:
theList
代表gameBoard
x[0]
代表棋盘上的一个单元格,4
代表一个被标记的单元格- 返回的列表代表
flaggedCells
注意,代码的简单性没有改变。它仍然有完全相同数量的操作符和常量,嵌套级别完全相同。但代码变得更加明确。
我们可以通过编写一个简单的单元格类而不是使用 ints
数组来改进代码。它可以包括一个表达意图的函数(称之为isFlagged
)来隐藏魔法数字。这导致函数的新功能。
public List<Cell> getFlaggedCells() {
List<Cell> flaggedCells = new ArrayList<Cell>();
for (Cell cell : gameBoard)
if (cell.isFlagged())
flaggedCells.add(cell);
return flaggedCells;
}
避免误导
程序员必须避免留下模糊代码含义的虚假线索。我们应该避免使用那些根深蒂固的含义与我们预期含义不同的词。
不要将一组账户称为 accountList
,除非它实际上是一个 List
。对于程序员来说,List
这个词有特定的含义。如果容器实际上不是列表,它可能会导致错误的结论。所以 accountGroup
或 bunchOfAccounts
或者简单地 accounts
会更好。
注意使用名称的小差异。在 XYZControllerForEfficientHandlingOfStrings
的一个模块中,发现与稍远一点的地方的 XYZControllerForEfficientStorageOfStrings
之间的微妙差异需要多长时间?这些词的形状非常相似。
做出有意义的区分
程序员在编写代码时,仅仅为了满足编译器或解释器的要求,会给自己制造问题。例如,因为你不能使用相同的名称来引用同一作用域中的两个不同的事物,你可能会被迫以任意方式更改一个名称。有时这是通过拼写错误来完成的,导致了一个令人惊讶的情况,即纠正拼写错误会导致无法编译。例如,你创建了变量 klass
,因为 class
这个名字已经被其他东西使用了。
在下一个函数中,参数没有提供信息,a1
和 a2
没有提供作者意图的线索。
public static void copyChars(char a1[], char a2[]) {
for (int i = 0; i < a1.length; i++) {
a2[i] = a1[i];
}
}
我们可以通过选择更明确的参数名称来改进代码:
public static void copyChars(char source[], char destination[]) {
for (int i = 0; i < source.length; i++) {
destination[i] = source[i];
}
}
噪音词是另一种无意义的区别。想象一下,你有一个名为Product的类。如果你有另一个叫做 ProductInfo
或ProductData
的类,你就使名称不同而没有使它们有任何不同的含义。Info 和 Data 是像 a,an 和 the 一样的不明确的噪音词。
噪音词是多余的。变量这个词永远不应该出现在变量名中。表这个词永远不应该出现在表名中。
使用可读名称
想象一下你有变量 genymdhms
(生成日期、年份、月份、日期、小时、分钟和秒),想象一下在一次对话中你需要谈论这个变量,称之为 “gen why emm dee aich emm ess”。你可以考虑将这样的类转换为:
class DtaRcrd102 {
private Date genymdhms;
private Date modymdhms;
private final String pszqint = "102";
/* ... */
};
到
class Customer {
private Date generationTimestamp;
private Date modificationTimestamp;
private final String recordId = "102";
/* ... */
};
使用可搜索名称
单字母名称和数字常数有一个特别的问题,那就是它们不容易在整个文本中定位。
避免编码
我们已经有足够的编码要处理,没有必要再增加更多的负担。将类型或作用域信息编码到名称中只会增加额外的解读负担。编码的名称很少是可读的,而且容易拼写错误。一个例子是使用匈牙利表示法或使用成员前缀。
接口和实现
这些有时是编码的特殊情况。例如,假设你正在为创建形状构建一个ABSTRACT FACTORY。这个工厂将是一个接口,并将由一个具体类实现。你应该如何命名它们?IShapeFactory
和 ShapeFactory
?最好让接口保持未修饰的状态。我不想让用户知道我给他们的是一个接口。我只想让他们知道它是一个 ShapeFactory
。所以如果我必须对接口或实现进行编码,我选择实现。将其称为 ShapeFactoryImp
,甚至可怕的 CShapeFactory
,比对接口进行编码更好。
避免心理映射
读者不应该不得不将你的名称在心理上翻译成他们已经知道的其他名称。
一个聪明的程序员和一个专业的程序员之间的一个区别是,专业人士明白清晰是至高无上的。专业人士利用他们的力量做好事,编写其他人可以理解的代码。
类名
类和对象应该有名词或名词短语名称,如 Customer
、WikiPage
、Account
和 AddressParser
。避免在类名中使用 Manager
、Processor
、Data
或 Info
这样的词。类名不应该是动词。
方法名
方法应该有动词或动词短语名称,如 postPayment
、deletePage
或 save
。访问器、变异器和谓词应该根据它们的值命名,并根据javabean标准加上get、set和is前缀。
当构造函数被重载时,使用描述参数的静态工厂方法。例如:
Complex fulcrumPoint = Complex.FromRealNumber(23.0);
通常比
Complex fulcrumPoint = new Complex(23.0);
更好。考虑通过使相应的构造函数私有来强制使用它们。
不要过于俏皮
俏皮名称 | 干净名称 |
---|---|
holyHandGranade |
deleteItems |
whack |
kill |
eatMyShorts |
abort |
每个概念选择一个词
为一个抽象概念选择一个词,并坚持下去。例如,拥有fetch、retrieve和get作为不同类等效方法的名称是令人困惑的。
不要双关
避免使用同一个词用于两个目的。使用同一个术语表示两个不同的想法本质上是一种双关语。
例子:在一个类中使用add
来创建一个新值,通过添加或连接两个现有值,而在另一个类中使用add
将一个简单参数放入集合中,更好的选择是使用像insert
或append
这样的名称。
使用解决方案领域名称
记住阅读你代码的人将是程序员。所以,尽管使用计算机科学(CS)术语、算法名称、模式名称、数学术语等。
使用问题领域名称
当你所做的事情没有“程序员 术语”时,使用问题领域的名称。至少维护你的代码的程序员可以问领域专家它的含义。
添加有意义的上下文
有一些名称本身是有意义的 - 大多数不是。相反,你需要通过将它们包含在良好命名的类、函数或命名空间中,为你的读者提供上下文。当别无选择时,可能需要作为最后的手段前缀名称。
变量如:firstName
、lastName
、street
、city
、state
。放在一起,它们清楚地形成了一个地址,但是,如果你在方法中单独看到变量state,你会怎么做?你可以使用前缀添加上下文,如:addrState
,至少读者会理解这个变量是更大结构的一部分。当然,更好的解决方案是创建一个名为Address
的类,这样即使是编译器也知道变量属于一个更大的概念。
不要添加不必要的上下文
在一个名为 “Gas Station Deluxe” 的假想应用程序中,给每个类前缀 GSD 是一个糟糕的主意。例如:GSDAccountAddress
较短的名称通常比较长的名称更好,只要含义是清晰的。所以在名称中不要添加超过必要的上下文。