命名的原则
1. “小处诚实非小事“
有个词叫做”以小见大“。以建筑作喻,宏大建筑中最细小的部分,比如关不紧的门、未铺平的地板,甚至时凌乱的桌面,都会将整个大局的魅力毁灭殆尽,这就是整洁代码之所系。
2. 有意义的命名
选个好名字,省下来的时间比花掉的多。一旦发现有更好的名字,就换掉旧的名字。
2.1 名副其实
变量、函数和类的名字应该告诉读者:它为什么存在、它做什么事、它应该怎么用。如果名称需要注释来补充,那就不算是名副其实。
举例:
int d; // 消逝的时间,以日计
上例中,变量d只能暗示这是一个表示day-天数的变量,但没有“消逝”的含义。我们应该明确指明计量对象和计量单位的名称:
int elapesdTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;
再举一个例子:
std::list<vector<int>> *getThem()
{auto *list1 = new std::list<vector<int>>;for (auto &x : theList){if (x[0] == 4)list1->push_back(x);}return list1;
}
上例中getThem、x[0] == 4
、theList等,都比较模糊。可以改成这样:
list<vector<int>> *getFlaggedCells()
{auto *flaggedCells = new list<vector<int>>;for (auto &cell : gameBoard){if (cell[STATUS_VALUE] == FLAGGED)flaggedCells->push_back(cell);}return flaggedCells;
}
还可以更进一步,不用vector<int>来表示单元格,而是用一个对象来表示,并做好封装:
list<Cell> *getFlaggedCells()
{auto *flaggedCells = new list<Cell>;for (auto &cell : gameBoard){if (cell.isFlagged())flaggedCells->push_back(cell);}return flaggedCells;
}
与第一个版本比起来,这里明显更容易理解了。
2.2 避免误导
程序员必须避免留下掩藏代码本意的错误线索。
- 避免使用特定平台专有名称。如hp、aix、sco。
- 避免物理数据结构名称与变量名不同的情况。例如AccountList,其内部是用list存储的吗?
- 避免使用不同之处较小的名称。
- 避免l、o这种外观有歧义的名称。
2.3 做有意义的区分
对于编译器或者解释器来说,只要名称中有一个字符的不同就不会出错,但是对于读者来说,我们不仅需要名称不同,还要对名称做出有意义的区分。
举例来说,对于a1, a2, a3,…这样的区分方式,任何人不结合上下文的情况下看了都会一头雾水。还有类似getActiveChannel, getActiveChannels, getActiveChannelInfo这样的命名方式,也不容易看出它们的区别。
另外,我们也要尝试抛弃变量命中没有意义的部分,例如NameString,不如改为Name,除非Name还可以不用String来表示。
2.4 使用读的出来的名称
读的出来的名称一方面便于程序员间的交流,另一方面也利用了人类大脑的语言功能,便于理解和记忆。
比较下面两个例子:
class DtaRcrd102
{
private:Date genymdhms;Date modymdhms;const string pszqint = "102";
};
class Customer
{
private:Date generationTimestamp;Date modificationTimestamp;const string recordId = "102";
};
2.5 使用可搜索的名称
可搜索性还是在强调名称要具有区分度。所以坚持这个原则:长名称胜于短名称(除非短名称已足够精确),搜得到的名称胜于用自造编码代写的名称。
单字母名称仅适用于短方法中的本地变量。名称长短应与其作用域大小相对应。
2.6 避免使用编码
尽量避免将对象的类型和作用域编码进名称中,因为这会增加不必要的负担。现代的编译器和静态分析器能够帮助我们检查对象的类型。
看一下有哪些反例:
- 匈牙利标记法(Hungarian Notation, HN)
- 成员前缀m_
有一个例外是对接口和实现的编码,例如对于抽象工厂模式,可以将接口类命名为ShapeFactory,而将具体类命名为ShapeFactoryImpl。
2.7 避免思维映射
不应当让读者在脑中把你的名称翻译为他们熟知的名称。这种问题经常出现在选择是使用问题领域术语还是解决方案领域术语时。
我们应该尽量让名称明确,而不是自以为是的认为一种名称与某个对象之间存在一种映射关系。例如以r代表url。最终目的是编写让他人能轻易理解的代码。
2.8 类名
类名和对象名应该是名词或者名字短语,如Customer, WikiPage, Account和AddressParser。避免使用Manager, Processor, Data, Info这种缺少实际意义的词。类名不应当是动词。
2.9 方法名
方法名应当是动词或动词短语,如postPayment、deletePage或save。
重载构造器时,使用描述了参数的静态工厂方法名。例如,
Complex fulcrumPoint = Complex.FromRealNumber(23.0);
通常好于
Complex fulcrumPoint = new Complex(23.0);
2.10 别扮可爱
名字不要太耍宝,毕竟别人不一定有你的幽默感。宁可明确,毋为好玩。
2.11 每个概念对应一个词
给每个抽象概念一个词,并且一以贯之。例如不要混用fetch、retrieve和get。在同一堆代码中有controller,manager和driver,它们的区别是什么?
2.12 别用双关语
避免将同一单词用于不同目的。如果同一术语用于不同的概念,那么它基本上就是双关语了。
我们要遵循“一词一义”规则,例如对于add方法,如果它出现在多个类中,我们要保证这些add方法的参数和返回值在语义上等价。具体地说,如果在一个类中。add方法通过增加或连接两个现存值来获得新值并返回。现在要写一个新类,需要一个方法能够把参数添加道容器中,那么就不能把这个方法叫做add,因为这样做的话,add就变成了双关语。可以使用insert、append之类地词。
2.13 使用解决方案领域名称
我们的读者都是程序员,所以如果可以尽量使用计算机科学中的术语、算法名、模式名和数学术语。例如,对于熟悉访问者(VISITOR)模式的程序员来说,AccountVisitor富有意义。
2.14 使用源自所涉问题领域的名称
如果真的不能用程序员所熟悉的术语来命名,那就采用问题涉及领域的名称吧。至少,负责维护代码的程序员知道该去请教谁。
优秀的程序员和设计师,其工作之一就是分离解决方案领域和问题领域的概念。与所涉问题更为贴近的代码,应当采用源自问题领域的名称。
2.15 添加有意义的语境
单单一个名称能够说明的信息还是太过有限。我们需要有良好命名的类、函数或者名称空间来放置名称,给读者提供语境。如果没有这么做,那么给名称添加前缀就是最后一招了。
那么如何添加语境呢?举例来说,可以从一个复杂的函数体中拆分出多个步骤,然后给这些子函数命上合适的名字,从而提供更多语境,告诉读者整个函数的逻辑。
不要添加没用的语境
只要现有的代码已经足够清楚,就没必要拆分出不同的子函数。只要短名称足够清楚,就比长名称好。