为什么命名值得单独讨论
一个项目里,变量名、函数名、类型名加起来占了代码字符量的一半以上。命名不好,读代码的人就得靠上下文去猜,猜错一次就是半小时的排查。
更要命的是,命名的影响是长期的:写错一次,之后每一次阅读都要重新付一遍理解成本。
好名字的三条标准
我判断一个名字好不好,只看三条:
- 能读出用途:不点开实现就知道它是什么
- 不含误导:名字描述的和实际做的必须一致
- 长度匹配作用域:活得越久、跨得越远,名字就该越完整
三条里最容易违反的是第二条。一个叫 getUser 的函数如果顺手写了缓存,它就已经在骗人了。
从「是什么」出发,而不是「怎么算」
新手容易把实现细节写进名字里:
// 不好:暴露了实现方式,改算法就得改名
const userArrayFilteredByAge = users.filter((u) => u.age > 18);
// 好:描述业务含义,实现随便换
const adultUsers = users.filter((u) => u.age > 18);
判断方法很简单:如果换一种实现方式,这个名字还成立吗?不成立就说明它绑定了细节。
常见坏名字与改法
| 坏名字 | 问题 | 改法 |
|---|---|---|
data info temp | 什么都没说 | pendingOrders userProfile |
list arr obj | 只说了类型 | expiredTokens requestBody |
flag | 看不出真假含义 | isPublished hasPaid |
handleClick2 | 数字后缀说明有重复 | 合并成一个,或用具体行为区分 |
doProcess | 动词太泛 | syncOrders rebuildIndex |
getUserDataFromDb | 把存储方式写进名字 | findUserById |
布尔值用肯定式
布尔变量统一用 is / has / can / should 开头,而且不要用否定式:
// 不好:双重否定读起来要绕一圈
if (!isNotReady) { ... }
// 好
if (isReady) { ... }
否定式命名还有一个隐患:取反时容易写漏。isDisabled 取反是 isEnabled,但写的人常常写成 !isDisabled,多一层否定就多一次出错机会。
名字长度与作用域成正比
一个只在三行内使用的循环下标叫 i 完全没问题,因为它一眼就能看完。但一个跨模块的配置对象叫 cfg 就过分了,读的人必须跳过去看定义。
// 作用域极小,短名字反而清爽
for (let i = 0; i < rows.length; i++) { ... }
// 作用域大,名字要自解释
const maxRetryCountBeforeGivingUp = 3;
缩写与行业词
缩写的原则是:行业通用缩写可以用,自创缩写不行。
- 可以:
idurlhttpapidbconfigauth - 不可以:
usrMgrsvcCtlrtmpBuf2
拿不准的时候就写全。多敲几个字符的成本,远低于别人读错的成本。
一致性比「正确」更重要
同一个概念在项目里必须只有一个词。如果一会儿叫 user、一会儿叫 member、一会儿叫 account,读代码的人会以为这是三个不同的东西。
建议在项目初期就定一份词汇表,写进 README:
| 概念 | 统一用词 | 不要用 |
|---|---|---|
| 登录用户 | user | member account customer |
| 文章 | article | post blog entry |
| 删除 | remove | delete destroy drop 混用 |
哪怕某个词在语义上更准确,只要团队已经统一了另一个,就应该跟随。一致带来的可预测性,价值高于细微的语义精确。
改名的时机
遇到坏名字不要想着「下次再改」——下次永远不会来。改名的正确时机是你因为这个名字多花了一次时间的时候。
现代编辑器都有安全重构功能,改名成本已经很低。真正阻止人们改名的,是怕影响别处的心理负担,而这种负担恰恰说明名字已经太模糊了。
小结
命名不是风格问题,而是设计问题。名字里藏着你对这段代码的理解,理解模糊,名字必然模糊。
把「能读出用途、不含误导、长度匹配作用域」这三条做到,代码的可读性就已经超过大多数项目了。