浅谈变量类型以外的变量命名

在编程实践中定义变量时,咱们所能控制的无非两点:变量类型与变量名。某种程度上,这二者分别考验的实际上是开发者的数学水平与语文水平。在今天,即使已经有了很是高大上的类型系统,「名存实亡」的变量名仍然常常能对开发者形成困扰。那么,咱们有什么理论能用来指导变量命名呢?编程

在计算机科学的萌芽时代,变量名和变量类型之间并无明确的界限。好比,著名的「匈牙利命名法」提倡的就是把属性 + 类型 + 描述一股脑地写进变量名:例如 const int max 就对应 c_i_max 的名字。编程语言发展到今天,这种实践看起来天然就很别扭了。对于如今的主流编程语言,咱们大能够认为,它们中的变量名就应该是纯粹的「对变量的描述」。数组

那么,怎么样描述好一个变量呢?这时,咱们的关注点其实已经从天然科学中的类型理论转移到人文科学中的文学创做了 :) 在给变量取名时,咱们实际上要作的是言简意赅地描述清楚一个抽象的东西,这其实并非件容易的事情——「名字」但是万恶的天然语言的范畴里的东西,试问列位能在白板上手写 bug-free 红黑树的大神们,可曾用母语写出过满分的高中做文?bash

因此,天然语言在编程中其实发挥着很是重要的做用——毕竟代码是写给人看的。笔者相信,每一个正常的阅读者,都会在潜意识里用从小耳濡目染的天然语言来理解代码逻辑,而后才是套用(也许是培训班昨天才教的)编程语言语法规则来考量细节。这里,某些标榜着简洁但充斥着大量符号的编程语言就能够做为反例:用它们写出的代码也许对于热爱推导公式的 Geek 来讲很是友好,但普通人第一眼看上去就是天书。固然,矫枉过正的例子也不是没有:对于一些鼓励超长变量名的语言,维护它们的代码……多少会有些捏着鼻子的感受。app

到此,咱们已经总结出了变量命名的若干微妙之处:编程语言

  • 变量命名与类型系统是分离的。
  • 变量命名依据实际上是天然语言。
  • 命名过于简洁时,可读性很差。
  • 命名过于冗长时,可读性也差。

听起来是否是很主观,很难量化呢?这就引出了咱们的主题:虽然命名难以量化,但你能够依据天然语言的语法,来整理出一些通用的规则呀 :)函数式编程

语法是笔者高中时代比较反感的东西——你不学它,光凭「语感」也能中个八九不离十;你学了它,反而可能稀里糊涂套错场景……不过,语法其实很像天然语言的类型系统,它与编程语言中相应的概念有着很微妙的联系。好比,JavaScript 中的关键字,就能够根据语法中的词性来这样粗略地分类:函数

名词
function var class

动词
import export extends return break continue
delete switch new try catch throw yield

介词
for in else

连词
if while

代词
this
复制代码

是否可以注意到动词明显地比名词更多?并且,编程语言中还有很多介词 / 连词这类的虚词来表达控制流。相比之下,HTML 则几乎就是名词的天下:吃我 head body form button 啦!HTML 无非是一堆名词的堆叠嵌套,相比存在着复杂逻辑的编程语言来讲,较少遇到与命名相关的维护性问题。那么,在维护编程语言的代码时,有哪些天然语言的规则可以有助于提升可读性和可维护性呢?这里笔者总结了这么几点:工具

  • 注意命名与类型在语义上的匹配
  • 保持概念的一致性
  • 避免制造出反语言直觉的结构
  • 慎用宽泛的抽象概念

让咱们逐条看一下吧 :-Dfetch

注意命名与类型在语义上的匹配

前面咱们已经知道,词汇的词性和变量的类型有着一些微妙的关系。在命名时,保持这种关系的匹配有利于加强可读性。注意,这并非像匈牙利命名法那样把类型写入变量名,而是根据类型来选择更契合的变量名:ui

  • 对于布尔类型的变量,其命名经常使用形如 isSomething / hasSomething 的形式。这其实很是接近天然语言里的陈述句——陈述句有着确定和否认的形式,这就暗合了布尔值的 truefalse
  • 对于数组类型的变量,其命名经常使用复数形式。例如 apples 在天然语言里就暗示着有不止一个 apple,这和数组的思惟模型是很是契合的。
  • 对于对象类型的变量,其命名经常使用名词性短语。例如 UserModel 这样的 class。别忘了,名词和动词都属于实词,不过两者一个更加「面向对象」,一个更加「函数式」罢了。
  • 对于函数类型的变量,其命名经常使用动词,或者说更接近祈使句式。一个函数就应该明确地去「作某件事」,这时候「说什么就作什么」显然更加简单真诚。好比,你能够尝试给你的类方法前面都加上个 Please,这时候 getUserData() 就能无缝地变成通顺的 Please get user data 的祈使句了。固然,在函数式编程中,面向函数耍出的各类花样,还能够用各类虚词来粉饰。譬如像 withState 这样的名字,其中就暗含着对参数的「修饰」能力。再有各种回调的场景,也常见 on / before / after 这样的介词来代表相应动做的触发时机。

不一样的编程语言和范式,在词汇的选用上会有很是鲜明的区别。好比 Java 就是个名词王国,而函数一等公民的语言里,动词的地位就高得多。这里的风格建议是在什么地方就按什么地方的规矩来讲话,这样到哪都吃得开 XD

保持概念的一致性

在有了 Lint 等格式化工具以后,项目里的换行缩进通常都能获得统一,这确实能消除代码格式排版上的潦草感受。可是,许多项目里仍然很常见这样的地方:

  • 三个相似的操做分别封装成 loadUser / fetchUser / getUser 三个不一样的函数,它们好像都是一回事,但是你敢随便换着用吗?
  • 上面一个 elements.forEach(element),下面就是 elements.forEach(elem)。一个 index 也能够有 i / idx / inx / ind 四种不一样的缩写 :)
  • 现有模块中私有变量按照形如 $xxx 的格式声明,新代码上来就是个 __xxx

其实这些问题单独拿出来,都很难称之为严重,相应的解决方案应该也都是老生常谈的了。但若是这样的不一致性遍及项目的代码库,那么再工整的空格缩进,也掩盖不了代码的杂乱感。这里的一个非技术建议是创建 Code Review 机制:这些问题很难单纯经过 Lint 工具来约束,提出 Review 意见与修改也并不难,关键在于流程:

  • 新人很难一上来就了解各类隐式的约定,许多老成员轻车熟路的实践,对于新人来讲反而是个不熟悉的暗坑。
  • 一旦代码并入主干,再去修改它的成本就会显著地提升——老代码跑得好好的,重构坏了谁负责?

固然了,Code Review 还有许多其余的功效,这里就再也不展开啦。

避免制造出反语言直觉的结构

咱们已经提到过,直觉在编码中发挥着潜移默化的做用。那么,什么叫作「反直觉」呢?譬如这么些小地方:

  • 双重否认表示确定,那么咱们就应该放心地用 2N 重的否认来表示确定吗?机器固然能正确地理解 if (!dataNotLoaded !== false) 的精妙逻辑,不过这时候恐怕很容易把本身和别人绕进去,尤为是在与或条件多起来的时候 :-p
  • 把数据「封装一层」是很常见的手法,不过这时候若是出现了 data.data,该怎么肯定其它代码中用到的 data 变量指的是谁呢?
  • 深度嵌套和花式跳转等形式的控制流也是反直觉的,可是这已经超过变量命名的范畴了 XD

慎用宽泛的抽象概念

得益于天然语言中丰富的抽象概念,总有一些变量名是很是「方便偷懒」的。好比,若是你实在想不通一个装数据的变量该怎么命名,那就叫它 data 啊!若是一个基类不知道叫什么好,那就叫它 Base 呗!

其实,若是是由于「没有想清楚该起什么名字」而使用了这样宽泛的概念,实际上就会在暗中欠下「没有清晰正确的设计」的技术债。好比上面的行为,在扩展或重构时极可能遇到这样的问题:

  • 处处都是 data,以致于不只无法简单地查找与替换来重构,依赖 IDE 来改个变量名都要提心吊胆,不肯定影响范围。
  • 各类形如 core / base / common 的模块,难以界定边界在哪:core 到底管些什么事,继承它到底会带来哪些反作用,而个人新函数要不要放在里面呢?

对于装数据的变量来讲,data / item 这样的名字写了和没写差很少——固然了,对于能够多处复用的工具函数,这样的命名彻底没有问题。相对地,对于函数来讲,各类具体的动做就比较容易描述清楚,不过你若是非要把全部的回调名一概写成 callback,那也不是不能够……

其实上面的这些问题,不少只要在提交前 double check 一遍流程,就可以在自省中发现。这也是一个很是重要的技能:找出本身哪里作得还不够好,自己就是一种进步了。

总结

对各类概念的抽象过程经常是编程中最有乐趣的地方之一,而好的变量命名无疑有助于将思惟过程更清晰地表达出来。在本文提出的观点里,变量的命名之难,与类型的强弱和类型系统的动态静态并无直接的关系:数学大师的语文未必就足够好,反之亦然。咱们也基于一些天然语言里很是简单的规则来提出了一些对编码实践的建议。不过,若是下次遇到 diss 你变量命名的 review 建议的时候,本文也能够为你提供一个有力的反击论据:

咱们连编程语言的类型系统都很难彻底搞明白,更况且天然语言的呢?

相关文章
相关标签/搜索