高质量代码的命名
本文与大家聊一聊编程中非常关键的一个点,如何更好的对代码命名。
《代码整洁之道》这本书提出了一个观点:代码质量与其整洁度成正比,干净的代码,既在质量上可靠,也为后期维护、升级奠定了良好基础。书中介绍的规则均来自作者多年的实践经验,涵盖从命名到重构的多个编程方面,虽为一“家”之言,然诚有可资借鉴的价值。
但我们知道,很多时候,理想很丰满,现实很骨感,也知道人在江湖,身不由己。因为项目的紧迫性,需求的多样性,我们无法时时刻刻都写出整洁的代码,保持自己输出的都是高质量、优雅的代码。
但若我们理解了代码整洁之道的精髓,我们会知道怎样让自己的代码更加优雅、整洁、易读、易扩展,知道真正整洁的代码应该是怎么样的,也许就会渐渐养成持续输出整洁代码的习惯。
而且或许你会发现,若你一直保持输出整洁代码的习惯,长期来看,会让你的整体效率和代码质量大大提升。
先放出这篇文章所涉及内容知识点的一张思维导图,就开始正文。大家若是疲于阅读文章正文,直接看这张图,也是可以Get到本文的主要知识点的大概。
名副其实说起来貌似很简单,但真正做起来,似乎没那么容易。选个好名字要花一些时间,但其实选好名字之后省下来的时间,要比之前选名字时花掉的时间多得多。
我们一旦发现有更好的名称时,就应该换掉之前的旧名称,这样做读你代码的人(包括你自己),都会很开心。
一个好的变量、函数或类的名称应该已经几乎答复了所有的大问题。它应该告诉你,这个名称所代表的内容,为什么会存在,做了什么事情,应该如何用等。
如果一个名称需要注释来补充才能让大家明白其真正含义,那其实就不算是名副其实。(并不是说不需要注释,恰如其分的注释是程序员让自己代码锦上添花的好方法,关于注释的一些注意事项,稍后会有文章专门涉及。)
举个栗子:
以下的这句代码里的d就不算是个好命名。名称d什么都没说,它没引起我们对时间消逝的感觉,更别说单位是天了:
- int d; // elapsed time in days||经过了几天时间
我们应该选择这样的指明了计量对象和计量单位的名称:
- int elapsedTimeInDays;
- int daysSinceCreation;
- int daysSinceModification;
- int fileAgeInDays;
我们应该避免留下隐藏代码本意的错误线索,也应该避免使用与本意相悖的词。例如,别用accountList来指一组账号,除非它真的是List类型,用accountGroup、bunchOfAccounts,或者直接用accounts,都是更好的选择。
尽量提防长得太像的名称。想区分XYZControllerForEfficientHandlingOfStrings和XYZControllerForEfficientStorageOfStrings,会花费我们太多的时间。因为这两个词,实在太相似了。
以同样的方式拼写出同样的概念才是信息,拼写前后不一致就是误导。
1.尽量避免使用数字系列命名(a1、a2…….aN)。这样的名称纯属误导,因为很多时候完全没有提供正确的信息,没有提供导向作者意图的线索。
2.废话是另一种没有意义的区分。如果我们有一个Product类,还有一个ProductInfo或ProductData类,那么他们的名称虽然不同,但意思却无区别。这里的Info、Data就像a、an和the一样,是意义含混的废话。
注意,只要体现出有意义的区分,使用a、the这样的前缀就没错。例如,将a用在域内变量,把the用于函数参数。
我们要使用读得出来的名称。如果名称读不出来,讨论的时候就会不方便且很尴尬,甚至让旁人觉得很蠢。
例如,变量名称是beeceearrthreecee,讨论的时候读起来简直像没吃药。
单字母和数字常量有个问题,就是很难再一大篇文字中找出来。
找MAX_CLASSED_PER_STUDENT很容易,但想找数字7,就很麻烦。
同样,字母e也不是个便于搜索的好变量名。因为作为英文中最常用的字母,在每个程序、每段代码中都有可能出现。
名称长短应与其作用域大小相对应,若变量或常量可能在代码中多处使用,应赋予其以便于搜索的名称。
举个栗子,比较如下两段代码:
- for (int j=0; j<34; j++)
- {
- s += (t[j]*4)/5;
- }
和
- const int WORK_DAYS_PER_WEEK = 5;
- int sum = 0;
- for (int j=0; j < NUMBER_OF_TASKS; j++)
- {
- int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
- int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
- sum += realTaskWeeks;
- }
按整洁代码的要求来评判,第一段代码会让读者不知所云,第二段代码比第一段好太多。第二段代码中,sum并非特别有用的名称,但至少他搜索得到。采用能表达意图的名称,貌似拉长了函数代码,但要想想看,WORK_DAYS_PER_WEEK要比数字5好找得多,而列表中也只剩下了体现我们意图的名称。
我们取名的时候要避免思维映射,不应当让读者在脑中把你的名称翻译为他们熟知的名称,也就是说取名不要绕弯子,而是要直白,直截了当。
在多数情况下,单字母不是个好的命名选择,除非是在作用域小、没有名称冲突的地方,比如循环。循环计数器自然有可能被命名为i,j或k(最好别用字母l),这是因为传统上我们惯用单字母名称做循环计数器。
程序员通常都是聪明人,聪明人有时会借助脑筋急转弯炫耀其聪明。而聪明程序员和专业程序员之间的区别在于,专业程序员了解,明确就是王道。专业的程序员善用其能,能编写出其他人能理解的代码。
类名尽量用名词或名词短语,比如Customer, WikiPage,Account, 或 AddressParser。
类名最好不要是动词。
方法名尽量用动词或动词短语。比如postPayment, deletePage, 或者save。
属性访问器、修改器和断言应该根据其value来命名,并根据标准加上get、set和is前缀。
举个栗子,这里的getName、setName等命名都很OK:
- string name = employee.getName();
- customer.setName("mike");
- if (paycheck.isPosted())...
而重载构造器时,使用描述了参数的静态工厂方法名。如:
- Complex fulcrumPoint =Complex.FromRealNumber(666.0);
通常好于:
- Complex fulcrumPoint = new Complex(666.0);
我们也可以考虑将相应的构造器设置为private,强制使用这种命名手段。
我们需给每个概念选一个词,并且一以贯之。
例如,使用fetch、retrieve和get来给在多个类中的同种方法命名,你怎么记得住哪个类中是哪个方法呢?
同样,在同一堆代码中混用controller、manager,driver,就会令人困惑。DeviceManager和Protocol-Controller之间有何根本区别?为什么不全用controller或者manager?他们都是Driver吗?这就会让读者以为这两个对象是不同的类型,也分属不同的类。
所以,对于那些会用到你代码的程序员,一以贯之的命名法简直就是天降福音。
我们应尽力写出易于理解的变量名,把代码写得让别人能一目了然,而不必让人去非常费力地去揣摩其含义。我们想要那种大众化的作者尽责地写清楚的通俗易懂的畅销书风格,而不是那种学者学院风的晦涩论文写作风格。
记住,只有程序员才会读你写的代码。所以,尽管去用那些计算机科学(Computer Science,CS)领域的专业术语、算法名、模式名、数学术语。
对于熟悉访问者(Visitor)模式的程序来说,名称AccountVisitor富有意义。给技术性的事物取个恰如其分的技术性名称,通常就是最靠谱的做法。
很少有名称是可以自我说明的。所以,我们需要用有良好命名的类,函数或名称空间来放置名称,给读者提供语境。若没能提供放置的地方,还可以给名称添加前缀。
举个栗子,假如我们有名为firstName、lastName、street、houseNumber、city、state和zipcode的变量。当他们搁一块儿的时候,的确是构成了一个地址。不过,假如只是在某个方法中看到一个孤零零的state呢?我们会推断这个变量是地址的一部分吗?
我们可以添加前缀addrFirstName、addrLastName、addrState等,以此提供语境。至少,读者可以知道这些变量是某个更大变量的一部分。当然,更好的方案是创建名为Address的类。这样,即便是编译器也会知道这些变量是隶属于某个更大的概念了。
另外,只要短名称足够好,对含义的表达足够清除,就要比长名称更合适。添加有意义的语境甚好,别给名称添加不必要的语境。
其实,取一个好名字最难的地方在于需要良好的描述技巧和共有的文化背景。与其说这是一种技术、商业或管理问题,还不如说这是一种教学问题。
不妨试试上面列出的这十二条规则与要点,看看你的代码可读性是否有所提升。而如果你是在维护别人的代码,或者是在重构,效果应该会是立竿见影的。
文章开头部分已经用思维导图的方式展现了本文的知识点,这边再贴出一个文字列表版,方便大家整理:
要点一:要名副其实。一个好的变量、函数或类的名称应该已经答复了所有的大问题。一个好名称可以大概告诉你这个名称所代表的内容,为什么会存在,做了什么事情,应该如何用等。
要点二:要避免误导。我们应该避免留下隐藏代码本意的错误线索,也应该避免使用与本意相悖的词。
要点三:尽量做有意义的区分。尽量避免使用数字系列命名(a1、a2…….aN)和没有意义的区分。
要点四:尽量使用读得出来的名称。如名称读不出来,讨论的时候会不方便且很尴尬。
要点五:尽量使用可搜索的名称。名称长短应与其作用域大小相对应,若变量或常量可能在代码中多处使用,应赋予其以便于搜索的名称。
要点六:取名不要绕弯子。取名要直白,要直截了当,明确就是王道。
要点七:类名尽量用名词。类名尽量用名词或名词短语,最好不要是动词。
要点八:方法名尽量用动词。方法名尽量用动词或动词短语。
要点九:每个概念对应一词,并一以贯之。对于那些会用到你代码的程序员,一以贯之的命名法简直就是天降福音。
要点十:通俗易懂。应尽力写出易于理解的变量名,要把代码写得让别人能一目了然,而不必让人去非常费力地去揣摩其含义。
要点十一:尽情使用解决方案领域专业术语。尽管去用那些计算机科学领域的专业术语、算法名、模式名、数学术语。
要点十二:要添加有意义的语境。需要用有良好命名的类,函数或名称空间来放置名称,给读者提供语境。若没能提供放置的地方,还可以给名称添加前缀。