Bootstrap

【编程规范】一文讲解开发中的命名规范

命名规范

好的代码本身就是注释, 所以我们需要统一命名风格。

​ 在本文中,将从大到小,从外到内,总结Java编程中的命名规范。文中将会涉及到日常工作中常见的命名示例,如包命名,类命名,接口命名,方法命名,变量命名,常类命名,抽象类命名,异常类命名以及扩展类命名等。我将按照项目工程目录结构,从包,类(接口,抽象类,异常类),方法,变量和常量的顺序展开介绍。

常量命名

1.命名说明

常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。

2.命名技巧

1、业务含义清晰。

在具名常量时,应该根据该常量所表示的含义,而不是该常量所具有的数值为该抽象事物命名。FIVE 是个很糟的常量名(不论它所代表的值是否为 5.0)。CYCLES_NEBDED 是个不错的名字。CYCLES_NEEDED 可以等于 5.0 或者 6.0。而 FIVE=6.0 就显得太可笑了。出于同样原因,BAKERS_DOZEN 就是个很糟的常量名;而DONUTS_MAX 则很不错。

2、允许任何魔法值(即未经定义的常量)直接出现在代码中。

3、long 或者 Long 初始赋值时,必须使用大写的 L,不能是小写的 l,小写容易跟数字 1 混淆,造成误解。

说明: Long a = 2l; 写的是数字的 21,还是 Long 型的 2?

4、不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护。如: 缓存相关的常量放在类: CacheConsts 下;系统配置相关的常量放在类: ConfigConsts 下。
说明 : 大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。

5、常量的复用层次有五层: 跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。

  1. 跨应用共享常量(两个不同的应用): 放置在二方库中,通常是 client.jar 中的 constant 目录下。
  2. 应用内共享常量(同一个应用): 放置在一方库的 modules 中的 constant 目录下。
    反例 : 易懂变量也要统一定义成应用内共享常量,两位攻城师在两个类中分别定义了表示“是”的变量:
类 A 中: public static final String YES = "yes";  
类 B 中: public static final String YES = "y";  
A.YES.equals(B.YES),预期是 true,但实际返回为 false,导致线上问题。
  1. 同一个应用下的子工程内部共享常量: 即在当前子工程的 constant 目录下。
  2. 包内共享常量: 即在当前包下单独的 constant 目录下。
  3. 类内共享常量: 直接在类内部 private static final 定义。

变量命名

1.命名说明

变量名是名词,要正确和清晰地描述业务语义

2.命名技巧

1、业务含义清晰。避免无业务语义的命名,如:list、val、a...;

2、语境范围精准。

避免小范围词套大范围数据,反之亦然,不使用过于宽泛的名词,如:date 看上去不错,但经过最后推敲它也只是个坏名字,因为这里所说的日期并不是所有的日期均可,而只是特指当前日期;而 date 本身并未表达出这层含义。

3、名词复数。统一风格,加s或List尾缀,变量名建议使用s尾缀,函数名建议使用List尾缀;

4、前置对仗词。位于变量前置部分,用于修饰后面的名词。

  • begin/end:beginTime,endTime。
  • first/last
  • locked/unlocked
  • min/max
  • next/previous
  • old/new
  • opened/closed
  • visible/invisible
  • source/target
  • source/destination
  • up/down

5、后置限定词。描述名词的作用范围属性,例如:

  • 请求入参:xxxQuery/xxxRequest
  • 返回结果:xxxResponse/xxxResult
  • 传参数据:xxxDTO/xxxVO/xxxInfo
  • 运算结果:xxxTotal(总和)/xxxMax(最大值)/xxxAverage(平均值)

6、变量长度控制。

当变量名的平均长度在10 到16 个字符的时候,调试程序所需花费的气力是最小的(1990)。平均名字长度在8到20个字符的程序也几乎同样容易调试。这项原则并不意味着你应该尽量把变量名控制在9到15或者10到16 个字符长。它强调的是,如果你查看自己写的代码时发现了很多更短的名字,那么你需要认真检查,确保这些名字含义足够清晰。

7、为特定类型的数据命名。

在为数据命名的时候,除了通常的考虑事项之外,为一些特定类型数据的命名还要求做出一些特殊的考虑。下面将讲述与循环变量状态变量临时变量布尔变量枚举类型具名常量有关的考虑事项。

  1. 循环变量。无论循环体内还是循环体外,都应该给变量赋予更符合语义的名称,而不是用i、j、k潦草定义。
  2. 状态变量。为状态变量取一个比 flag 更好的名字 。最好是把标记(flag)看做状态变量,标记的名字中不应该含有 flag,因为你从中丝毫看不出该标记是做什么的。
    1. 坏的名字:statusFlag = 50。除非你亲自写了这段代码,或者有文档能告诉你 statusFlag 和 50的含义。
    2. 好的名字:dataReady = true;recalcNeeded = false。
  1. 临时变量。无。
  2. 布尔变量。
    1. 谨记典型的布尔变量名。done表示某件事情已经完成;error表示有错误发生;found表示已经找到值....
    2. 使用肯定语气的布尔变量名。否定的名字如 notFound、 notdone 以及 notSuccessful等较难阅读,特别是如果它们被求反:if not notFound。
  1. 为枚举变量命名。
    1. 常量池则添加组前缀。在使用枚举类型的时候,可以通过使用组前缀,如 Color_,Planet_或者Month_来明确表示该类型的成员都同属于一个组。下面举一些通过前缀来确定枚举类型元素的例子:Color_Red,Color_Blue...
    2. 枚举类则不添加前缀。 在有些编程语言里,枚举类型的处理很像类,枚举成员也总是被冠以枚举名字前缀,比如 Color.Color_Red 或者 Planet. Planet_Earth(java语言就是这样)。如果你正在使用这样的编程语言,那么重复上述前缀的意义就不大了,因此你可以把枚举类型自身的名字作为前缀,并把上述名字简化为 Color. Red 和 Planet. Earth。
  1. 具名常量。在具名常量时,应该根据该常量所表示的含义,而不是该常量所具有的数值为该抽象事物命名。FIVE 是个很糟的常量名(不论它所代表的值是否为 5.0)。CYCLES_NEBDED 是个不错的名字。CYCLES_NEEDED 可以等于 5.0 或者 6.0。而 FIVE=6.0 就显得太可笑了。出于同样原因,BAKERS_DOZEN 就是个很糟的常量名;而DONUTS_MAX 则很不错。

8、POJO、DO等实体类中的变量不要以is开头。有些框架解析的时候会出错。

9、RPC‘接口返回值’以及‘接口入参值’,禁止使用枚举;枚举在你的系统内部使用即可。推荐在这个字段上注释一个枚举类型

3.基本数据类型

基本数据类型是构建其他所有数据类型的构造块 (building blocks )。本章包含了使用数(普遍意义上)、整数、浮点数、字符和字符串、布尔变量、枚举类型、具名常量以及数组的一些技巧。本章的最后一节将讲述如何创建自己的数据类型。

传送门 - 《代码大全2》第12章 基本数据类型

函数命名

1.命名说明

通常采用动词+名词。函数命名要体现做什么,而不是怎么做,要清楚表达出操作意图业务语义

2.命名技巧

1、动名词搭配,动词表达操作意图,名词表达业务语义。

public interface MemberFacade {
    /**
     * 查询会员信息
     * 
     * 命名的问题:
     *  1.没有表达操作意图:build是构建,没有表达查询的意图。
     *  2.没有表达业务语义:没有表达build的目标对象是什么。
     **/
    SingleResult<MemberDTO> build(MemberBuildRequest request);
    /**
     * 更新会员信息
     *
     * 命名的问题:
     *  1.没有表达业务语义:不知道具体更新会员的哪些信息
     *  2.操作意图表达过于宽泛:update可以把所有会员信息更新都放在这个函数里。
     **/
    SingleResult update(MemberUpdateRequest request);
}
public interface MemberFacade {
    /**
     * 查询会员信息
     **/
    SingleResult<MemberDTO> queryMember(MemberQuery query);
    /**
     * 更新会员昵称
     **/
    SingleResult updateNick(MemberUpdateRequest request);
}

2、名词复数。变量名建议使用s尾缀,函数名建议使用List尾缀;

3、正反操作使用对仗词,例如:

  • add/remove
  • open/close
  • begin/end
  • insert/delete
  • first/last
  • min/max

类命名

1.命名说明

类是面向对象中最重要的概念,是一组关联数据的相关操作的封装,通常可以把类分为两种:

1)实体类:通常采用名词承载业务的核心数据和业务逻辑,命名要充分体现业务语义,比如Order/Buyer/Item。

2)辅助类:通常采用名词+功能后缀协调实体类完成业务逻辑,命名通常加后缀体现出其功能性,比如OrderQueryService/OrderRepository。

2.命名技巧

1、辅助类尽量避免用 Helper/Util 之类的后缀,因为其含义过于笼统,容易破坏单一职责原则。

2、针对某个实体的辅助操作过多,或单个操作很复杂,可通过 “实体 + 操作类型 + 功能后缀”来命名,同时符合职责单一和接口隔离的原则,比如OrderService:

  • OrderCreateService:订单创建服务;
  • OrderUpdateService:订单更新服务;
  • OrderQueryService:订单查询服务。

3、如果使用了设计模式,类名中应该体现出具体的模式。将设计模式体现在名字中,有利于阅读者快速理解架构设计思想。

public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;

接口命名

1.命名说明

接口(Interface)是一种表述某一类型对象动作的特殊类;简单来说,接口也是类(不太严谨),所以,接口的名称的书写也应该符合类名书写规范。

与普通类名不同的是,接口命名时通常采用形容词动词来描述接口的动作行为,也可以配合名词一起使用。

2.命名技巧

1、可以interface后缀结尾。

2、Oracle Java中一些标准库的接口使用形容词命名示例:

public interface Closeable{
    //...
}
public interface Cloneable{
    //...
}
public interface RunnableP{
    //...
}
public interface Comparable<T>{
    //...
}
public interface CompletionService<V>{
    //...
}
public interface Iterable<T>{
    //...
}
public interface EventListener{
    //...
}

3、在Spring Framework标准库中,通常采用名词+动词/形容词的组合方式来命名接口,下列是Spring Framework中一些接口命名示例:

public interface AfterAdvice{
    //...
}
public interface TargetClassAware{
    //...
}
public interface ApplicationContextAware{
    //...
}
public interface MessageSourceResolvable{
    //...
}

抽象类命名

1.命名说明

抽象类(Abstract Class)是一种特殊的类,其命名与普通类的命名规范相当。

一般地,为了将抽象类与普通类和接口做出区别,提高抽象类的可读性,在命名抽象类时,会以“Abstract”/“Base”作为类命的前缀。

2.命名技巧

1、以“Abstract”/“Base”作为类命的前缀。

2、编程中一些常规的命名示例:

public abstract class AbstractRepository<T>{
    //...
}
public abstract class AbstractController{
    //...
}
public abstract class BaseDao<T,ID>{
    //...
}
public abstract class AbstractCommonService<T>{
    //...
}

3、Spring Framework中常见的抽象类示例:

public abstract class AbstractAspectJAdvice{
    //...
}
public abstract class AbstractSingletonProxyFactoryBean{
    //...
}
public abstract class AbstractBeanFactoryPointcutAdvisor{
    //...
}
public abstract class AbstractCachingConfiguration{
    //...
}
public abstract class AbstractContextLoaderInitializer{
    //...
}

异常类命名

1.命名说明

异常类(Exception Class)也是类的一种,但与普通类命名不同的是,异常类在命名时需要使用“Exception”作为其后缀。

另外,在Java中还有另外一类异常类,它们属于系统异常,这一类异常类的命名使用“Error”作为其后缀,以区分Exception(编码,环境,操作等异常)。

2.命名技巧

1、Exception或者Error结尾。

2、exception示例:

public class FileNotFoundException{
    //...
}
public class UserAlreadyExistException{
    //...
}
public class TransactionException{
    //...
}
public class ClassNotFoundException{
    //...
}
public class IllegalArgumentException{
    //...
}
public class IndexOutOfBoundsException{
    //...
}

3、error示例:

public abstract class VirtualMachineError{
    //...
}
public class StackOverflowError{
    //...
}
public class OutOfMemoryError{
    //...
}
public class IllegalAccessError{
    //...
}
public class NoClassDefFoundError{
    //...
}
public class NoSuchFieldError{
    //...
}
public class NoSuchMethodError{
    //...
}

测试类命名

1.命名说明

在项目中,测试类采用被测试业务模块名/被测试接口/被测试类+“Test”后缀的方法进行书写,测试类中的测试函数采用“test”+用例操作_状态的组合方式进行书写。

2.命名技巧

1、测试类以Test结尾。

2、测试方法test+用例操作_状态。如:

public class UserServiceTest{

    public void testFindByUsernameAndPassword(){
        //...
    }

    public void testUsernameExist_notExist(){
        //...
    }

    public void testDeleteById_isOk(){
        //...
    }
}

包命名

1.命名说明

通常,包命使用小写英文字母进行命名,并使用“.”进行分割,每个被分割的单元只能包含一个名词

一般地,包命名常采用顶级域名作为前缀,例如com,net,org,edu,gov,cn,io等,随后紧跟公司/组织/个人名称以及功能模块名称。

package org.springframework.boot.autoconfigure.cloud
package org.springframework.boot.util
package org.hibernate.action
package org.hibernate.cfg
package com.alibaba.druid
package com.alibaba.druid.filter
package com.alibaba.nacos.client.config
package com.ramostear.blog.web

2.命名技巧

1、包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。

2、包的命名要大小适中,不能太具体,也不能太抽象。

  1. 比如类Apple、Orange都是水果,可以收纳进fruit包内。
  2. 比如包名叫Apple,太具体导致类Orange放不进去。
  3. 又比如包名叫food,太抽象导致其他非水果也被放进来了。

3、实际工程中,常见的分类维度主要是两种,按功能性或业务域分类。

  • 功能性分类:metaq、mapper、service、dao等;
  • 业务域分类:user、item、order、promotion等。

4、同一层级的包,要严格保持分类维度的一致性,要么先按业务域分类,再按功能性分类;要么就先按功能性分类,再按业务域分类。

client
  |----request
          |----order
          |----item
  |----response
          |----order
          |----item
  |----service
          |----OrderQueryService.java
          |----ItemQueryService.java

其他命名规范

泛型命名

在书写泛型类时,通常做以下的约定:

  • E表示Element,通常用在集合中;
  • ID用于表示对象的唯一标识符类型
  • T表示Type(类型),通常指代类;
  • K表示Key(键),通常用于Map中;
  • V表示Value(值),通常用于Map中,与K结对出现;
  • N表示Number,通常用于表示数值类型;
  • ?表示不确定的Java类型;
  • X用于表示异常;
  • U,S表示任意的类型。

下面时泛型类的书写示例:

public class HashSet<E> extends AbstractSet<E>{
    //...
}
public class HashMap<K,V> extends AbstractMap<K,V>{
    //...
}
public class ThreadLocal<T>{
    //...
}
public interface Functor<T,X extends Throwable>{
    T val() throws X;
}
public class Container<K,V>{
    private K key;
    private V value;
    Container(K key,V value){
        this.key = key;
        this.value = value;
    }
    //getter and setter ...
}
 
public interface BaseRepository<T,ID>{
    T findById(ID id);
 
    void update(T t);
 
    List<T> findByIds(ID...ids);
}
 
public static <T> List<T> methodName(Class<T> clz){
    List<T> dataList = getByClz(clz);
    return dataList;
}

参考资料

  • 《代码大全2》
  • 《阿里巴巴开发规范》
  • 《阿里技术》

;