Skip to main content

有意义的命名

@Br

阅读《Clean Code》的总结

名副其实

变量、函数或者类的名称应该已经包含了所有的重要信息,它该告诉你,它为什么存在,它做什么事,应该怎么用。如果名称需要用注释来补充,那就不算是名副其实。

int d; // elapsed time in days

名称d什么也没有说明。我们应该选择指明计量对象和计量单位的名称。下列命名才是合适的名称。

int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;

关于短名称

虽然这篇文章大部分讲如何让一个变量的意义明确,其主要方式还是

将上下文融入变量名来加长变量名,提供更多信息

然而,在这里,我想引入一个很有意思的说法

变量名长度和生命周期的长度相匹配

这意味着,for循环的循环变量,因为上下文代码很少,依然允许采用i,j,k一类的短命名

而部分只用一两次的临时变量,随便起个名字也就还可以,比如取单词辅音缩写或者首字母;

这些名称,通常不需要大篇幅阅读代码也可以理解其含义,那么就不需要为了生命周期极短的临时变量仔细取名字了,有些得不偿失

func InitSomeLongModule() {
// 这里就没必要叫做SomeLongModuleConfig了
cfg := SomeLongModule.Config{ a=xx, b=xx}
SomeLongModule.Setup(cfg)
}

避免误导

  • 避免在名称中出现容器类型名
    • 不要用accountList来指代一组账号,除非它真的是List类型,所以使用accountGroup或者bunchOfAccounts,甚至直接使用accounts都会好一些
  • 提防使用外形相似度较高的名称
    • XYZControllerForEfficientHandlingOfStrings
    • XYZControllerForEfficientStorageOfStrings
  • 误导真正可怕的例子是小写字母l和大写字母O,因为他们真的很像数字1和数字0。必须使用更换字体来解决是一个很蠢的方式
//血压升高时刻
int a = 1;
if (O == l)
a = Ol;
else
l = 01;

做有意义的区分

不要只是为了满足编译器或者解释器的需要而写代码。

例如,因为同一作用范围内的两个不同的东西不能重名,你可能会随手改掉其中一个名称,有时干脆以错误的拼写充数,结果就会出现在更正拼写错误后导致编译器出错的情况。

例如class已用,就给一个变量名命名为klass。这非常的愚蠢。

以数字系列命名(a1a2...aN)很蠢

public static void copyChars(char a1[], char a2[]) {
for (int i = 0; i < a1.length; i++) {
a2[i] = a1[i];
}
}

如果将参数改成sourcedestination,这个函数就会像样很多。

废话也是很烂的变量区分方式。比如一个叫做ProductInfoProductData的类,他们虽然名称不同,但是意思毫无区别。

废话都是冗余。variable一词永远不应该出现在变量名中。table一词永远不应该出现在表名中。NameString不比Name来的好,因为Name不会是一个浮点数。

有一个实际的例子反映了这个状况。

getActiveAccount();
getActiveAccounts();
getActiveAccountInfo();

程序员怎么知道要调用哪个函数?

区分名称,就要以读者能鉴别出不同之处的方式来区分

这一块,鲍勃大叔虽然痛骂了这种废话取名法,但是Br还是对两个基本一致的变量或者函数如何命名感到疑惑。鲍勃大叔也没有讲应该怎么正确命名。

使用读得出来的名称

如果名称没法直接读出来,那讨论的时候就会像一个傻鸟。

有一家公司,程序里面写了一个genymdhms(生成日期,年月日时分秒),他们一般读作

gen why emm dee aich emm ess

有些人喜欢看到词就照着拼读,于是会读成

gen-yah-mudda-hims

在给新开发者解释变量的意义的时候,他们总是读出傻乎乎的自造读音,而不是正常的英文单词。

使用generationTimeStamp就看起来像人话的多。

使用可搜索的名称

使用单字母名称和数字常量,有一个问题,很难使用搜索功能从一大篇文字中找出来。

MAX_CLASSES_PER_STUDENT很容易,但是找数字7就很难,因为7可能会出现在很多奇怪的地方。

直接使用常量数字也很容易由于被人错改而逃过搜索。如114514被多次使用了,如果一个地方错改成了114511,就很难再次找出来了。

鲍勃大叔认为单字母名称仅用于短方法中的本地变量。名称长短应该与其作用域大小相对应。若变量或者常量可能在代码中多次使用,则应赋予其便于搜索的名称。

避免使用编码

这一块Br也不是很认同,但是鲍勃大叔的意思大概是不要随便引入奇怪的命名规范

  • 匈牙利语标记法
    • 当时编译器不做类型检查,所以变量前面都加上类型前缀,比如bBusy表示一个Boolean,名称为Busy
  • 成员前缀
    • 不必使用m_前缀来标明成员变量,应当把类和函数做得足够小以消除对成员前缀的需要。
  • 接口与实现
    • IShapeFactory中的前导字母I来指示是一个接口(Interface)是很没有必要的

避免思维映射

不要假定你的代码的读者知道你的奇怪的名称。这种问题经常出现在选择是使用问题领域术语还是解决方案领域术语中。

比如像单字母变量名。虽然大家都习惯用i/j/k来作为循环计数器,但是在大多数情况下,单字母名称并不是一个好选择。

大家通常都是聪明人。聪明人有时会借助类似于脑筋急转弯一样的知识来炫耀其聪明的才智。总而言之,假使你记得r代表不包含主机名和模式(scheme)的小写字母版url的话,那你真是太聪明了。

(乐)

类名

应该是名词或名词短语。不应当是动词。

方法名

应当是动词或者动词短语。

请依据Javabean标准加上前缀get、set和is

重载构造器的时候,建议使用描述了参数的静态方法名。比如

// Good
Complex fulcrumPoint = Complex.FromRealNumber(23.0);
// Bad
Complex fulcrumPoint = new Complex(23.0)

可以考虑将相应的构造器设置为private来强制使用上面那一种命名手段。

别抖机灵

不要起名太耍宝。

例如,别用YiYanDingZheng()来表示IsFalse()这类与文化紧密相关的笑话。

言到意到。意到言到。

每个概念对应一个词

给每个抽象概念选一个词,并一以贯之。例如,使用fetch、retrieve和get来给在多个类中的同种方法命名。

如果在同一堆代码中有controller,有manager,还有driver就令人困惑。例如DeviceManagerProtocol Controller,他们没什么根本区别,为什么不全用controller或者manager呢?

别用双关语

避免同一单词用于不同的目的。比如add。如果你把“连接两个值或者做加法”定义为add的话,那你最好使用append或者insert来作为“将单个参数加入到集合(collection)内”的单词。

使用解决方案领域的名称

只用程序员会读你的代码。因此尽量使用Computer Science的术语、算法名、模式名、数学术语。

依据问题所涉领域来命名不是聪明的做法,你不该让你的协作者老是来问你每个名称的含义。

使用源自所涉问题领域的名称

如果不能用程序员熟悉的术语来给手头的工作命名,那就只能从所涉问题领域而来的名称命名了(比如坐骨神经节?)。至少负责维护代码的程序员还可以去请教领域内的专家。

添加有意义的语境

多数名称都不能自我说明,因此,如果你想要用命名良好的类、函数或者命名空间来放置名称,请提供具体语境,否则加前缀就只能是最后的办法了。

假设你有名为firstNamelastNamestreethouseNumbercitystatezipcode的变量。虽然把他们放在一起就很明显可以看出来这是一个邮递地址。但是如果你只是在一个方法中看到一个孤零零的state呢,你无法理所当然地推断这个是邮递地址的一部分。

因此可以添加前缀addrFirstNameaddrLastName等来提供语境。

当然更好的方案是创建一个名为Address的类。

不要添加没有用的语境

假设有一个名为“加油站豪华版”(Gas Station Deluxe)的应用,不要给其中的每个类添加GSD前缀。你这是在和自己的IDE过不去。输入GSD,按下自动完成键,结果会得到一个超级长的列表。不要搞的IDE没法帮助你。

只要短名称足够清楚,就比长名称好。别给名称添加不必要的语境。

结尾

起好名字最难的地方在于需要良好的描述技巧和共有文化背景。

改名这个行为可能会让某些愚蠢的家伙吃惊,但是这些家伙也会在你做其他代码改善工作时一样吃惊。别让这种事情阻碍你前进的步伐。

另外,多使用现代IDE提供的重构工具。