前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

一、命名风格

1、类名 命名

类名使用 UpperCamelCase 风格,必须遵从驼峰形式,但以下情形例外:(领 域模型的相关命名) DO / BO / DTO / VO / DAO

影响: 代码可读性差

 正例 ForceCode / UserDO / HtmlDTO /TcpUdpDeal / TaPromotion
 反例 forcecode / UserDo / HTMLDto /TCPUDPDeal / TAPromotion

2、方法名、参数名、成员/局部变量 命名

1.1.2、方法名、参数名、成员/局部变量统一使用 lowerCamelCase,必须遵从驼峰形式

影响:反序列化或者 Lombok@Data赋值失败

正例: localValue / testMethod()

3、抽象类 命名

抽象类命名使用 AbstractBase开头

影响: 代码可读性差

正例 AbstractActionDemo

4、单元测试类 命名

单元测试类命名以它要测试的类的名称开始,以 Test结尾

影响: 代码可读性差

 正例 DemoTest

5、下划线和美元符号 使用

代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束

影响: 代码可读性差

反例:  _name / __name / $name / name_ / name$ / name__

6、异常类 命名

异常类命名使用 Exception 结尾

影响: 代码可读性差

CacheDemoException

7、常量 命名

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

影响: 代码可读性差

正例:MAX_STOCK_COUNT / CACHE_EXPIRED_TIME 

反例:MAX_COUNT / EXPIRED_TIME

8、 ServiceDAO类 使用

对于 ServiceDAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别

影响: 代码可读性差

正例public class DemoServiceImpl implements DemoService

9、包名 命名

包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式

影响: 代码可读性差

正例 com.jd.mpp.util/com.jd.tddl.domain.dto

10、POJO 类使用

1)布尔类型 命名

POJO 类中的任何布尔类型的变量,都不要加is,否则部分框架解析会引起序列化错误

影响: 部分框架解析会引起序列化错误

正例Boolean success
  
反例定义为基本数据类型Boolean isDeleted的属性当它的方法也isDeleted()框架在反向解析的时候,“误以为对应的属性名称是deleted导致属性获取不到进而抛出异常

2)禁止在 POJO 类中,同时存在对应属性 xxxisXxx()getXxx() 方法

禁止在 POJO 类中,同时存在对应属性 xxxisXxx()getXxx() 方法

影响:框架在调用属性 xxx 的提取方法时,并不能确定哪一个方法一定是被优先调用到的

3)类型与中括号紧挨相连来表示数组

类型与中括号紧挨相连来表示数组

影响: 代码可读性差

正例定义整形数组int[] arrayDemo;

反例在main参数中使用String args[]来定义

11、如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式

将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。

正例:

public class OrderFactory; 

public class LoginProxy; 

public class ResourceObserver;

12、枚举命名

枚举类名带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开

13、方法命名

场景 命名
saveBean
deleteBean
udpateBean
查-单记录查询 getBeane
查-列表记录查询 listBean
查-分页记录查询 pageBean

二、注释

1、字段和方法 注释

所有的字段和方法必须要用 javadoc 注释

影响:严重影响代码可读性和编码效率

2、枚举 注释

所有的枚举类型字段必须要有注释,说明每个数据项的用途

影响: 代码可读性差

public enum TestEnum {
      /**
      * 月份
      */
      MONTH;
}

3、单行 注释

方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/**/注释,注意与代码对齐

影响:影响代码可读性和编码效率

4、类注释

类必须有 @author(创建者)、@since(创建日期)、类功能描述等注释信息

5、中英文注释

与其“半吊子”英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持英文原文即可

6、代码修改记得改注释

代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。

说明:代码与注释更新不同步,就像路网与导航软件更新不同步一样,如果导航软件严重滞后,就失去了导航的意义

7、对于注释的要求

第一、能够准确反映设计思想和代码逻辑;

第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路; 注释也是给继任者看的,使其能够快速接替自己的工作

三、集合

1、集合转数组

使用集合转数组的方法,必须使用 toArray(T[] array),传入类型完全一样的数组,大小 list.size()

说明:使用 toArray 带参方法,数组空间大小的 length

1) 等于 0,动态创建与 size 相同的数组,性能最好。

2) 大于 0 但小于size,重新创建大小等于 size的数组,增加 GC 负担。

3) 等于 size,在高并发情况下,数组创建完成之后,size 正在变大的情况下,负面影响与2相同。

4) 大于 size,空间浪费,且在 size 处插入 null 值,存在 NPE 隐患。

 正例Integer[] b = (Integer [])c.toArray(new Integer[0]);

 反例直接使用toArray无参方法存在问题返回值只能是Object[]若强转其它类型数组将出现ClassCastException错误

Integer[] a =(Integer[])c.toArray();

2、集合初始化时,指定集合初始值大小

集合初始化时,指定集合初始值大小

说明:HashMap 使用 HashMap(int initialCapacity) 初始化,如果暂时无法确定集合大小,那么指定默认值(16)即可。

影响:浪费内存,降低性能


正例initialCapacity = (需要存储的元素个数 / 负载因子) + 1注意负载因子即loader factor默认为0.75如果暂时无法确定初始值大小请设置为16即默认值)。

反例HashMap需要放置1024个元素由于没有设置容量初始大小随着元素不断增加容量7次被迫扩大resize需要重建hash表当放置的集合元素个数达千万级别时不断扩容会严重影响性能

四、OOP

1、比较相等

1)Object的equals

Objectequals 方法容易抛出空指针异常,应使用常量或确定有值的对象来调用 equals

影响:导致业务逻辑错误

正例"test".equals(object); Objects.equals(a, b);

反例object.equals("test");

2)包装类对象之间值的比较,全部使用 equals 方法比较

所有的包装类对象之间值的比较,全部使用 equals 方法比较

影响:导致业务逻辑错误 (对于 Integer 在-128至127之间的值会在缓存里对象复用,区间外数据会产生新对象)

 正例:Objects.equals(a, b);

 反例:Integer.valueOf(a) == Integer.valueOf(b);

2、基本数据类型与包装数据类型的 使用

1、 所有的 POJO 类属性必须使用包装数据类型;

2、RPC 方法的返回值和参数必须使用包装数据类型

3、所有的局部变量推荐使用基本数据类型

说明:POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证

正例:数据库查询结果可能是null,因为自动拆箱,用基本数据类型接收有NPE风险。

反例: RPC方法的返回值使用基本类型,无法区分null值和0的区别

3、 DO / DTO / VOPOJO 类时,不要加任何属性默认值

说明:ORM框架根据列修改时,会将默认值覆盖数据库的存储值

反例 POJO类的createTime默认值为new Date()但是这个属性在数据提取时并没有置入具体值在更新其它字段时又附带更新了此字段导致创建时间被修改成当前时间

4) POJO 类必须写 toString方法

说明:使用工具类 source > generate toString 时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。打印对象信息日志,性能优于 JSON

影响: 降低编码效率,影响日志打印性能

public class ToStringDemo extends Super {

    private String secondName;

    @Override
    public String toString() {
        return (
            super.toString() +
            "ToStringDemo{" +
            "secondName=" +
            secondName +
            "}"
        );
    }
}

class Super {

    private String firstName;

    @Override
    public String toString() {
        return "Super{" + "firstName=" + firstName + "}";
    }
}

5)BigDecimal

1)精度问题

禁止使用构造方法 BigDecimal(double) 的方式把 double值转化为 BigDecimal 对象

影响: 数据精度丢失

说明: BigDecimal(double) 存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。

如:BigDecimal g = new BigDecimal(0.1f); 实际的存储值为:0. 100000000000000005551115123125782702118

正例:优先推荐入参为 `String` 的构造方法:

BigDecimal recommend1 = new BigDecimal("0.1"); 

BigDecimal recommen d2 = BigDecimal.valueOf(0.1);
或使用BigDecimal的valueOf方法,此方法内部其实执行了Double的toString,而 Double的toString按double的实际能表达的精度对尾数进行了截断。 

6、方法的参数个数建议不超过5个,方便代码阅读与理解

方法的参数个数建议不超过5个,方便代码阅读与理解

影响:降低编码效率,不方便阅读和理解

7、体的行数不能多于70行

影响: 降低编码效率,不方便阅读和理解

8、强转对象操作需要判断对象类型是否匹配

说明:强转之前必须使用 instanceof 进行类型判断

影响: 类型转换失败,抛出ClassCastException

理解:个人认为大可不必

9、字符串循环拼接

字符串循环拼接,不能用 + 连接, 需要用 StringBuilder 代替,提高性能

影响:浪费内存,影响性能

10、switch 控制语句

在一个 switch 块内,每个 case 要么通过 break / return 等来终止,要么注释说明程序将继续执行到哪一个case 为止;在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代码也没有

说明:注意break 是退出switch 语句块,而 return 是退出方法体。 没有 break 的情况下,增加 case 场景容易出现 bug

11、避免采用取反逻辑运算符

’!’运算符不利于快速理解

说明:取反逻辑不利于快速理解,并且取反逻辑写法必然存在对应的正向逻辑

12、并发规约

1)线程资源必须通过线程池提供,不允许在应用中自行显式创建线程

使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。

如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题

2)线程池不允许使用 Executors 去创建

线程池不允许使用Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的人员更加明确线程池的运行规则,规避资源耗尽的风险

说明:Executors返回的线程池对象的弊端如下:

1) FixedThreadPoolSingleThreadPool :允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM

2) CachedThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM

3)创建线程或线程池时请指定有意义的线程名称,方便出错时回溯

说明:创建线程池的时候请使用带ThreadFactory 的构造函数,并且提供自定义 ThreadFactory 实现或者使用第三方实现

4)避免Random实例被多线程使用

虽然共享该实例是线程安全的,但会因竞争同一 seed导致的性能下降

正例:可以使用 ThreadLocalRandom

public class RandomInThread extends Thread { 
private Random random = ThreadLocalRandom.current(); 
	@Override 
	public void run() { 
		long t = random.nextLong(); 
} 

反例:

public class RandomInThread extends Thread {
private Random random = new Random();
  @Override
  public void run() {
  	long t = random.nextLong();
  }
}

13、圈复杂度

圈复杂度 方法圈复杂度
圈复杂度-高: 方法圈复杂度大于50(等级:·BLOCKER
圈复杂度-中高: 方法圈复杂度大于40,小于等于50(等级:CRITICAL
圈复杂度-中: 方法圈复杂度大于30,小于等于40(等级:MAJOR
圈复杂度-低: 方法圈复杂度大于20,小于等于30(等级:WARNING

14、避免出现重复的代码

随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。

五、异常处理

1、异常不要用来做流程控制,条件控制

异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多

2、不随意catch

catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。

说明:对大段代码进行 try- catch ,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。

正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程序上作出分门别类的判断,并提示给用户。

3、捕获异常是为了处理它

捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。

4、定义时区分 unchecked / checked异常

避免直接抛出 new RuntimeException(),更不允许抛出 Exception 或者Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException 等。

5、错误码还是抛异常

对于公司外的 http / api 开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result方式,封装 isSuccess() 方法、“错误码”、“错误简短信息”。

问题:关于 RPC 方法返回方式使用 Result 方式的理由:

1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。

2)如果不加栈信息,只是 new 自定义异常,加入自己的理解的error message,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。

ContactAuthor