代码精进之路:从码农到工匠
上QQ阅读APP看书,第一时间看更新

1.3 有意义的命名

代码即文档,可读性好的代码应该有一定的自明性,也就是不借助注释和文档,代码本身就能显性化地表达开发者的意图。这种自明性在很大程度上依赖于我们对问题域的理解,以及命名是否合理。

通常,如果你无法想出一个合适的名字,很可能意味着代码“坏味道”、设计有问题。这时可以思考一下:是不是一个方法里实现了太多的功能?或者类的封装内聚性不够?又或者是你对问题的理解还不够透彻,需要获取更多的信息?

变量名应该是名词,能够正确地描述业务,有表达力。如果一个变量名需要注释来补充说明,那么很可能说明命名就有问题。

int d; // 表示过去的天数

观察上面的命名,我们只能从注释中知道变量d指的是什么。如果没有注释,阅读代码的人为了知道d的含义,就不得不去寻找它的实例以获取线索。如果我们能够按照下面这样的方式命名这个变量,阅读代码的人就能够很容易地知道这个变量的含义。

int elapsedTimeInDays;

类似的还有魔术数,数字86400应该用常量SECONDS_PER_DAY来表达;每页显示10行记录的,10应该用PAGE_SIZE来表达。

这样做还有一个好处,即代码的可搜索性,在代码中查找PAGE_SIZE很容易,但是想找到10就很麻烦了,它可能是某些注释或者常量定义的一部分,出现在不同作用的各种表达式中。

函数命名要具体,空泛的命名没有意义。例如,processData()就不是一个好的命名,因为所有的方法都是对数据的处理,这样的命名并没有表明要做的事情,相比之下,validateUserCredentials()或者eliminateDuplicateRequests()就要好许多。

函数的命名要体现做什么,而不是怎么做。假如我们将雇员信息存储在一个栈中,现在要从栈中获取最近存储的一个雇员信息,那么getLatestEmployee()就比popRecord()要好,因为栈数据结构是底层实现细节,命名应该提升抽象层次、体现业务语义。合理的命名可以使你省掉记住“出栈”的脑力步骤,你只需要简单地说“取最近雇员的信息”。

类是面向对象中最重要的概念之一,是一组数据和操作的封装。对于一个应用系统,我们可以将类分为两大类:实体类和辅助类。

实体类承载了核心业务数据和核心业务逻辑,其命名要充分体现业务语义,并在团队内达成共识,如Customer、Bank和Employee等。

辅助类是辅佐实体类一起完成业务逻辑的,其命名要能够通过后缀来体现功能。例如,用来为Customer做控制路由的控制类CustomerController、提供Customer服务的服务类CustomerService、获取数据存储的仓储类CustomerRepository。

对于辅助类,尽量不要用Helper、Util之类的后缀,因为其含义太过笼统,容易破坏SRP(单一职责原则)。比如对于处理CSV,可以这样写:

 CSVHelper.parse(String)
 CSVHelper.create(int[])

但是我更建议将CSVHelper拆开:

 CSVParser.parse(String)
 CSVBuilder.create(int[])

包(Package)代表了一组有关系的类的集合,起到分类组合和命名空间的作用。在JavaScript的早期阶段,因为缺乏明确的分包机制,导致程序(特别是大型程序)很容易陷入混乱。

包名应该能够反映一组类在更高抽象层次上的联系。例如,有一组类Apple、Pear、Orange,我们可以将它们放在一个包中,命名为fruit。

包的命名要适中,不能太抽象,也不能太具体。此处以上面提到的水果作为例子,如果包名过于具体,比如Apple,那么Pear和Orange放进该包中就不恰当了;如果报名太抽象,称为Object,而Object无所不包,这就失去了包用来限定范围的作用。

这里说的模块(Module)主要是指Maven中的Module,相对于包来说,模块的粒度更大,通常一个模块中包含了多个包。

在Maven中,模块名就是一个坐标: <groupId, artifactId>。一方面,其名称保证了模块在Maven仓库中的唯一性;另一方面,名称要反映模块在系统中的职责。例如,在COLA架构中,模块代表着架构层次,因此,对任何应该遵循COLA规范的应用都有着xxx-controller、xxx-app、xxx-domain和xxx-Infrastructure这4个标准模块。更多内容请参考12.3节。