变量命名指南
2017-01-15 00:07
134 查看
变量名很有用
软件是写给人来理解的;因此要合理地选择变量名。别人需要梳理你的代码,并且去理解代码的意图,才能够扩展或者修复。既浪费了空间又不直接明了的变量名很多。即使用心良苦,很多工程师最后选的变量名最多也只是徒有其表。这篇文章目的就是帮助工程师如何选取好的变量名。我们侧重于代码评审,因为在代码评审中最容易暴露出糟糕变量名的问题。当然,使用好的变量名还有其他很多原因(比如提高代码可维护性)。
为什么要命名变量
给变量一个有意义的命名主要原因就是能够让人理解这些变量。如果仅仅只是写给机器的话,那么那些自动生成并且没有任何意义的变量也无妨,比如:Java
1234567891011121314 | int f1(int a1, Collection<Integer> a2){ int a5 = 0; for (int a3 = 0; a3 < a2.size() && a3 < a1; a3++) { int a6 = a2.get(a3); if (a6 >= 0) { System.out.println(a6 + " "); } else { a5++; } } System.out.println("n"); return a5;} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | int processElements(int numResults, Collection<Integer> collection) { int result = 0; for (int count = 0; count < collection.size() && count < numResults; count++) { int num = collection.get(count); if (num >= 0) { System.out.println(num + " "); } else { result++; } } System.out.println("n"); return result; } |
processElements
几乎所有的代码都是在“处理”事物(毕竟,代码的作用都是“processor”),所以process这个单词其实就是七个没意义的字母,仅仅只是表示“计算”而已。Elements这个词也没有好到哪里去。很显然这个函数是要在集合上进行操作。而且使用这个函数名也不能帮助读者找出bug。
numResults
大多数代码都会产生“结果”(最终都会);所以就像process一样,Results也是七个没意义的字母。完整的变量名,numResults给人感觉像是要限制输出的数量,但是又太含糊让读者很伤脑筋。
collection
浪费空间;很显然这是个集合,因为之前的类型申明就是Collection<Integer>.
num
仅仅就表达这是int类型
result, count
这两个就是编码时的陈腔滥调了;就如numResults一样,它们既浪费了空间也过于空泛,并没有提供帮助来让读者理解这段代码。
然而,我们需要牢记变量名的真正用意:读者需要理解代码,这就需要达到以下两点:
程序员的意图是什么?
这段代码到底是在做什么?
来看一个长变量名的例子是怎么给读者增加精神负担的,下面是重新写好的代码,这段代码很好地展示了什么是让读者去推测变量名:
Java
123456789101112131415161718 | int doSomethingWithCollectionElements(int numberOfResults, Collection<Integer> integerCollection){ int resultToReturn = 0; for (int variableThatCountsUp = 0; variableThatCountsUp < integerCollection.size() && variableThatCountsUp < numberOfResults; variableThatCountsUp++) { int integerFromCollection = integerCollection.get(count); if (integerFromCollection >= 0) { System.out.println(integerFromCollection + " "); } else { resultToReturn++; } } System.out.println("n"); return resultToReturn;} |
关于代码评审
代码评审的时候主要有两种精神负担:距离和样板代码。从变量名角度来说,距离的意思是指评审人员需要额外看多少代码才能够理解这个变量的作用。评审人员不会像编码人员写代码的时候那样脑海里有大概轮廓,他们只能快速地自己重建这个轮廓。而且评审人员需要很快完成;因为不值得在评审上花费和编码同样的时间。好的变量名能够很好地解决距离这个问题,因为它们能够提醒评审人员这些变量的目的是什么。那样的话评审人员也不需要花时间去回看之前的代码。另一个负担就是样板代码。代码经常在做一些复杂的事情;它是其他人写的;评审人员经常会根据自己的代码进行上下文切换;他们每天都要看大量代码并且很有可能评审了多年。介于这些,评审人员很难一直保持精神集中。因此,每一个没用的字符都会消耗评审的效率。对于单独一个小的案例,其实不清楚也不是什么大问题。在有足够的时间和精力(可能需要和编码人员有后续交流)的情况下,评审人员完全可以搞清楚所有代码的作用。但是他们不能年复一年地重复这么做。这相当于将评审人员千刀万剐。一个好的例子
所以,为了能够让代码评审人员理解意图,编码人员在尽可能少用字符情况下可以重写成以下代码:Java1 2 3 4 5 6 7 8 9 10 11 12 13 14 | int printFirstNPositive(int n, Collection<Integer> c) { int skipped = 0; for (int i = 0; i < c.size() && i < n; i++) { int maybePositive = c.get(i); if (maybePositive >= 0) { System.out.println(maybePositive + " "); } else { skipped++; } } System.out.println("n"); return skipped; } |
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能够很好地展示变量的类型,使得添加类型到命名中很累赘、这种做法也会招致一些错误,我就看过下面这种代码:Java
1 | Set<Host> hostList = hostSvc.getHost |
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会使得听起来比其本身更笼统。Java1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // GOOD Set<Host> overloadedCPUs = hostStatsTable.search(overCPUUtilizationThreshold); // BAD Set<Host> matches = hostStatsTable.search(criteria); // GOOD List<String> lowercasedNames = people.apply(Transformers.toLowerCase()); // BAD List<String> newstrs = people.apply(Transformers.toLowerCase()); // GOOD Set<Host> findUnhealthyHosts(Set<Host> droplets) { } // BAD Set<Host> processHosts(Set<Host> hosts) { } |
值得一提的是这里也不是说禁止使用笼统的命名。那些确实是在做一些一般性工作的代码就可以用个笼统的命名。例如,在下面这个例子中的transform是可以的,因为这是一个一般性字符串操作库里面的一部分。
Java
123 | class StringTransformer { String transform(String input, TransformerChain additionalTransformers);} |
将简单的注释写入到变量名中
像之前所说的,变量名是无法(也是不应该)代替注释的。如果用变量名代替一条注释,那这也是很可以的,因为:能够让代码评审人员读代码时减少视觉上的混乱(注释也是一种精神疲劳,所以提供其真正的价值)如果一个变量的使用离注释较远,那么代码评审人就没必要转移他们的注意力而返回去查看注释来理解变量的用意。
例如,Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // BAD String name; // First and last name // GOOD String fullName; // BAD int port; // TCP port number // GOOD int tcpPort; // BAD // This is derived from the JSON header String authCode; // GOOD String jsonHeaderAuthCode; |
避免过度使用陈词滥调
除了不使用日耳曼语系命名外,以下的这些变量名被广泛滥用了很多年,而实际上这些变量名从来不应该被使用:val, value
result, res, retval
tmp, temp
count
str
还有仅仅只是在变量名加上其类型名称也是要被禁止的,比如像tempString或者intStr这类等等
在必要之处使用一些习惯用法
不像之前所说的陈词滥调,有一些习惯的用法是被广泛理解而且能够被安全使用即使是字面上看含义有些模糊。这里有一些事例(都是些Jave/C的例子,但是也适用于其他所有语言):在循环语句中使用i,j,k作为迭代
当用意明显的时候,使用n作为界限或者数量
在catch语句中,使用e作为一个异常
Java
12345 | // OKfor (int i = 0; i < hosts.size(); i++) { } // OKString repeat(String s, int n); |
用意明显的时候使用短命名
短的命名甚至是一个字母的变量名在某些场合中是更好的。当评审人员看到一个很长的名字,他们就会觉得需要去注意这些长的命名,如果最后发现这个命名完全没用,那纯属于浪费时间。一个短的命名表达了唯一需要了解这个变量的就是它的类型。所以在一下两个成立的情况下,使用短名字(一个或者两个字母)的完全合理的:变量的声明和使用不是很远(比如五行以内,所以也就是说变量声明是在读者视觉范围以内)除了类型以外,找不到一个更好得变量名
读者在这段代码里面没有其他的东西需要记住(研究表明人类能够同时记住七件事)
这里有个例子:Java
1 2 3 4 5 6 7 8 9 10 11 12 | void updateJVMs(HostService s, Filter obsoleteVersions) { // 'hs' is only used once, and is "obviously" a HostService List<Host> hs = s.fetchHostsWithPackage(obsoleteVersions); // 'hs' is used only within field of vision of reader Workflow w = startUpdateWorkflow(hs); try { w.wait(); } finally { w.close(); } } |
Java
12345678910 | void updateJVMs(HostSevice hostService, Filter obsoleteVersions){ List<Host> hosts = hostService.fetchHostsWithPackage(obsoleteVersions); Workflow updateWorkflow = startUpdateWorkflow(hosts); try { updateWorkflow.wait(); } finally { updateWorkflow.close(); }} |
删除没意义的一次性变量
一次性变量,也被成为垃圾变量,是指那些被迫用于函数传递间的中间结果。他们有时也是有用的(详见下一准则),但是大多数时候都是无意义的,并且也会使得代码库混乱。下面的代码段中,编码人员就让读者读起来更艰难:Java1 2 | List<Host> results = hostService.fetchMatchingHosts(hostFilter); return dropletFinder(results); |
Java
1 | return dropletFinder(hostService.fetchMatchingHosts(hostFilter)); |
使用短的一次性变量来拆分较长的行
有时需要一个一次性变量来拆分长的代码行Java1 2 | List<Host> hs = hostService.fetchMatchingHosts(hostFilter); return DropletFinderFactoryFactory().getFactory().getFinder(region).dropletFinder(hs); |
使用短的一次性变量来拆分复杂的表达
读这段代码会很困难:Java
123 | return hostUpdater.updateJVM(hostService.fetchMatchingHosts(hostFilter), JVMServiceFactory.factory(region).getApprovedVersions(), RegionFactory.factory(region).getZones()); |
1 2 3 4 | List<Host> hs = hostService.fetchMatchingHosts(hostFilter); Set<JVMVersions> js = JVMServiceFactory.factory(region).getApprovedVersions(), List<AvailabilityZone> zs = RegionFactory(region).getZones(); return hostUpdater.updateJVM(hs, js, zs); |
使用长的一次性变量来解释难懂的代码
有时你需要一次性代码简单地解释这段代码在做什么。例如,有时候不得不使用别人写的一些糟糕命名的代码,所以你不能修复这些命名。但是你能做的就是使用有意义的一次性变量来解释在做些什么,比如,Java
12 | List<Column> pivotedColumns = olapCube.transformAlpha();updateDisplay(pivotedColumns); |
1 2 | List<Column> ps = olapCube.transformAlpha(); updateDisplay(ps); |
本文由 伯乐在线 - Venn_宇 翻译,艾凌风 校稿
英文出处:nickels
from: http://blog.jobbole.com/109645/
相关文章推荐
- C++变量(C++变量定义、变量赋值、命名规则)
- Java中类、接口、变量、方法、属性、常量的命名规则
- 31天重构指南之二十八:为布尔方法命名
- android studio 重命名项目变量名称
- C#中的变量命名规则
- 自己的代码的风格——关键字的定义(变量的定义)匈牙利命名法
- volatile 变量使用指南
- FleaPHP 开发指南 - 4. 命名规范和目录结构
- php 变量循环命名 赋值 变量名输出 <三>
- 变量,方法,类命名规则
- JavaScript知识点总结(命名规范,变量的作用域)
- 著名的变量命名规则
- 第5章 常量--变量C++/C 编程指南,v 1.0
- Google C++编程风格指南(五):命名约定
- MATLAB使用变量为文件命名
- .NET Framework 常规参考 -- 命名指南
- C#指南,重温基础,展望远方!(3)类型和变量
- Shell脚本中的变量名命名约束
- c语言 变量命名法
- Python变量、方法、类的命名规则