引言
Google的一个明智之举就是进行严格的代码审查。每次代码改动在落地之前,都至少要经过两轮审查。首先,团队中的某个人进行常规审查,确保代码的功能符合预期。
接下来,还有第二层审查,叫做可读性审查。它确保代码是易读的:它便于理解和维护吗?是否遵循了语法风格和习惯用法?是否有良好的说明文档?
最近Google内部使用Dart越来越多,而我一直在做相关的代码审查。作为一个语言设计师,这个过程十分有趣。我可以从一线了解人们使用Dart的情况,这对一个语言的持续发展至关重要。我可以清晰地看到最常见的错误和最常用的功能,仿佛自己是一位记录原住民生活的人类学家。
但是,无论如何,这不是这篇文章的重点,和Dart关系也不大。我想谈论的是我在太多代码中看到的一件快把我逼疯的事:命名怎么就这么长?
是的,命名有时候太短,当C语言要求标识符前六个字符唯一的时候1,当代码自动补齐还没有被发明的时候;当每次按键就像在雪地中向前爬两次的时候,这确实是个问题。我很开心我们生活在乌托邦一样的未来中,像p、idxcrpm和x3这样过度简化的命名很少见了。
但是摆锤向另一侧摆的太远了。我们不应该成为海明威,但也不应该成为田纳西·威廉姆斯2。过长的命名会严重损害代码的清晰度。巨大的标识符,会让你的操作显得微不足道,会让代码很难一眼看完,并且会引入额外的换行符,导致代码流被打断。
过长的类名会让用户不愿意声明该类型的本地变量,而是将函数嵌套起来使用,导致代码变得冗长而复杂。过长的方法名会让同样重要的函数参数变得模糊。过长的变量名用起来非常烦人,导致冗长的方法链或者级联。
我曾经见过超过60个字符的标识符,你可以在其中塞进一首短诗或者禅宗轶事(或许比原有的命名更有启发意义),不要担心,我来帮忙了。
选一个好名字
一个名称应该有两个目标:
- 它需要清晰:你需要知道名称指的是什么。
- 它需要确切:你需要知道它不指代什么。
当一个名称可以完成上述目标之后,任何额外的字符都将是累赘。下面是我在代码命名时遵循的一些原则:
1.当类型显而易见时,去掉相关单词
如果你用的语言有静态类型系统,用户通常知道一个变量的类型。方法往往很短,因此即使在查看需要推断类型的本地变量,或者在代码审查时,或者是其他一些没有静态分析的情况下,通常看几行代码也能确定变量的类型。
鉴于此,在变量名称中增加类型信息是多余的。我们已经做过正确的抉择,放弃了匈牙利命名法3,就让它去吧。
1 | // 反例: |
尤其是对于集合来说,最好使用集合所含内容的复数形式,而不是简单的描述集合的类型。如果读者关心的是集合中的内容,那么就应该反映这一点。
1 | // 反例: |
对于函数名称来说也是一样的。函数名称不需要描述它的参数、参数的类型等,这些是参数列表需要做的事。
1 | // 反例: |
我认为这样让函数调用更易读了。
1 | mergeTableCells(tableCells); |
只有我这样认为吗?或许有人有不同的想法?
2.当不能消解歧义时,去掉相关单词
有些人喜欢将了解到的一切相关内容都塞进名称当中。但是希望你能记住,名称是一个标识符:它指向的被定义的位置。它不是一个详尽的目录,需要向用户介绍所有想要了解的信息。定义需要完成这件事,而名称将用户指引到定义。
当我看到recentlyUpdatedAnnualSalesBid这样的命名时,我会思考:
- 有年度销售投标更新不是最近的吗?
- 有最近年度销售投标不是更新的吗?
- 有最近更新销售投标不是年度的吗?
- 有最近更新年度投标不是销售的吗?
- 有最近更新年度销售不是投标的吗?
上面任意一个问题的答案是否,都意味着有一个多余的单词。
1 | // 反例: |
当然,你也可能做过头。例如把第一个例子缩写成bid可能是过于模糊的。但是如果你有疑问,就把它省略掉。如果后续发现一个命名会导致冲突或误解,那你总是可以再添加新的限定词,但是很少有人会专门回来删除不必要的部分。
3.当上下文明确时,去掉相关单词
在这个段落中,我可以使用“我”,因为你知道这篇博文是由Bob Nystrom所写,我的大脸就摆在上面。我不需要一直强调Bob Nystrom(尽管我很想这么做)。代码也是同样的道理,函数或者字段出现在一个类的上下文中,将环境信息视为已知并且不要重复。
1 | // 反例: |
实际上,这意味着如果一个名称所处的嵌套层级越深,它所拥有的上下文环境就越多。这一般意味着它可以有一个较短的名称。效果就是作用域小的变量有较短的名称。
4.当意义欠缺时,去掉相关单词
在游戏行业中,我经常看到相关的行为。一些人总是忍不住诱惑为自己的标识符添加严肃的业务术语。我猜想这样可以让他们觉得自己的代码更重要,从而感觉自己更重要。
在大多数情况下,这些空洞的术语没有任何实际意义。常见的例子比如说:data、state、amout、value、manager、engine、object、entity以及instance。
一个好的命名会为读者描绘一幅画像。将一个变量命名为manager并不会让读者对它的作用有什么理解.它是用来评估你绩效的吗?还是坐在你旁边问催你快点写TPS报告4?
问问你自己:“如果去掉这个词,这个命名的意义会变化吗?”如果答案是否,那这个词就没用,赶紧删掉。
将准则用于,,,华夫饼
为了让你感受到如何在实际工作中应用这些规则,我这里举一个违反所有规则的例子。说实在的,这个编造的例子其实和我在审查代码时看到的真实代码非常接近:
1 | class DeliciousBelgianWaffleObject { |
我们从参数列表中知道它接受的是Strawberry的List(#1),因此我们去掉它:
1 | class DeliciousBelgianWaffleObject { |
除非我们的程序包括难吃的比利时华夫饼,或者其他国籍的华夫饼,否则我们就可以去掉这些形容词(#2):
1 | class WaffleObject { |
这个方法在一个华夫饼对象中(#3),所以我们知道它是要装饰什么:
1 | class WaffleObject { |
很明显这是一个对象(#4),一切都是对象,因为这是面向对象编程的含义:
1 | class Waffle { |
看,是不是好多了呢?
我认为上述原则都比较简单。你可能觉得思考这些没啥用,但是我坚持认为,命名是编程时重要的基本任务之一,名称是我们为无形的比特世界赋予的结构。
译者注:
六个字符:在早期的C语言标准中,由于技术限制,只有标识符的前六个字符才会被编译器识别和区分。因此,在命名变量、函数和其他标识符时,需要确保前六个字符是唯一的,以避免编译错误或混淆。这个限制在现代编程语言中已经不存在,但是仍然存在一些历史遗留问题需要考虑。
海明威和田纳西·威廉姆斯:二人都是文学史上著名的作家,二人在文学风格和修辞手法上有很大的差异。海明威以简洁、明快、雄健的文风而著称,他的小说在语言和情节上都非常精简,很少使用过多的修辞手法。相比之下,田纳西·威廉姆斯则更注重情感和内心世界的描绘,他的作品较为繁复、复杂,并常常使用大量的修辞手法来表达复杂的情感和内心冲突。
匈牙利命名法:Hungarian notation是一种变量命名约定,由微软公司的程序员Charles Simonyi在1970年代首先提出并使用。其主要目的是为了提高代码的可读性和可维护性,通过在变量名中添加前缀来表示变量的数据类型和作用。例如,变量名strName表示字符串类型的名称,nCount表示整数类型的计数器,bEnabled表示布尔类型的是否启用等。尽管匈牙利命名法曾经非常流行,但它在现代编程实践中已经不再被广泛使用,因为它被认为增加了代码的复杂性和维护成本。
TPS报告:TPS报告是一个商业术语,代表“三部曲报告”(The Three Parts of Status reports),通常在管理咨询和公司管理中使用。三部曲报告的三个部分是:任务(Tasks),问题(Problems)和解决方案(Solutions)。在电影《Office Space》中,TPS报告被用作一种幽默元素,被描绘为一种无聊和烦人的任务,由办公室的管理层强制执行。