如何给变量取个简短且无歧义的名字

译自:Long Names Are Long,作者:Bob Nystrom

引言:来自 Code Review 的观察

Google 最明智的规定之一,是严格执行代码审查(Code Review)。每个改动上线前,都需要经过两种审查:

  1. 功能审查:确保代码完成了既定功能。
  2. 可读性审查:确保代码易于理解、维护,并符合语言惯例和文档规范。

作为 Dart 语言的设计者,我有幸参与了大量此类审查。这让我能像人类学家一样,观察开发者如何使用这门语言,并发现一些普遍的模式。其中最令我困扰的模式之一,就是过长的变量和函数命名

长命名的问题

在早期编程时代,外部标识符可能只靠前六个字符区分,且没有自动补全,长命名确实是个负担。但如今,我们似乎走向了另一个极端——命名变得过于冗长。

过长的命名会带来以下问题:

  • 损害代码清晰度:命名过于臃肿,反而让核心意图变得模糊。
  • 破坏代码结构:长命名常导致不必要的换行,破坏代码的视觉流。
  • 增加使用负担:长的类名使变量声明变得繁琐;长的方法名使其调用变得晦涩;长的变量名导致方法链过长。

我曾见过超过60个字符的命名,这足以写一首短诗。那么,如何解决这个问题呢?

命名的核心目标与原则

一个好的命名应达成两个目标:

  1. 清晰:能让人立刻知道它关联的是什么。
  2. 精确:能让人知道它关联的是什么。

一旦达成这两个目标,多余的字符就是噪音。以下是一些实用的命名原则。

原则一:省略类型信息

在静态类型语言中,类型声明已经提供了足够的信息,无需在命名中重复。

1
2
3
4
5
6
7
8
9
// 不佳的命名
String nameString;
List<DateTime> holidayDateList;
Map<Employee, Role> employeeRoleHashMap;

// 改进的命名
String name;
List<DateTime> holidays;
Map<Employee, Role> employeeRoles;

对于集合,使用复数名词来描述其内容,比使用“List”、“Map”等类型词汇更直观。方法名同样无需描述参数类型。

1
2
3
4
5
6
7
// 不佳的命名
mergeTableCells(List<TableCell> cells)
sortEventsUsingComparator(List<Event> events, Comparator<Event> comparator)

// 改进的命名
merge(List<TableCell> cells)
sort(List<Event> events, Comparator<Event> comparator)

原则二:省略无助于消除歧义的词

命名是一个标识符,用于定位定义,而非承载对象的所有细节。不要将你知道的所有信息都塞进命名里。

例如,看到一个名为 recentlyUpdatedAnnualSalesBid 的变量,读者会困惑:

  • 是否存在“非最近更新”的年度销售投标?
  • 是否存在“非年度”的销售投标?
  • 如果答案都是“否”,那么 recentlyUpdatedAnnual 就是冗余信息。
1
2
3
4
5
6
7
// 不佳的命名
finalBattleMostDangerousBossMonster;
weaklingFirstEncounterMonster;

// 改进的命名
boss;
firstMonster;

大胆地从简洁的命名开始。如果后续发现它会引起歧义,再添加修饰词也不迟。反之,一个冗长的命名很难再被简化。

原则三:省略可从上下文中推断的词

类中的方法和属性、方法中的局部变量,都存在于一个明确的上下文中,无需重复上下文信息。

1
2
3
4
5
6
7
8
9
10
11
// 不佳的命名
class AnnualHolidaySale {
int _annualSaleRebate;
void promoteHolidaySale() { ... }
}

// 改进的命名
class AnnualHolidaySale {
int _rebate;
void promote() { ... }
}

一般来说,变量的作用域越小,其命名就可以越短

原则四:省略无实际意义的“空泛词”

有些词如 datastatemanagerengineobjectentity 等,本身不传达具体信息,只是让命名听起来更“正式”或“重要”,应尽量避免。

一个好的命名能在读者脑海中勾勒出一幅画面。manager 这个词能让你想到什么?是做绩效评估还是管理预算?它什么具体画面都没提供。

实战演练:重构“华夫饼”代码

让我们通过一个违反所有原则的例子,看看如何应用上述规则进行重构。

原始代码(问题重重):

1
2
3
4
class DeliciousBelgianWaffleObject {
void garnishDeliciousBelgianWaffleWithStrawberryList(
List<Strawberry> strawberryList) { ... }
}

第一步:去掉参数类型信息
方法参数已经声明了 List<Strawberry>,方法名中无需重复。

1
2
3
class DeliciousBelgianWaffleObject {
void garnishDeliciousBelgianWaffle(List<Strawberry> strawberries) { ... }
}

第二步:去掉无助于消除歧义的形容词
除非系统中存在“难吃的比利时华夫饼”或“法国华夫饼”,否则 DeliciousBelgian 是多余的。

1
2
3
class WaffleObject {
void garnishWaffle(List<Strawberry> strawberries) { ... }
}

第三步:去掉可从上下文推断的词
garnishWaffleWaffleObject 类的方法,因此 Waffle 是多余的。

1
2
3
class WaffleObject {
void garnish(List<Strawberry> strawberries) { ... }
}

第四步:去掉无意义的“空泛词”
在面向对象语言中,所有类都是对象,Object 这个词没有提供任何额外信息。

1
2
3
class Waffle {
void garnish(List<Strawberry> strawberries) { ... }
}

最终结果:清晰、简洁、意图明确。

总结

命名是编程中最基础也最重要的任务之一。追求清晰的命名,并非鼓励使用晦涩的缩写,而是倡导在提供足够信息的前提下力求简洁。记住命名的两个核心目标:清晰与精确。让你的代码像海明威的小说一样简洁有力,而不是像法律条文一样冗长繁琐。