如何给变量取个简短且无歧义的名字
译自:Long Names Are Long,作者:Bob Nystrom
引言:来自 Code Review 的观察
Google 最明智的规定之一,是严格执行代码审查(Code Review)。每个改动上线前,都需要经过两种审查:
- 功能审查:确保代码完成了既定功能。
- 可读性审查:确保代码易于理解、维护,并符合语言惯例和文档规范。
作为 Dart 语言的设计者,我有幸参与了大量此类审查。这让我能像人类学家一样,观察开发者如何使用这门语言,并发现一些普遍的模式。其中最令我困扰的模式之一,就是过长的变量和函数命名。
长命名的问题
在早期编程时代,外部标识符可能只靠前六个字符区分,且没有自动补全,长命名确实是个负担。但如今,我们似乎走向了另一个极端——命名变得过于冗长。
过长的命名会带来以下问题:
- 损害代码清晰度:命名过于臃肿,反而让核心意图变得模糊。
- 破坏代码结构:长命名常导致不必要的换行,破坏代码的视觉流。
- 增加使用负担:长的类名使变量声明变得繁琐;长的方法名使其调用变得晦涩;长的变量名导致方法链过长。
我曾见过超过60个字符的命名,这足以写一首短诗。那么,如何解决这个问题呢?
命名的核心目标与原则
一个好的命名应达成两个目标:
- 清晰:能让人立刻知道它关联的是什么。
- 精确:能让人知道它不关联的是什么。
一旦达成这两个目标,多余的字符就是噪音。以下是一些实用的命名原则。
原则一:省略类型信息
在静态类型语言中,类型声明已经提供了足够的信息,无需在命名中重复。
1 | // 不佳的命名 |
对于集合,使用复数名词来描述其内容,比使用“List”、“Map”等类型词汇更直观。方法名同样无需描述参数类型。
1 | // 不佳的命名 |
原则二:省略无助于消除歧义的词
命名是一个标识符,用于定位定义,而非承载对象的所有细节。不要将你知道的所有信息都塞进命名里。
例如,看到一个名为 recentlyUpdatedAnnualSalesBid 的变量,读者会困惑:
- 是否存在“非最近更新”的年度销售投标?
- 是否存在“非年度”的销售投标?
- 如果答案都是“否”,那么
recentlyUpdated和Annual就是冗余信息。
1 | // 不佳的命名 |
大胆地从简洁的命名开始。如果后续发现它会引起歧义,再添加修饰词也不迟。反之,一个冗长的命名很难再被简化。
原则三:省略可从上下文中推断的词
类中的方法和属性、方法中的局部变量,都存在于一个明确的上下文中,无需重复上下文信息。
1 | // 不佳的命名 |
一般来说,变量的作用域越小,其命名就可以越短。
原则四:省略无实际意义的“空泛词”
有些词如 data、state、manager、engine、object、entity 等,本身不传达具体信息,只是让命名听起来更“正式”或“重要”,应尽量避免。
一个好的命名能在读者脑海中勾勒出一幅画面。manager 这个词能让你想到什么?是做绩效评估还是管理预算?它什么具体画面都没提供。
实战演练:重构“华夫饼”代码
让我们通过一个违反所有原则的例子,看看如何应用上述规则进行重构。
原始代码(问题重重):
1 | class DeliciousBelgianWaffleObject { |
第一步:去掉参数类型信息
方法参数已经声明了 List<Strawberry>,方法名中无需重复。
1 | class DeliciousBelgianWaffleObject { |
第二步:去掉无助于消除歧义的形容词
除非系统中存在“难吃的比利时华夫饼”或“法国华夫饼”,否则 Delicious 和 Belgian 是多余的。
1 | class WaffleObject { |
第三步:去掉可从上下文推断的词garnishWaffle 是 WaffleObject 类的方法,因此 Waffle 是多余的。
1 | class WaffleObject { |
第四步:去掉无意义的“空泛词”
在面向对象语言中,所有类都是对象,Object 这个词没有提供任何额外信息。
1 | class Waffle { |
最终结果:清晰、简洁、意图明确。
总结
命名是编程中最基础也最重要的任务之一。追求清晰的命名,并非鼓励使用晦涩的缩写,而是倡导在提供足够信息的前提下力求简洁。记住命名的两个核心目标:清晰与精确。让你的代码像海明威的小说一样简洁有力,而不是像法律条文一样冗长繁琐。