在上一篇炸鸡 可读代码编写炸鸡二(上篇) - 命名的长度 中,我们知道了:
由于代码命名添加信息后,存在 命名长度 和 命名歧义 这两个方面问题。
上一篇也提供了一些关于 命名长度 方面的一些建议。
所以接下来本篇炸鸡便提供一些关于 命名歧义 方面的一些建议。
同时不要忘记上一篇炸鸡中抛出的一个问题:
表达 商店数量上限 的常量命名可以是
MAX_SHOP_COUNT,那SHOP_COUNT_LIMIT合适吗?
带着这个问题,我们开始吧。
命名的歧义是如何产生的?
由于命名需要词汇组织,那么 词汇的多义性 可能会导致命名产生歧义。
同时程序员中 约定俗成 的规则也可能使得命名出现歧义。
百闻不如一见,举些例子和针对其的解决方法,希望能给你一点启发。
results = filter("year <= 2011")
这个 filter 方法,到底是过滤了什么。过滤了 2011 年以前的数据,还是过滤了 2011 年以后的数据?
如果需要这些数据,不妨使用 select()
results = select("year <= 2011")
如果想过滤掉这些数据,不妨使用 exclude()
results = exclude("year <= 2011")
所以用更具体的意思来表达功能,是更妥当的。
函数 clip 代表缩短的意思,将文本做一个缩短操作。
但是,这个方法在阅读者角度,就产生歧义。这个 clip 可能存在两种意思:
clipTail 也许会更贴近本意一些。clipTo 会不会更恰当一些?同时我们也可以看出,length 也充满了歧义,如果写成 max_length,可能就稍微清晰一些。
但是这个 max_length 是 text 的字符串长度,还是字节长度,还是包含单词个数?
很明显,先前文章 可读代码编写炸鸡一 提到的,加入更多的信息的一个办法 —— 加入单位。
比如说,需要一个常量来规定购物车数量限制。
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) 这样的函数原型,参数的命名会带来一定歧义。
所以如果是要两端都包含,也就是 [n, m] ,最好使用 first/last 作为命名。
那么如果是要表达 [n, m) 呢。那就是使用 begin/end。但是这两个太惯用了,例如 lua 就是用 end 来作为函数的范围结尾限定。可以试用 ending。
关于命名布尔值变量产生的歧义,举一个 bool read_password 例子。
这个变量会出现两种意思。
passwordpassword 已经是否被读了。所以 read 这个词还是换了。如果要表达第一种意思,可以用 need_password。
第二种意思的话,就用 user_is_authenticated。
所以,善于使用 is, has, can, should, 都可使得布尔值命名传达的信息更加清晰,而且让人读了就知道,这是布尔值变量。
同时再举一个例子,这个例子我的项目组挺喜欢用的,就是使用带有 否定意味 的词语。
noSync = falsedisable_ssl = false这样绕不绕?
所以 否定意味 的词语尽量不要加入布尔变量的命名。
我这么改造一下,意思就明朗多了,不需要绕来绕去九曲十八弯。
needSync = falseis_use_ssl = false。一般情况下,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()。这样就能告诉阅读者,这是繁重的操作,谨慎使用。
在链表实现代码中,常常有求链表长度的操作,不少人将其命名为 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、needSyncget(), List::size(),会传递一个 轻量操作 的错误信息。
我们可以使用 computeXXX()、 allocateXXX() 、fetchxxx() 修改命名,告诉阅读者函数的意图。同时也可以修改函数实现来贴合这些约定俗成的规则。虽然不知道看到这里的朋友还有多少,但是我还是想说一说我接下来要写什么。
通过本篇炸鸡外加之前两篇炸鸡 —— 可读代码编写炸鸡二(上篇) - 命名的长度、可读代码编写炸鸡一,我们大致在代码命名这方面有了一定的理解。
这三篇炸鸡涉及的代码范围是在
一个变量,一个函数名
这样很狭小的范围内。
所以接下来,我们可以将这个范围稍微扩大,开始立足于
多行代码,多个函数
这样的代码范围内进行可读性的优化。
最后多谢各位的阅读,望多多支持多选参数,多多支持编写可读代码炸鸡系列。