有意义的命名:名副其实、避免误导、做有意义的区分、使用读得出来的名称
好,咱们今天聊聊命名。说实话,代码里最常被忽视但又最要命的问题,就是命名。
我见过太多项目,代码逻辑本身没问题,但读起来像在猜谜。变量名叫 a、b、temp,函数名叫 doStuff、handleIt。你想想看,这种代码三个月后你自己回来看,还能记得 temp 到底是临时存什么的吗?
命名这件事,说白了就是给代码里的每个元素起个合适的名字。名字起好了,代码自己就能说话。起不好,就得靠注释来擦屁股。
名副其实:名字要说出真相
什么叫名副其实?就是变量名、函数名、类名要准确反映它的含义和用途。别玩文字游戏,别搞抽象艺术。
举个例子,你看到下面这段代码,能猜出 d 是什么吗?
int d; // 经过的天数
嗯,注释写了是“经过的天数”。但问题是,为什么不用 elapsedDays 呢?
我个人习惯是:如果一个变量需要注释来解释,那说明名字没起好。好的名字应该自带文档属性。
int elapsedDays; // 这才对,一目了然
再看一个函数命名的例子:
// 糟糕的命名
public List<int[]> getThem() {
List<int[]> list1 = new ArrayList<>();
for (int[] x : theList) {
if (x[0] == 4) {
list1.add(x);
}
}
return list1;
}
这段代码里,theList 是什么?x[0] 是什么?4 又代表什么?完全看不懂。
重构一下:
public List<int[]> getFlaggedCells() {
List<int[]> flaggedCells = new ArrayList<>();
for (int[] cell : gameBoard) {
if (cell[STATUS_VALUE] == FLAGGED) {
flaggedCells.add(cell);
}
}
return flaggedCells;
}
现在清楚多了吧?gameBoard 是棋盘,cell 是格子,FLAGGED 是标记状态。名字自己就把故事讲完了。
避免误导:别让名字撒谎
这一点我特别有感触。我在项目中遇到过好几次,因为命名误导导致的线上bug。
最常见的误导就是缩写和简写。比如 hp,你以为是“惠普”?其实是 hypotenuse(斜边)。再比如 cust,到底是 customer 还是 custom?
还有更隐蔽的误导:
- 用
list命名一个不是 List 类型的变量 - 用
accountGroup命名一个普通的字符串 - 用
isActive命名一个返回 int 的方法(0/1 表示真假)
这些都会让读者产生错误的预期。代码读起来就像在玩“猜猜我到底是什么”的游戏。
checkStatus(),结果同事以为它只是检查状态而不返回结果,直接忽略了返回值。后来我改成 isStatusValid(),问题就解决了。
另外,别用发音相似或者形状相似的字母组合。比如 l 和 1,O 和 0。你想想看,l1 和 10 在代码里能分得清吗?
做有意义的区分:别为了区分而区分
这个坑我踩过很多次。有时候为了满足编译器的要求(比如不能有两个同名变量),就随便加个数字或者前缀来区分。
比如:
public void copyChars(char a1[], char a2[]) {
for (int i = 0; i < a1.length; i++) {
a2[i] = a1[i];
}
}
a1 和 a2 有什么区别?哪个是源,哪个是目标?完全看不出来。
改成这样:
public void copyChars(char source[], char destination[]) {
for (int i = 0; i < source.length; i++) {
destination[i] = source[i];
}
}
现在清楚了:source 是源,destination 是目标。
还有一种常见的无意义区分:加“Info”、“Data”、“Object”这类后缀。
// 糟糕
Product productData;
Product productInfo;
Product productObject;
// 好
Product product;
你想想看,productData 和 productInfo 到底有什么区别?如果没区别,那为什么要加后缀?
使用读得出来的名称:别让嘴巴打结
这一点可能很多人没意识到。代码不仅要写给机器看,更要写给人看。人是用嘴巴和耳朵来思考的。
我见过一个变量名叫 genymdhms(生成日期:年、月、日、时、分、秒)。你试试读出来:“gen-y-m-d-h-m-s”?这哪是变量名,这分明是密码。
改成 generationTimestamp 或者 createDate,是不是顺口多了?
再比如:
// 糟糕
private Date crDt;
// 好
private Date createDate;
crDt 读出来是“C-R-D-T”,四个字母。而 createDate 读出来是“create date”,两个单词。哪个更容易记住?哪个更容易在讨论时说出来?
这一点在团队协作中尤其重要。你想想看,开会讨论代码时,你说“把那个 crDt 改一下”,别人得反应半天。但你说“把 createDate 改一下”,大家立刻就知道你在说什么。
知识体系总览
下面这张图总结了有意义的命名的核心要点:
总结一下
命名这件事,说难不难,说简单也不简单。它考验的不是你的技术能力,而是你对代码的敬畏心。
我这些年写代码最大的体会就是:好的命名,是对未来读代码的人的一种尊重。那个未来读代码的人,很可能就是三个月后的你自己。
所以,下次起名字的时候,多花30秒想一想:这个名字够不够清楚?会不会误导人?有没有更好的选择?
嗯,这30秒的投资,回报率绝对高得吓人。