命名是编程中最被低估的工程问题:名字起得好,代码自己就是文档;起得差,每个读者都要付费重建一次理解。本文从”降低认知成本”出发,给出类、方法、变量的具体法则,讨论魔法值与领域语言,并赏析常见反例。

核心概念:命名的本质是降低认知成本

Martin Fowler 曾转述一个经典说法:计算机科学里只有两件难事——缓存失效和命名。命名难,因为它不是翻译问题而是抽象问题:一个名字背后是你对这段代码职责的理解,想不出好名字往往说明职责没切干净。读代码的时间远多于写代码,含糊的名字迫使读者反复跳转上下文、在心里维护一张临时映射表,认知成本就这样逐行累积。好名字的标准很朴素:读者不借助注释,就能说对这段代码”是什么、干什么”。

实践要点

类、方法、变量的基本法则

  • 类用名词或名词短语,表达一个角色或职责:OrderRepository、PricingPolicy。慎用 Manager、Handler 这类万能后缀,它们通常意味着职责还没想清楚。
  • 方法用动词或动词短语:calculateTax、sendInvoice。查询与命令要分清——getUserAndUpdateLastLogin 把读和写揉在一起,调用方极易踩坑。
  • 布尔量与布尔方法用 is/has/can/should 前缀:isPaid、hasPermission,让 if (order.isPaid()) 读起来像一句可验证的陈述。
  • 集合用复数,时间金额带单位或币种:timeoutSeconds、priceInCents,避免”这个值到底是秒还是毫秒”的悬案。
  • 形态遵守语言惯例:Java 类名大驼峰,方法与变量小驼峰,常量大写蛇形,包名全小写且用单数名词。

消灭魔法值

散落在代码里的字面量是命名的重灾区:

// 反例:3 是什么?86400 又是什么?
if (user.getStatus() == 3 && idleTime > 86400) {
// ...
}

// 正例:常量承载含义
private static final int STATUS_DISABLED = 3;
private static final long MAX_IDLE_SECONDS = Duration.ofDays(1).getSeconds();

if (user.getStatus() == STATUS_DISABLED && idleTime > MAX_IDLE_SECONDS) {
// ...
}

更进一步,状态码应收敛为枚举(如 UserStatus.DISABLED),既消灭魔法值,又能获得编译期检查,非法取值直接无法通过编译。

统一领域语言

同一个业务概念在代码里只能有一个名字。产品文档写”租户”,代码里却一会儿 tenant、一会儿 customer、一会儿 account,每个新人都要重建一次映射表,评审时间全花在猜词上。这正是领域驱动设计强调的通用语言(Ubiquitous Language):代码词汇表应与业务对话保持一致,发现不一致时优先改代码,而不是让团队背三套说法。内部改名交给 IDE 安全重构、随功能修改小步推进;对外接口的字段改名则要留兼容期,避免一次性巨型 diff 淹没评审。

常见误区

  • data、info、obj、temp:能编译,但信息量为零。
  • 无共识缩写:CustomTF 这类自创简写只有作者本人认识;HTTP、ID 这类通行缩写才安全。
  • 类型冗余进名字:nameString、userList 里的类型信息,IDE 一眼可见。
  • 用拼音命名如 dazhe:既难读又无法与领域术语对齐;alibaba、taobao 这类已国际化的专有名词除外。
  • 名字靠注释解释:需要注释才能看懂的名字,该改的是名字本身。
  • 反向布尔命名 isNotDeleted:双重否定逼读者做逻辑运算。
  • 一词多义与多词一义并存:同一个 count 在订单上下文指”条目数”、在库存上下文指”可用件数”,调用方极易张冠李戴;反过来,同一个”下单时间”被写成 createTime、orderTime、submitTime 三个字段,联调时只能靠猜。消除这类歧义,正是统一词汇表要解决的日常问题。

另外提醒一点:改名也要看收益。局部变量命名含糊但作用域只有三行,影响有限;公共 API、跨模块共享的枚举与消息字段,一旦起错名字,成本会随着调用方数量复利增长。把精力优先投向这些高杠杆位置,性价比最高。

小结

  • 命名即抽象:起不出好名字,先回头检查职责划分。
  • 立刻可落地的五条:类名词、方法动词、布尔 is/has、集合复数、量纲入名。
  • 魔法值收敛为常量或枚举,业务术语全团队对齐,代码才会”自己会说话”。