日常编码规范(Java版)

规范:

    1. 命名:
      1. 接口命名。接口必须是名词,并且接口是能准确的描述要做的事情,命名能清晰的看出输入输出,可以是抽象的行为描述。接口必须以一个动作的名词形式结尾,比如reader,handler等。
        接口的命名,必须是抽象的,除非接口本身和具体实现紧密相关,否则不应该在接口中包含任何和具体实现相关的名词。
        接口命名根据行为分为以下几种:
        1. 读取某个数据,命名: {数据名称}Reader/Loader,如果是读取列表可以是 {数据名称}ListReader,或者 名称复数形式+Reader
          1. xxxReader 读取数据的接扣
          2. xxxLoader 加载数据的接口
        2. 进行某件事情,比如进行一个订单金额的计算,OrderCalculatePerformer, {名词}{动词}Performer。
        3. xxxHelper 某些要共用的帮助
        4. xxxMaker 生成DTO VO的接口,生成数据的接口,比如 PaperDTOMaker,OrderVOMaker
        5. xxxHandler 处理某个事件
        6. xxxConverter 数据库对象、DTO、VO等数据承载类的转换器
        7. xxxInterceptor 拦截某个时间,一般只有一个intercept方法,返回boolean表明拦截是否成功
        8. xxxFiller 对DTO VO 根据业务逻辑进行字段set填充的接口,一般filler只有一个方法fillXXX,传入DTO VO和其他参数,且没有返回值,比如:
        9. xxxFinder 通过其他服务来查找数据的接口
      2. 接口方法命名:
        1. 方法必须是描述一个动作,比如 getOrder, listOrders,方法以动词开头,{动词},或者 {动词}{名词} 的形式结尾,比如 handle getOrder。
        2. 常用的几个动词:
          1. getXXX 返回单个对象
          2. listXXX(复数) 返回一组数据,可以是list,或者set等collection
          3. saveXXX 保存数据
          4. deleteXXX, removeXXX 删除、清理数据
          5. createXXX addXXX
      3. 数据对象命名:
        1. 常规的公认的对象类型简写,可使用
          1. DTO 数据传输对象
          2. VO viewObject 展示对象,一般提供给前端
          3. BO businessObject ,业务对象
          4. info 数据信息结尾
        2. 数据库对象,全部用Do结尾,所有以DO结尾的对象,都是特制mybatis 映射出来的和数据库表对应的对象。
        3. 不以DO结尾的数据对象,比如DTO VO等,是暴露给外部api或者内部使用的,非数据库对象。和数据库对象之间使用xxxConverter接口进行转换。
      4. 包名:
        1. 包名、文件夹名称,必须是小写,不能是多个单词。有且只能有单个单词,除了约定俗成的简写,不能使用简写。
          举例:正确的: order.detail  错误:orderdetail  orderDetail od 等。
          正确的:com.dt.school.web.action.impl
          包名的单词可以是名词、动词等,约定俗成的简写有:vo bo dto impl
        2. 接口所在的包名,要准确的描述此接口在业务上的含义,此包名不应该包含任何和具体实现有关的信息。
          1. 正确的:com.dt.school.grade.GradeReader
          2. 错误的:com.dt.school.grade.mysql.GradeReader 错误原因:接口所在的包,包含了具体的存储实现mysql。
        3. 实现类所在的包名可以包含具体的实现信息,比如一个接口有mysql和redis两种实现类,那实现类所在的包名可以分为mysql和redis。
      5. 接口实现类命名:
        1. 当某个接口在短期内只有唯一的一个实现时,实现类的命名为:{接口名称}Impl, 比如接口:OrderCalculatePerformer 对应的唯一实现类为:OrderCalculatePerformerImpl,并且放置在接口目录下的 子目录 impl 中。
        2. 当接口有多个实现时,多个实现类应该根据具体的实现细节来确定实现名称,此名称应该很简单易懂的表明此实现的实现细节,对于使用者来说,从名称就非常容易判断此实现的具体信息。
          比如:接口: orderDetailReader, 有两个实现:MysqlOrderDetailReaderImpl RedisOrderDetailReaderImpl 从名字上能简单的分清楚此实现的细节,具体从数据库还是从redis获取。
        3. 接口有多个实现类时,实现类可以在不同的包名下的impl文件夹中。
        4. 不要介意实现类的类名太长,只要是能准确的描述实现类的功能或者使用方式,都是好的命名。
    2. 设计
      1. 基于接口设计
        1. 永远基于接口编程。对任何具体实现类的引用,都应该通过接口来调用,而不是直接使用实现类。
        2. 我们的业务项目,都是通过Spring框架来进行。bean管理,在项目内,不要使用静态类、不要使用静态方法,任何静态类和静态方法,比如帮助类,都应该转换为接口和对应的唯一实现类。
        3. 一个接口,通常应该只有一个方法,确保接口本身是纯粹的。除非万不得已,不应该为接口增加新的方法。目前我们的center服务中,只有两种特例,一种是:mapper接口,作为数据库映射,当有新的数据库查询语句时,mapper接口会增加新的方法。另外一种是对外暴露的服务接口,会因为业务发展,新增或修改方法。除了这两种接口外,其他内部使用或者作为中间件对外暴露的接口,一旦使用,不应该修改。
        4. 每个接口应该是干且只干一件事情,所以接口设计应该分层次,不同的层次实现不同的事情,层次之间应该只通过接口连接。
      2. 实现类
        1. 任何实现类的代码不应该超过250行,包括所有import。超过250行的实现类,证明你的代码逻辑拆分的不够细。不管是不是通用的代码块,只要是能相对独立的业务模块,都应该拆分为新的独立的接口和对应的实现。
        2. 单个方法内,代码行数不能超过80行,超过80行,证明你的代码分离度不够、抽象有问题。
        3. 实现类,不能有超过3层缩进的代码块。不能有多层缩进的if else 等逻辑判断。代码编写的原则是尽早返回,如果不满足约束条件,应该return提前终止。
        4. 多层次的条件判断,应该把中间的代码块抽成接口和对应的多个实现,然后通过简单的Map<key, 接口实现> 来实现。举个例子:
          现在有一个boolean判断,true的时候进入某个逻辑,false的时候进入另外一个逻辑。这时候应该抽出一个公共接口,然后增加两个此接口的实现,分别对应true和false的情况。然后在这个判断所在的实现类的构造函数中,初始化一个hashMap<Boolean, xxxInterface> ,插入两个kv,k分别是true false,v分别是两个实现类的实例。最后再使用的地方,通过 map.get(boolean) 获得接口的实现,再调用接口的方法。
        5. 不能有冗余代码,冗余的代码都是不好的设计,应该把冗余代码抽象提取出接口和实现。
        6. 实现类只能通过构造函数注入新的bean引用。
        7. 实现类本身有两种标记初始化方式,一种是@Component 标记到类上,一般此种用于构造函数较为简单的、注入外部bean比较少的实现类初始化。
          另外一种是:通过 config目录下,新建xxxConfiguration 并且标记 @configuration ,然后在这个配置中通过新增 标记为@ bean 的新方法,在此方法内,组织需要被引用的bean,然后通过new 对象来生成实现类的实例。
          建议:如果实现类比较复杂,配置需要更加灵活,建议通过第二种方式初始化,这样保证实现类本身的代码不需要修改,只需要修改configuration就能生效。
        8. 荐单例的实现类,都通过bean管理,而不是在其他bean单例中初始化唯一的实例。因为通过bean管理的单例,spring会维持bean之间的依赖关系,在应用程序关闭时,会根据依赖关系按照层次来关闭。对于持有连接池的类,非常重要。除此之外,通过bean管理的单例,可以在UT的时候进行mock和fake。
      3. 接口方法
        1. 不要使用方法重载。方法重载的可读性较差。不方便维护。
        2. 方法参数上必须使用jetbrains NotNull 和Nullable注释,标记方法参数是否允许为空或者不能为空。方法返回值上必须标明返回值是否可能为空。

           

        3. 接口的注解和方法、参数注解必须齐全。
        4. 方法的参数,不能超过两个,并且当为两个时,不能是同一种数据类型,比如 handle(String,String) 禁止此类方法。如果不满足这个条件,使用builder模式生成方法参数。
        5. 当接口有超过2个以上的参数,特别是参数中有相同数据类型时,是非常不方便调用方明确含义的。在调用方未拿到source code jar时,多个参数,很容易传错,而且调用方的代码,含义不明确,更要命的是,如果方法有多个重载,支持更多的参数时,非常容易出错。
        6. 建议使用lombok 的 at Builder 注释,增加到DTO对象上。自动提供builder模式。

        7. 使用lombok 的 at Builder 注释,要注意一个地方:只标记builder,会将dto对象的无参数构造函数取消,这样在对外提供服务时,json反序列化会有问题。所以如果你这个DTO对象要通过feign或者web接口提供给外部使用,必须同时标记

          @Builder
          @NoArgsConstructor
          @AllArgsConstructor


          否则RPC服务调用时会json反序列化错误。如果DTO仅仅在你的项目内被引用。可以只标记builder。
        8. 如果你的dto对象中有set list等集合属性,建议用以下方式自定义builder,对集合对象使用putXX putXXs 两个方法来允许添加一个或多个集合,方便使用。

          builder 可选

          1. 通过使用Builder模式来构造DTO,解决此问题
          2. 构造DTO对象 abc
          3. 构造DTO对象对应的builder abcBuilder
          4. 两者必须是同一个包下。且builder必须独立于对应的dto,注意:有些书籍中建议builder作为dto的内部类,但因为我们的dto要经过dubbo spring cloud 序列化。内部类builder会导致序列化异常。
          5. dto中设置private 字段,并实现get set,注意:set方法对builder模式不是必须的,但是如果没有相应set方法,会导致spring cloud序列化出错。所以要保留set方法。
          6. builder中设置同名的protected字段。builder例子:

             

          7. 在builder中,增加方法用于传入参数,方法名建议用介词:for, on, of, with, by and etc 不建议用set。以免混淆。比如 forOrder, byPartner, withCode, onChannel在DTO中,保证有一个默认构造函数,一个传入builder的构造函数,例如:

             

          8. 在Builder中,最后有一个 build() 方法,这个build方法内,直接return new abc(this) 即可。
          9. 如果DTO要求某些字段必须有值,可以在build方法return前,对参数进行简单的校验。注意:此处校验,不要包含任何可能会变化的业务逻辑,只对数据是否null等进行基础校验,如果不确定对象是否永远不能为空,请勿在build方法中校验,因为Builder类是随着API一起发布出去的,在调用方引入的,任何包含可能会变的业务校验,都将依赖于调用方更新API包才能实现。所以更复杂的DTO校验,请在服务提供方进行。
          10. DTO必须有空的构造函数,否则序列化会出错。
          11. DTO建议通过IntelliJIDEA 自动生成toString重载,例子:

             

          12. 这样比较方便debug调试。
          13. 当DTO对象有List等集合字段时,可以通过下面的builder例子来设置此集合,避免调用方必须先创建集合,再传入。

             

          14. 全部实现builder模式后,对于调用方。代码和参数含义非常清楚明细,层次结构优雅,以下是一个复杂的多层次的builder模式构造出来的对象,构造对象的代码

             

          15. builder模式下,构建DTO是通过链式调用,并且最后build()来完成,类似java stream的操作。链式调用和stream操作一样,每个链式调用都要换行,保证代码层次清晰。
      4. 框架使用
        1. 所有String类型的操作,都不使用JDK原生的String操作,而是统一使用commons-lang3 StringUtils等框架类,对字符串进行操作。
        2. 所有时间操作和存储,禁止使用calendar类,传输可使用Date类型,时间操作,必须使用jodaTime 或者java8 中的LocalTime等。
        3. 返回集合数据的时候,禁止返回null,如果没数据要返回,返回empty的list set等。
        4. 接收集合数据判断时,必须使用Spring自带的CollectionUtils.isEmpty 进行判断,禁止直接判断=null 或者size()>0。
      5. 数据对象
        1. 任何POJO对象,不管是mapper生成的数据库映射对象还是对外输出的对象。应该是纯粹的对象,只能包含private的字段,和对应的get set方法,boolean类型的字段,不能是isXXX的命名。所有字段get set必须是纯粹的,不能加入任何逻辑判断。所有字段不能有初始值,不能有自定义的构造函数。所有字段必须是对象类型,不能有基本类型。目的:保证dto对象纯粹无默认值,确保双方约定下,没有理解差异。
        2. 内部使用或者输出时,提供对应的builder类,用来生成此对象,builder类中可以有初始化初始值的功能,和部分简单的字段范围校验。
        3. 根据需要重载toString方法,使用commons-lang3中的toStringBuilder生成对象的字符串表达。

           

        4. 如果对象需要进行比较、放入set等操作,建议重载hashCode equals,使用commons-lang3 EqualsBuilder自动生成下列代码:

           

      6. lombok的使用
        1. 通过lombok插件,简化部分代码,以下是允许使用的lombok 功能
          1. val (final) var (非final) 在方法内部用来简化代码,实现类似java10种引入的var语法糖
          2. Getter Setter,在dto vo等类上添加这两个注释,可以自动将private变量生成对应的get set,特别注意:当使用了这两个注释后,必须在 src/main/java 下增加lombok.config 文件,并且增加以下配置:lombok.getter.noIsPrefix=true。这个配置用来覆盖lombok默认行为:对boolean类型的字段,追加is前缀。而是让lombok生成常规的getEnabled() 这种方法。
          3. EqualsAndHashCode ,在dto vo等类上增加此注释,用来自动重写这两个方法。如果此类继承自其他父类,请确保父类也是使用此注释重写的这两个方法。并且使用:@EqualsAndHashCode(callSuper=true) 来表明继承父类。对于不参与判断的字段,使用注释 @EqualsAndHashCode.Exclude 进行排除。特别说明:当类参与比较排序放入set等情况时,务必合理选择需要参与的字段,并不是每个字段都一定要参与计算。
          4. Data 如果不需要自定义toString的场景,可以使用Data注释
        2. 不允许使用的lombok注释
          1. NonNull ,和jetBrains的notNull重复,直接使用notNull即可。
          2. Cleanup,对资源池的关闭等,使用try with resource的原生功能
          3. toString, 请使用commons-lang3的ToStringBuilder,相比lombok,提供了更加灵活的配置型,比如对super的tostring处理,字符串可以输出为json格式。
          4. RequiredArgsConstructor and AllArgsConstructor,我们的dto vo要求不显性制定任何构造函数,全部通过builder生成,dto vo本身要非常纯粹,不能有自定义构造函数
          5. 其他注释不不常用,不能使用。
posted @ 2020-07-24 17:21  Jony.K.Chen  阅读(2043)  评论(0编辑  收藏  举报