代码命名规范

635 阅读6分钟

变量命名:

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

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

函数命名:

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

类命名:

类是面向对象中最重要的概念之一,是一组数据和操作的封装。对于一个应用系统,我们可以将类分为两大类:实体类和辅助类。 实体类承载了核心业务数据和核心业务逻辑,其命名要充分体现业务语义,并在团队内达成共识。 如Customer、Bank和Employee等。 辅助类是辅佐实体类一起完成业务逻辑的,其命名要能够通过后缀来体现功能。 例如,用来为Customer做控制路由的控制类CustomerController、提供Customer服务的服务类CustomerService、获取数据存储的仓储类CustomerRepository。

对于辅助类,尽量不要用Helper、Util之类的后缀,因为其含义太过笼统,容易破坏SRP(单一职责原则)。 比如对于处理CSV,可以这样写: CSVHelper.parse(String) 但是我更建议将CSVHelper拆开: CSVParser.parse(String) CSVBuilder.create(int[])

包命名:

包名应该能够反映一组类在更高抽象层次上的联系。 例如,有一组类Apple、Pear、Orange,我们可以将它们放在一个包中,命名为fruit。 包的命名要适中,不能太抽象,也不能太具体。此处以上面提到的水果作为例子,如果包名过于具体,比如Apple,那么Pear和Orange放进该包中就不恰当了;如果报名太抽象,称为Object,而Object无所不包,这就失去了包用来限定范围的作用。

一致性:

每个概念对应一个词,并且一以贯之。 例如,fetch、retrieve、get、find和query都可以表示查询的意思,如果不加约定地给多个类中的同种查询方法命名,你怎么记得是哪个类中的哪个方法呢?同样,在一段代码中,同时存在manager、controller和handler,会令人感到困惑。

image.png

遵守对仗词的命名规则有助于保持一致性,从而提高代码的可读性。 像first/last这样的对仗词就很容易理解;而像fileOpen()和fClose()这样的组合则不对称,容易使人迷惑。下面列出一些常见的对仗词组:

image.png

后置限定词加到名字的最后,并在项目中贯彻执行,保持命名风格的一致性。

这种方法有很多优点。首先,变量名中最重要的部分,即为这一变量赋予主要含义的部分应位于最前面,这样可以突出显示,并会被首先阅读到。其次,可以避免同时在程序中使用totalRevenue和revenueTotal而产生的歧义。如果贯彻限定词后置的原则,我们就能收获一组非常优雅、具有对称性的变量命名,例如revenueTotal(总收入)、expenseTotal(总支出)、revenueAverage(平均收入)和 expenseAverage(平均支出)。

统一技术语言

有些技术语言是通用的,业内人士都能理解,我们应该尽量使用这些术语来进行命名。 这些通用技术语言包括DO、DAO、DTO、ServiceI、ServiceImpl、Component和Repository等。例如,在代码中看到OrderDO和OrderDAO,马上就能知道OrderDO中的字段就是数据库中Order表字段,对Order表的操作都在OrderDAO里面。

中间变量

我们可以通过添加中间变量让代码变得更加自明,即将计算过程打散成多个步骤,并用有意义的变量名来命名中间变量,从而把隐藏的计算过程以显性化的方式表达出来。 例如,我们要通过Regex来获得字符串中的值,并放到map中。

Matcher matcher = headerPattern.matcher(line);
if(matcher.find()){
    headers.put(matcher.group(1), matcher.group(2));
}

用中间变量,可以写成如下形式:

Matcher matcher = headerPattern.matcher(line);
if(matcher.find()){
    String key = matcher.group(1);
    String value = matcher.group(2);
    headers.put(key, value);
}

中间变量的这种简单用法,显性地表达了第一个匹配组是key,第二个匹配组是value。只要把计算过程打散成一系列良好命名的中间值,不透明的语义自然会变得透明。

设计模式语言

使用设计模式,我们有必要在命名上就将设计模式显性化出来,这样阅读代码的人能很快领会到设计者的意图。 例如,Spring里面的ApplicationListener就充分体现了它的设计和用处。通过这个命名,我们知道它使用了观察者模式,每一个被注册的ApplicationListener在Application状态发生变化时,都会接收到一个notify。这样我们就可以在容器初始化完成之后进行一些业务操作,比如数据加载、初始化缓存等。

小心注释

不要复述功能。 为了复述代码功能而存在的注释,主要作用是弥补我们表达意图时遭遇的失败,这时要考虑这样的注释是否是必需的。如果编程语言足够有表达力,或者我们擅长用代码显性化地表达意图,那么也许根本就不需要注释。因此,在写注释时,你应该自省自己是否在表达能力上存在不足,真正的高手是尽量不写注释。

要解释背后意图。 注释要能够解释代码背后的意图,而不是对功能的简单重复。例如,我们在一个系统中看到如下代码:

try {
    // 在这里等待2秒
    Thread.sleep(2000);
} catch (InterruptedException e) {
    LOGGER.error(e);
}

这里的注释和没写是一样的,因为它只是对sleep的简单复述。正确的做法应该是阐述sleep背后的原因,比如改写成如下形式就会好很多。

try {
    // 休息2秒,为了等待关联系统处理结果
    Thread.sleep(2000);
} catch (InterruptedException e) {
    LOGGER.error(e);
}

或者直接用一个private方法将其封装起来,用显性化的方法名来表达意图,这样就不需要注释了。

private void waitProcessResultFromA( ){
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        LOGGER.error(e);
    }
}