您的位置:首页 > 编程语言

代码质量随想录(三)名字好,误会少 推荐

2012-06-05 16:53 176 查看
  写完前两篇之后,有点小倦怠,因为一方面要整理读书笔记,一方面还要结合自己的思路加以重新表述,颇费周张。不过前两日看到有小朋友过来赞我的文章,说对实际代码有所帮助,还是满欣慰的,本系列随想录的目的之一,就是要营造一个努力改良代码质量的思维环境。

  要想让标识符的名称更易理解,就应该多考虑考虑此名称是否会被误读。

  先看两个很容易误读的例子。



Object[]results=Database.getAllObjects().filter("year<=2011");

  到底是要选出year小于等于2011的那部分对象,还是选出year大于2011的那部分呢?filter到底是排除(exclude),还是遴选(select)呢?我自己在日常编码中也爱用filter,多半由于习惯。现在自己思量,是得改正了。再看



publicStringclip(Stringtext,intlength);//裁掉文本的末尾

  clip方法有歧义:到底是去掉文本后的length个字符,还是从头开始截取最大length个字符呢?比如clip("Java",2);到底是"va"还是"Ja"?如果是前者应该叫removeLast,如果是后者则应叫truncate。而且length也有毛病,到底以什么为单位?字节?字符?还是词语?如果是字符,应该是truncate(Stringtext,intmaxCharCount)。

  归纳起来说,以下几种情形应格外注重选取避免误解的名称。

1.以常量表示包含端点的上限或下限时,应分别用MAX与MIN做前缀。

  例如CART_TOO_BIT_LIMIT=10到底是说购物车中最多放10件商品还是11件?抑或是9件?改为MAX_ITEMS_IN_CART=10则很清楚:最多10件。

2.在表达包含左右端点的区间时,应用first及last。



publicvoidprintIntegerInRange(intstart,intstop){...}
...
printIntegerInRange(2,6);

  到底打印[2,3,4,5]还是[2,3,4,5,6]?如果是后者,应该是printIntegerInRange(intfirst,intlast)。

3.在表达包含左端点而不含右端点的区间时,应当使用begin与end。

  英文中没有哪个常用词的字面意思能表示“区段内最后一个值的紧下一个值”这个意思,所以使用end只是约定成俗而已,并不精确。例如publicvoidprintEventsInRange(Stringbegin,Stringend),可以使用如下参数来调用:printEventsInRange("OCT1600:00","OCT1700:00"),这样的话,一般人都能理解右端点("OCT1700:00")不含在范围内。如果用publicvoidprintEventsInRange(Stringfirst,Stringlast),则是printEventsInRange("OCT1600:00","OCT1623:59")。

4.使用判断词来消除boolean变量的歧义。

  为boolean变量起名时一定注意是否有歧义:


boolreadPassword=true;

  到底是当前需要读取密码,还是密码已经被读取过了?前者应是needPassword,后者应是userIsAuthenticated。

  使用is、has、can、should等词汇来让boolean变量与方法的意图更加清晰,尤其是在那些不需要申明方法或函数返回类型的编程语言中。例如:spaceLeft()到底是返回剩下的空间大小,还是返回是否有剩余空间?根据是简单获取还是复杂计算,前者应命名为getLeftSpaceInPixel()或calcLeftSpacePx(),分别指示轻量级(get)和重量级(calculate或compute)的两种获取办法;而后者则应是hasSpaceLeft(),只说有没有剩余空间,不谈具体的量。

5.避免在boolean命名中使用否定形式。

  例如:



booldisableSSL=false;

  不如下面这种命名方式清晰:



booluseSSL=true;

6.不要同约定成俗的命名方式相违逆。

  例如getXXX()格式的方法一般有两个隐含意义:1.该操作为轻量级。2.该操作返回所在类的某个成员。

  如下统计算数平均数的方法名称即为不宜:


publicclassSampleCollector{
publicvoidadd(doublesample){...}

publicdoublegetMean(){
...//叠加所有采样值并返回“总和/样本数”
}
...
}

  getMean()并非轻量级操作,且不返回本类某个成员。不如叫它computeMean()更好,compute会引人联想该操作是不是稍为复杂一些,耗时一些。如果非要用getMean做名称的话,那么mean应被纳入缓存机制。例如:

privatebooleanmeanCached;//计算完样本后置为true,样本改动时置为false
...

publicdoublegetMean(){
if(!meanCached){
...//叠加所有采样值
mean=sampleSum/sampleCount;
meanCached=true;
}
returnmean;
}[code]

[/code]


voidShrinkList(list<Node>&list,intmax_size){
while(list.size()>max_size){
FreeNode(list.back());
list.pop_back();
}
}


  size()操作的时间复杂度为O(1)应是大多数人的共识,可是恰恰list的size()是时间复杂度为O(n)的操作,这导致整个函数的复杂度变为O(n2)。按理说size()应该叫为countSize()或countElements(),以体现其重量级运算的特质来,不过,为了和其余容器类相符合,还是叫成size了。所幸新版C++规范强制要求size操作的时间复杂度为O(1)了(ARC书的作者这么说的,我未查证。大家帮忙在C++11规范中查证此事。原有规范只是“建议”它应具有常数时间复杂度,并未强制)。

  小翔以为,如果某个抽象接口定义了一个貌似轻量级的简单操作,如Collection的size(),则子类对象在实现时应该尽量降低时间复杂度。实在不能时甚至可以考虑抛出异常或对客户提出警告。根本的解决办法还是学习C++规范那样,给出一个建议的时间复杂度来。

7.在多个候选名称中取舍时应该仔细质询其可能带来的歧义。

  例如有两份相似的服务器配置参数文件:



config_id:100
description:"increasefontsizeto14pt"
traffic_fraction:5%
...




config_id:101
description:"increasefontsizeto13pt"
[其余参数与前一份相同]


  我们现在想通过某个机制复用整套参数,例如这样:



config_id:101
想要复用的配置文件id:100
[其余参数与前一份相同]


  那么,这个“想要复用的配置文件id”,应该怎么起名呢?备选关键词有:template、reuse、copy和inherit。

  template很模糊:“template:100”到底是说自己是一份名叫“100”模板,还是说使用一个名叫“100”的模板作为其基础参数?况且模板这个概念太过抽象,给人感觉需要以具体内容填充它。

  “reuse:100”到底是说这份参数最多可以使用100次,还是说复用名为“100”的那份配置文件中的参数?

  “copy:100”是第100份拷贝吗?还是说拷贝自编号为“100”的那套配置?后者不如叫copy_config_from更好。

  “inherit:100”,inherit这个词,大多数程序员很熟悉,且与日常生活的“财产继承”概念可相比拟,所以引起的误解相对较少。可以扩充为inherit_config_from来更精确地阐明这个意思。

  综上,copy_config_from或inherit_config_from应为最终中选名称。

  总之,好的标识符名称可以尽量消除代码阅读者的误解,提高代码可读性与可维护性,亦能促进业务交流。所以应当仔细考究,尽量选取免于误会的名称,尤其是遇到“filter、length和limit”这些模棱两可的词语时。此外,区间与上下限含不含端点、boolean类型的标识符会不会引起误解、方法名称所隐含的意义是否符合常识,这些问题也应该在起名时反覆考量。

  用了两篇文章才讲完给标识符起名的事情,可见其的确关乎代码质量的提升。下一篇我们谈谈代码的排版问题。

爱飞翔2012年6月4日

本文使用CreativeCommonsBY-NC-ND3.0协议(创作共用自由转载-保持署名-非商业使用-禁止衍生)发布。

本文网址:http://agilemobidev.com/eastarlee/code-quality/think_in_code_quality_3_good_name_zh_cn/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息