变量命名指南

变量名很有用

软件是写给人来理解的;因此要合理地选择变量名。别人需要梳理你的代码,并且去理解代码的意图,才能够扩展或者修复。既浪费了空间又不直接明了的变量名很多。即使用心良苦,很多工程师最后选的变量名最多也只是徒有其表。这篇文章目的就是帮助工程师如何选取好的变量名。我们侧重于代码评审,因为在代码评审中最容易暴露出糟糕变量名的问题。当然,使用好的变量名还有其他很多原因(比如提高代码可维护性)。

为什么要命名变量

给变量一个有意义的命名主要原因就是能够让人理解这些变量。如果仅仅只是写给机器的话,那么那些自动生成并且没有任何意义的变量也无妨,比如:

任何一位工程师都看得出上面的代码很难理解,因为它违背了两条准则:1)禁止简写;2)赋予有意义的变量名。不过话说回来,这些准则也不一定总是有用的。缩写也不一定就是不好,之后我们会讨论到。“有意义”这个概念有些模糊,而且取决于不同人的解释。有些工程师认为冗长的就是有意义的(例如MultiDictionaryLanguageProcessorOutput)。有些人则发现理解所谓”真正的“有意义是很困难的,所以在做努力之前就已经放弃了。因此,在尽量满足上述两条准则下,写出的代码可能如下:

比起第一个例子来,评审人员更容易理解上述代码。变量名很准确而且可读性强。但实际上却没有任何的帮助并且浪费空间,因为:

processElements

几乎所有的代码都是在“处理”事物(毕竟,代码的作用都是“processor”),所以process这个单词其实就是七个没意义的字母,仅仅只是表示“计算”而已。Elements这个词也没有好到哪里去。很显然这个函数是要在集合上进行操作。而且使用这个函数名也不能帮助读者找出bug。

numResults

大多数代码都会产生“结果”(最终都会);所以就像process一样,Results也是七个没意义的字母。完整的变量名,numResults给人感觉像是要限制输出的数量,但是又太含糊让读者很伤脑筋。

collection

浪费空间;很显然这是个集合,因为之前的类型申明就是Collection<Integer>.

num

仅仅就表达这是int类型

result, count

这两个就是编码时的陈腔滥调了;就如numResults一样,它们既浪费了空间也过于空泛,并没有提供帮助来让读者理解这段代码。

然而,我们需要牢记变量名的真正用意:读者需要理解代码,这就需要达到以下两点:

  1. 程序员的意图是什么?
  2. 这段代码到底是在做什么?

来看一个长变量名的例子是怎么给读者增加精神负担的,下面是重新写好的代码,这段代码很好地展示了什么是让读者去推测变量名:

这种改变让代码看起来比自动生成的代码都糟糕,至少自动生成的代码更短。这段代码并没有让程序员的意图更明显,甚至需要读者看更多的字符。要知道代码评审需要看很多代码,糟糕的命名使得一项艰巨的任务更加艰巨了。那如何才能减少代码评审负担呢?

关于代码评审

代码评审的时候主要有两种精神负担:距离和样板代码。从变量名角度来说,距离的意思是指评审人员需要额外看多少代码才能够理解这个变量的作用。评审人员不会像编码人员写代码的时候那样脑海里有大概轮廓,他们只能快速地自己重建这个轮廓。而且评审人员需要很快完成;因为不值得在评审上花费和编码同样的时间。好的变量名能够很好地解决距离这个问题,因为它们能够提醒评审人员这些变量的目的是什么。那样的话评审人员也不需要花时间去回看之前的代码。

另一个负担就是样板代码。代码经常在做一些复杂的事情;它是其他人写的;评审人员经常会根据自己的代码进行上下文切换;他们每天都要看大量代码并且很有可能评审了多年。介于这些,评审人员很难一直保持精神集中。因此,每一个没用的字符都会消耗评审的效率。对于单独一个小的案例,其实不清楚也不是什么大问题。在有足够的时间和精力(可能需要和编码人员有后续交流)的情况下,评审人员完全可以搞清楚所有代码的作用。但是他们不能年复一年地重复这么做。这相当于将评审人员千刀万剐。

一个好的例子

所以,为了能够让代码评审人员理解意图,编码人员在尽可能少用字符情况下可以重写成以下代码:

我们一起来分析一下每一个变量看看为什么能够让代码更容易理解:

printFirstNPositive

不像processElements,现在很清楚编码人员写这个函数的目的(并且提供了难得的机会发现bug)

n

有了清晰的函数名,对于n就没必要用个复杂的名字了

c

集合并不值得花费太多精力,所以我们削减了9个字符来减少读者浏览样板字符时的疲劳;介于这个函数很短,而且也只有一个集合变量,所以很容易就记住了c是一个整型的集合

skipped

不像results,现在自己就说明了(不需要注释)返回值是什么。介于这是个很短的函数,并且对skipped声明为一个int类型也很容易看到,如果用numSkipped就会浪费了3个字符。

i

在遍历一个循环的时候,使用i变量是个约定俗成的习惯,每个人都能够理解。姑且不说count这个变量名没一点用,i变量还节省了4个字符。

maybePositive

num仅仅只说明int做的事,然而maybePositive就很难被误解并且可以帮助定位出bug。

现在也更容易发现这段代码里面其实有两个bugs。在最初的版本中,如果编码人员只是想打印出正整数的话是很难发现。现在读者们可以注意到一个bug就是0并不是正数(所以n应该大于0,而不是大于等于)。(这里应该也需要单元测试)。此外,因为第一个参数现在是maxToPrint(相反的,maxToConsider),很显然如果集合里面有非正整数的话,函数不会打印出足够的元素。如何正确重写这个函数将留个读者作为练习。

命名的原则

  • 作为程序员,我们职责是和其他的人类读者交流,而不是和机器交流
  • 不要让我去猜。变量名应该直接表达编码人员的意图,所以读者是不应该去猜测的。
  • 代码评审非常重要,但是也会有精神疲劳。尽可能减少样板代码,因为它会分散评审人员的集中力。
  • 相比较注释,我们更喜欢好的命名,但是并不是说可以完全取代注释。

参考准则

为了满足这些原则,写代码的时候可以使用下面一些实用指南:

不要把数据类型放到变量名中

把变量的类型放到变量名中会加重读者的精神疲劳(需要扫描更多的样板代码)而且也不是一个好的替换。现在的编辑器比如Eclipse能够很好地展示变量的类型,使得添加类型到命名中很累赘、这种做法也会招致一些错误,我就看过下面这种代码:

最容易犯的错误就是在名字后面添加Str或者String,或者加入集合的类型。这里有一些建议:

Bad Name(s) Good Name(s)
hostList, hostSet hosts, validHosts
hostInputStream rawHostData
hostStr, hostString hostText, hostJson, hostKey
valueString firstName, lowercasedSKU
intPort portNumber

一般来说:

  • 使用复数命名而不要包含集合类型
  • 如果确实要在你的变量名中加入标量类型(int,String,Char),你应该做到:
    • 更好地解释了这是个什么变量
    • 解释使用这个新变量有什么变化

尽可能使用日耳曼语系名字

大多数命名都应该用日耳曼语系,它遵循了像挪威语那样的优点,而不是晦涩含糊如英语一样的罗曼语系。挪威语里面有更多像tannlege(“牙医”)和sykehus(“病房”)的单词,很少如dentist和hospital这类单词(这类单词拆分之后就不是英语单词了,除非你知道它们的意思,不然这就很难理解)。你应该尽可能使用日耳曼语系的优点来给你的变量命名:即使不认识的情况下也容易理解。

另一种方式使用日耳曼语系名字是在没有错误情况下尽可能的具体。比如,如果一个函数仅仅用于检测CPU的负荷,那就把这个函数命名为overloadedCPUFinder,而不是unhealthyHostFinder。虽然这个函数可能是被用于查找不正常的主机,但是unhealthyHostFinder会使得听起来比其本身更笼统。

日耳曼语系命名也有例外,在这部分的后面也会提到:习语和短变量名

值得一提的是这里也不是说禁止使用笼统的命名。那些确实是在做一些一般性工作的代码就可以用个笼统的命名。例如,在下面这个例子中的transform是可以的,因为这是一个一般性字符串操作库里面的一部分。

将简单的注释写入到变量名中

像之前所说的,变量名是无法(也是不应该)代替注释的。如果用变量名代替一条注释,那这也是很可以的,因为:

  • 能够让代码评审人员读代码时减少视觉上的混乱(注释也是一种精神疲劳,所以提供其真正的价值)
  • 如果一个变量的使用离注释较远,那么代码评审人就没必要转移他们的注意力而返回去查看注释来理解变量的用意。

例如,

避免过度使用陈词滥调

除了不使用日耳曼语系命名外,以下的这些变量名被广泛滥用了很多年,而实际上这些变量名从来不应该被使用:

  • val, value
  • result, res, retval
  • tmp, temp
  • count
  • str

还有仅仅只是在变量名加上其类型名称也是要被禁止的,比如像tempString或者intStr这类等等

在必要之处使用一些习惯用法

不像之前所说的陈词滥调,有一些习惯的用法是被广泛理解而且能够被安全使用即使是字面上看含义有些模糊。这里有一些事例(都是些Jave/C的例子,但是也适用于其他所有语言):

  • 在循环语句中使用i,j,k作为迭代
  • 当用意明显的时候,使用n作为界限或者数量
  • 在catch语句中,使用e作为一个异常

警告:习语应该只有在用意明显的时候被使用到

用意明显的时候使用短命名

短的命名甚至是一个字母的变量名在某些场合中是更好的。当评审人员看到一个很长的名字,他们就会觉得需要去注意这些长的命名,如果最后发现这个命名完全没用,那纯属于浪费时间。一个短的命名表达了唯一需要了解这个变量的就是它的类型。所以在一下两个成立的情况下,使用短名字(一个或者两个字母)的完全合理的:

  1. 变量的声明和使用不是很远(比如五行以内,所以也就是说变量声明是在读者视觉范围以内)
  2. 除了类型以外,找不到一个更好得变量名
  3. 读者在这段代码里面没有其他的东西需要记住(研究表明人类能够同时记住七件事)

这里有个例子:

也可以写成:

但是这种写法需要占据更多的空间却没有其他的收益;变量使用都很紧凑,读者们也没有问题去理解其用意。还有就是,长命名updateWorkflow表示着这个变量名有特别之处。评审人员要花费精力去看这个命名是不是样板。在这里可能不是个大问题,但是记住,代码评审会“死于”这种“千刀万剐”。

删除没意义的一次性变量

一次性变量,也被成为垃圾变量,是指那些被迫用于函数传递间的中间结果。他们有时也是有用的(详见下一准则),但是大多数时候都是无意义的,并且也会使得代码库混乱。下面的代码段中,编码人员就让读者读起来更艰难:

相对于上面的代码,编码人员应该将代码简化成:

使用短的一次性变量来拆分较长的行

有时需要一个一次性变量来拆分长的代码行

这是可以的,由于两行代码距离不远,可以使用一次性变量和一个或者两个字母的变量名(hs)来减少视觉混乱。

使用短的一次性变量来拆分复杂的表达

读这段代码会很困难:

写成这样就会更好:

重申一下,因为代码间距离很短而且意图明显,短的变量名完全可以使用。

使用长的一次性变量来解释难懂的代码

有时你需要一次性代码简单地解释这段代码在做什么。例如,有时候不得不使用别人写的一些糟糕命名的代码,所以你不能修复这些命名。但是你能做的就是使用有意义的一次性变量来解释在做些什么,比如,

记住,这种情况下你不能使用短的一次性变量:

这仅仅只是添加了视觉混乱而并没有帮助有效地解释代码。

打赏支持我翻译更多好文章,谢谢!

打赏译者

打赏支持我翻译更多好文章,谢谢!

3 5 收藏 7 评论

关于作者:Venn_宇

一名还在学习的工作程序员。如果对Linux,分布式,以及运维方面也有同样兴趣的,欢迎大家一起讨论 个人主页 · 我的文章 · 13 ·   

相关文章

可能感兴趣的话题



直接登录
最新评论
  • 王念一 初三学生 01/09

    嗯。。。這篇文章很辣雞。。。

    幾乎所有範例都違反了單一權責原則不説,居然還有下面這種“火車失事”的方法鏈:

    里氏原則:我靠!

    Robert Martin:我靠!

    • Venn_宇 软件工程师 01/10

      承认您所说的“火车失事”的问题,但是这里想要说明的是可以使用一个简单的临时变量把很长的语句拆分成多句,比如上述事例中的hs这个变量。

    • PunCha 程序员 01/10

      火车失事的代码就一定不好?如果使用Nullable pattern的话,完全可以安全使用链式表达式。另外,人家讨论的是变量命名,你偏要套用SOLID原则去评估。那么是不是还要喷,函数的参数没有做合法性检查?

  • show_time 程序员 01/10

    学习了。又可以重新规划一下自己的代码写法了

跳到底部
返回顶部