上周评审一个合并请求,我在一个五十行的方法里数出了这些变量名:data、data2、info、temp、flag、result、list。作者是个挺不错的工程师,逻辑写得很清楚,但我读这段代码花的时间是它应有的三倍。
我在评论里写了半天,删了重写了三次,因为我很怕显得在挑刺。命名这件事就是这样,说轻了没人改,说重了像找茬。
我总结的几条硬规则
名字长度应该和作用域成正比
循环里一个只活三行的索引,叫 i 完全没问题。但一个跨越四十行、被五个地方引用的变量,叫 d 就是犯罪。反过来也成立:一个只在两行之间存活的临时变量,起个二十个字符的名字反而是噪音。
布尔量用肯定式
disabled 这种否定式的名字,一旦遇到取反判断就变成双重否定,读的时候脑子要拐个弯。更狠的是我见过 isNotInvalid,三重否定,我盯着看了半分钟才确定它为真时是什么意思。
不要把类型写进名字
userList 改成 users 就够了,复数已经表达了集合的含义。而且一旦你把它从列表改成集合,名字就骗人了——这种过期的名字比没有名字更危险。
同一个概念全系统只用一个词
这条是我踩过最大的坑。我们有个系统里,同一个业务实体在三个模块里分别叫三个不同的词,数据库表用第一个,接口字段用第二个,前端展示用第三个。新人来了第一个月一直在问这三个是不是同一个东西,我每次都要画一张图解释。
更糟的是有一次真的出了 bug:一个开发以为其中两个是不同的概念,写了一段转换逻辑,把数据搞乱了。
我现在做新模块会先花半小时列一张术语表,写清楚每个业务概念用哪个词,然后要求代码、字段、文档统一。这半小时的收益是全项目周期的。
函数名要诚实地反映副作用
以 get 开头的方法却修改了状态,是我见过最坑人的一类。有一次我在一个循环里调用了一个看起来纯粹的查询方法,结果它内部会把查到的记录标记成已读,导致数据被批量误改。
方法名是一种契约。查询就只查询,要改状态就在名字里说出来。
但有时候问题不在命名
说个更深一层的例子。我们有个字段叫 status,八个取值。我一开始觉得问题是命名太笼统,想把它拆成更精确的名字。
结果分析下来发现,这八个值里有三个都表示「已完成」,只是完成的路径不同:正常完成、人工干预后完成、超时后自动完成。还有两个表示「进行中」,区别是有没有走到某个环节。
换句话说,这个字段同时编码了两个维度:状态和原因。命名怎么改都不可能改好,因为它本身就不是一个概念。后来我们拆成了状态和结束原因两个字段,八个值变成三个状态加四个原因,代码里所有的判断一下子清爽了。
这件事之后我多了一个习惯:当我给一个东西起名字特别费劲的时候,我会怀疑是不是这个东西本身没被想清楚。好名字起不出来,往往是因为被命名的对象职责混乱。命名的困难是一个信号,不是一个障碍。
最后
我也不是什么命名典范。翻自己三年前的代码,一样有 handleData 和 doProcess 这种什么都没说的方法名。区别只是现在改名字的成本我看得更清楚了——改一个名字,在带重构功能的编辑器里是三秒钟的事,而一个坏名字会让后面每一个读到它的人多花几分钟。
三秒对几分钟乘以未来所有读者,这笔账不难算。