一张图引发的思考

085658825.jpg

1 食人植物


试想一下对于图中植物我们如果正儿八经地说这是Corpse Flower(尸花),可能很难就一下子明白这是什么并关联相关意境,而食人植物一下子传递一些惊悚的场景,让大家很警觉。因此命名就是传递信息和意境,好的命名就是要尽可能容易地传递准确的信息。同时命名需要考虑上下文,如果是做植物研究的,可能需要说学名是什么,并且是哪个科哪个目。如果仅仅是参观介绍,可能就需要侧重生动、有意思了。同样编程中也存在命名的问题。

问题出现的原因

编程当中命名是个辛苦活,但绝对是一个值得投入的辛苦活。好的命名可以极大提高代码可读性,甚至可以一定程度上弥补代码结构、逻辑上的一些不足。结合我们目前的Code Review,其中命名问题的比例还是相对比较高,虽然没有准确统计,但从摘要上分析差不多五个问题中就有一个命名问题,而且越是早期这块问题越突出,甚至一部分Review感觉基本在纠结风格和命名问题,当然这不是什么好事,但这类问题的突出不得不引起我们的反思。到底原因是什么?总体上的原因可能有几个:


命名需要用英文,可惜我们的母语不是英语,学校中的英语学习中基本应试,实际使用很少。这是很多人面对命名问题时的第一反应,而且感觉也是很容易从此获得自我安慰。


重视度不够。对于功能实现的急迫造成一种纠结命名就是浪费时间的错觉,怎么简单怎么来,不查字典、不谷歌,导致一些命名被简化粗暴对待,诸如tempretget等一堆被用滥的词不断进入我们的视野。


缺乏编码理论支持。命名难,所以可能不能很快搞定,也可能不能一次搞定,但我们经常遇到的问题是一直搞不定,而且是不断在重复着原来的错误。除了重视的原因,还有一点就是我们接触编程开始,就很少人告诉我们如何命名,知道的仅仅是不能数字打头、不能用特殊符号等等,做到的仅是让编译器不抱怨而已。

几点思考

翻了翻《可读代码的艺术》,同时结合自己的编程和Review实践,有如下几点思考:

选择具体的词

比如如下代码的GetPage函数命名中Get就比较空泛,可能有几种情况,比如从文件读取、去远处获取、去数据库查询、或者就是简单地从返回成员变量,如果一切都是Get就需要扒开具体实现才可能确定到底用哪种方式。如果我们可以选择更具体的词,比如文件读取用Read或者Load、远程获取用Fetch、数据库查询用Query、简单返回成员变量时候采用Get,并且整个项目保持一致,可能仅仅看到名字就可以闪现出对应的场景。将信息塞进名字中的一部分就是选择很具体、避免空泛的一些词。


def GetPage(url):

...


多样化选择

英语的词汇是很丰富多彩的,不同的词可能有类似意思,同时也存在微妙的差别,因此希望命名选择的时候可以多考虑一些方案,并且根据项目实际进行选择,一旦选定了,就尽量保证一致性。同时必须牢记一点:清晰和准确比可爱更重要。一些常见单词的替代词事例参加下表:

单词

替代词

send

deliver, dispatch, announce, distribute, route

find

search, extract, locate, recover

start

launch, create, begin, open

make

create, set up, build, generate, compose, add, new

1 常见词的替代词

缩写的困扰

在讨论这点之前,我们首先要知道缩写的目的:缩短单词,便于记忆和拼写,从而便于交流。在IDE没有出现的时候,曾经出现过缩写泛滥的年代,其影响延续至今。毕竟当时敲代码是一件很苦力的事情,需要一个字母一个字母敲入,因此缩写就可以极大缓解这块的烦扰,同时也带来了更大的烦扰:缩写到极致后产生了记忆和理解的问题。因为缩写,大家需要更多记忆,同时误解和冲突也更多。如今IDE普遍支持代码自动补全,以至没有这个功能都不好意思称作IDE。因此过度缩写已经没有太多的意义,而合理地缩写需要注意如下几点:

1 避免单个字缩写,毕竟给出a,不结合具体上下文很难猜出表示什么,可能是表示啊america、也可能表示age,可能性多到仅只能表示这是个字母,给它加上任何具体含义可能都是危险的。


2 避免随意缩写,比如网络连接的connection,可能目前比较常见的缩写就是con,如果缩写成connec就会感觉很奇怪,也很割裂。


3 避免缩写下成了另一个意思。比如advertising,一旦缩写成adv,就经常容易理解成advanced,毕竟很多设备的高级功能部分都是这样缩写的。同时类似的缩写容易造成信息丢失以至于有多个还原的可能性。比如即使adv限定在广告领域,那指的是Advertising(广告)还是Advertiser(广告主)呢?


4 建议建立部门和项目的“缩写对照表”。试想下为什么银行或者飞机航班的缩写为什么可以保证全球统一,比如中国银行的缩写BOC是全球通用的,因为有专门的机构负责这块的规范,正式缩写需要经过申请审批流程。编程中的缩写如果有类似的流程,就能更好保证缩写的规范一致,因此部门或者项目范围内可以维护常用缩写对照表,如需新的缩写就可以在表中进行查询、新增和发布。

FLAG命名

Flag是一个令人又爱又恨的东西,代码实现的时候是必须,但同时又经常滥用。Flag很方便,但一旦没有控制住,数量一多很容易造成理解障碍。具体有几个建议:


1 具体命名。最糟糕的就是都叫flag,不过很多FLAG变量的命名很容易造成歧义。比如“bool read_password = true;”,是需要读取密码,还是已经读取密码。在这种情况下need_password 或者user_is_authenticated会更好。


2是否进行了合理的状态机分析。很简单的一个例子,交通灯系统中大家经常根据当前需要不时添加isRedisGreenisYellow等等的标签,可能使用时很便利,但是FLAG的排列组合会造成状态数爆炸,其中很多组合是没有意义的,比如(isRed && isGreen)。如果进行了合理的状态分析,画出状态迁移图,事情就会变得简单很多,而且容易验证很多。


3 是否使用不必要的否定。比如是否使用ssl命名时use_ssl要比disable_ssl好理解多,特别在试想下!use_ssl !disable_ssl的情况,否定的否定通常需要大脑更多的处理时间。因此为了绕晕对手,诡辩的时候通常会巧妙地使用否定词,比如“我不认为这不是一个不好的方法”,不反复思考可能不好弄清楚我是怎么认为的。代码也是这样,命名不合理,通常照成阅读时候的卡壳和停顿。


bool disable_ssl = false;

VS

bool use_ssl = true;



4区分时态,主要体现在toishas,分别表示要做什么、正在做什么和已经做什么,这些简单的CET4英语知识对于命名同样重要,合理区分可以让代码阅读更流畅。如下代码就说了一个故事:检查当前状态,看是否可以要启动。如果要启动,判断是否正在运行,如果不是运行中就启动,否则就什么都不处理。最后根据是否判断是否运行过,给出相应提示。其中toStartisRunninghasRun就体现了细微的差别。类似的还有canshould,分别表示能不能和应该不应该。


toStart = checkCondition();

if(toStart) {

if(!isRunning) {

start();

isRunning = true;

hasRun = true; // isRunning 不同,stop的时候不置成false

}

}

if(hasRun)

showTipMessage(“之前运行过”);


上述仅仅是示例,如果实际代码这样写绝对是设计出现了问题,等着吐槽吧!

避免误解。

被误解是很受伤的一件事,但是我们的命名就尝尝别误解,而且还常常是整个意思反过来了。因此在检查名字的时候就是主动问下自己“别人对于这个名字是否有不同的解读?”。比如如下代码:


results = Database.all_objects.filter("year <= 2011")



其中,results会包含什么呢?


year属性<= 2011的对象?

year属性不<= 2011的对象?


问题就出在filter上,可能是过滤掉、也可能是过滤出。建议就是如果要过滤出,select()会更好,如果要过滤掉,exclude()会更好。


Reserve for the future(为将来预留)。

足球队通常会特别慎重对待10号,基本是要努力等到那个绝对中场核心的人才可能赋予,否则提早给了后面更强大的中场加入的时候就难办了,这不仅仅是号码的问题,更多的是地位和认同的问题。编码中也经常遇到这种情形,有些好词想到就用,太挥霍了,比如EngineController等等,可能当时的场景下还是有些牵强但还是用了,到后来真正需要的时候就比较尴尬:调整伤筋动骨,不调整概念又不一致。排期开发中就遇到这个问题,初期的时候就早早将Order用掉了,当真正的订单概念出现,需要用Order的时候已经很难调整了,直至现在还需要“忍”着。


几点建议

当然做好命名需要长期刻意训练,我个人也有一些小建议:

大量阅读好代码,学习一些精彩命名。

阅读少量糟糕代码,练习如何更好地命名。

Google帮忙,有些不确定的词可以谷歌一下,看看搜索结果,判断该词是否常用。

阅读代码相关的书籍,比如《CleanCode》、《The Art of Readable Code》等等,形成一些对代码的系统认识。