大家好,我是多选参数的一员 —— 大炮。
在上一篇炸鸡 可读代码编写炸鸡二(上篇) - 命名的长度 中,我们知道了:
由于代码命名添加信息后,存在 命名长度 和 命名歧义 这两个方面问题。
上一篇也提供了一些关于 命名长度 方面的一些建议。
所以接下来本篇炸鸡便提供一些关于 命名歧义 方面的一些建议。
同时不要忘记上一篇炸鸡中抛出的一个问题:
表达 商店数量上限 的常量命名可以是
MAX_SHOP_COUNT
,那SHOP_COUNT_LIMIT
合适吗?
带着这个问题,我们开始吧。
命名的歧义
命名的歧义是如何产生的?
由于命名需要词汇组织,那么 词汇的多义性 可能会导致命名产生歧义。
同时程序员中 约定俗成 的规则也可能使得命名出现歧义。
百闻不如一见,举些例子和针对其的解决方法,希望能给你一点启发。
filter()
results = filter("year <= 2011")
这个 filter
方法,到底是过滤了什么。过滤了 2011 年以前的数据,还是过滤了 2011 年以后的数据?
如果需要这些数据,不妨使用 select()
results = select("year <= 2011")
如果想过滤掉这些数据,不妨使用 exclude()
results = exclude("year <= 2011")
所以用更具体的意思来表达功能,是更妥当的。
clip(text, length)
函数 clip
代表缩短的意思,将文本做一个缩短操作。
但是,这个方法在阅读者角度,就产生歧义。这个 clip
可能存在两种意思:
若函数本意是
将输入的 text 按照 length 长度,将 text 结尾 length 字符移除。
那命名为
clipTail
也许会更贴近本意一些。若函数本意是
将整个文本截断为最大长度 length。
那命名为
clipTo
会不会更恰当一些?
同时我们也可以看出,length
也充满了歧义,如果写成 max_length
,可能就稍微清晰一些。
但是这个 max_length
是 text
的字符串长度,还是字节长度,还是包含单词个数?
很明显,先前文章 可读代码编写炸鸡一 提到的,加入更多的信息的一个办法 —— 加入单位。
- max_chars
- max_bytes
- max_words
包含或者不包含
比如说,需要一个常量来规定购物车数量限制。
ITEM_IN_CART_LIMIT = 10
是要大于 10,还是大等于 10,才算超过限制?
亦或是小于 10,还是小等于 10,才算超过限制?
最好的办法就是加上 min/max
前缀。
如果换成 MAX_ITEM_IN_CART
, 这个就一目了然是最大个数的限制。
还有一个类似的问题,便是区间问题。假设我们有一个函数,根据传入区间左右两点来生成一串序列。
function intRange(start, stop)
...
end
那调用函数时,intRange(n, m)
,返回的序列存在四种情况:
所以 intRange(start, stop)
这样的函数原型,参数的命名会带来一定歧义。
所以如果是要两端都包含,也就是 ,最好使用
first/last
作为命名。
那么如果是要表达 呢。那就是使用
begin/end
。但是这两个太惯用了,例如 lua
就是用 end
来作为函数的范围结尾限定。可以试用 ending
。
命名布尔值变量
关于命名布尔值变量产生的歧义,举一个 bool read_password
例子。
这个变量会出现两种意思。
- 是否需要读
password
- 或者是
password
已经是否被读了。
所以 read
这个词还是换了。如果要表达第一种意思,可以用 need_password
。
第二种意思的话,就用 user_is_authenticated
。
所以,善于使用 is, has, can, should
, 都可使得布尔值命名传达的信息更加清晰,而且让人读了就知道,这是布尔值变量。
同时再举一个例子,这个例子我的项目组挺喜欢用的,就是使用带有 否定意味 的词语。
noSync = false
disable_ssl = false
这样绕不绕?
所以 否定意味 的词语尽量不要加入布尔变量的命名。
我这么改造一下,意思就明朗多了,不需要绕来绕去九曲十八弯。
needSync = false
is_use_ssl = false
。
约定俗成误导阅读者
get()
一般情况下,get
方法一般是被用于返回类的 轻量级 的内部成员,或者说,get
方法内部逻辑不复杂不繁重。
但是如果一个方法中存在大量的数据计算或者内存分配,只有一个 get
,就可能忽略了方法中大量的重的逻辑。
例如我们有一个 getCityInfo
的方法
function getCityInfo()
for _, city in pairs(cities) do
for cityId, info in pairs(city) do
...
...
end
end
end
由于 get
的存在,会让阅读者和使用者产生 这个操作很轻量 的错觉而泛滥使用,使得程序效率下降。
换成 computeXXX()
, allocateXXX()
会更好一些。
我所在的项目中,主程倒是推荐我们使用 fetchxxx()
。这样就能告诉阅读者,这是繁重的操作,谨慎使用。
list->size()
在链表实现代码中,常常有求链表长度的操作,不少人将其命名为 size
。
这个 size
,很容易会被当做是一个 属性 被返回,但是如果代码如下:
int List::size() {
int count = 0;
while (this->list.next() !=NULL) {
count ++;
}
return count;
}
由上述代码可知,size
不仅是一个方法,还是一个 操作很繁重 的方法。
除了上文提到的利用 computeXXX()
、 allocateXXX()
、fetchxxx()
修改命名,让阅读者知晓以外,还可以直接修改代码实现:
void List::insert() {
...
...
this->_size += 1;
}
int List::size() {
return this->_size;
}
让 List
保存一个 _size
成员变量来记录链表长度。这样就不用每次都遍历链表来求长度了。
回顾开头的问题
表达 商店数量上限 的常量命名可以是
MAX_SHOP_COUNT
,那SHOP_COUNT_LIMIT
合适吗?
现在不知道你心中有答案了吗?
总结
好的命名要将歧义出现的可能降到最低。
filter
,length
这些其实都充满歧义,使用更加具体的意义命名。如果要有个表示上下限变量,
max/min
前缀是个好选择。如果是表示
这个区间,用
first/last
比较好。如果是表示
这个区间,用
begin/ending,end
比较好。命名一个布尔值变量,善用
can, is, has
等修饰。同时,否定意味的词,例如disable_ssl
,noSync
尽量不用。我们可以使用
is_use_ssl
、needSync
由于一些约定俗成的规则,阅读者还是容易对一个词产生惯性思维。例如
get()
,List::size()
,会传递一个 轻量操作 的错误信息。我们可以使用
computeXXX()
、allocateXXX()
、fetchxxx()
修改命名,告诉阅读者函数的意图。同时也可以修改函数实现来贴合这些约定俗成的规则。
最后吟唱

本文使用 mdnice 排版