您的当前位置:首页正文

JAVA基础 - 开发规范

2024-11-08 来源:个人技术集锦

工程结构规范

每个项目必须拥有三个功能模块,分别为api、client、provider三个功能包。依赖关系图:

编程规范

命名风格
  • 【强制】方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase风格。
  • 【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
  • 【强制】抽象类命名使用Abstract或Base开头;异常类命名使用Exception结尾;测试类命名以它要测试的类的名称开始,以Test结尾。
  • 【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。
  • 【推荐】在常量与变量的命名时,表示类型的名词放在词尾,以提升辨识度。

        正例:startTime / workQueue / nameList / TERMINATED_THREAD_COUNT

  反例:startedAt / QueueOfWork / listName / COUNT_TERMINATED_THREAD

  • 【推荐】如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。

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

正例:public class OrderFactory; public class LoginProxy; public class ResourceObserver;

  • 【强制】接口和实现类的命名规则: 对于 RemoteService类,基于 SOA 的理念,暴露出来的服务一定是接口。正例: OrderHeaderController实现 OrderRemoteService接口。
  • 【强制】枚举类名带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。

说明:枚举其实就是特殊的常量类,且构造方法被默认强制是私有

正例:枚举名字为ProcessStatusEnum的成员名称:SUCCESS / UNKNOWN_REASON。

常量定义
  • 【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
  • 【强制】在long或者Long赋值时,数值后使用大写的L,不能是小写的l,小写容易跟数字混淆,造成误解。说明:Long a = 2l; 写的是数字的21,还是Long型的2。
  • 【推荐】不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
  • 【推荐】如果变量值仅在一个固定范围内变化用enum类型来定义。

说明:如果存在名称之外的延伸属性应使用enum类型,下面正例中的数字就是延伸信息,表示一年中的第几个季节。

OOP规约
  • 【强制】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
  • 【强制】所有的覆写方法,必须加@Override注解。

说明:getObject()与get0bject()的问题。一个是字母的O,一个是数字的0,加@Override可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。

  • 【强制】不能使用过时的类或方法。。
  • 【强制】Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。

正例:"test".equals(object); 反例:object.equals("test");

说明:推荐使用java.util.Objects#equals(JDK7引入的工具类)。

  • 【强制】浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用equals来判断。

说明:浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指数”的表示方式。二进制无法精确表示大部分的十进制小数。

正例1:指定一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的。

正例2:使用BigDecimal来定义值,再进行浮点数的运算操作。

  • 【强制】定义数据对象DO类时,属性类型要与数据库字段类型相匹配。

正例:数据库字段的bigint必须与类属性的Long类型相对应。

  • 【强制】禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象。

说明:BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。正例:优先推荐入参为String的构造方法,或使用BigDecimal的valueOf方法,此方法内部其实执行了Double的toString,而Double的toString按double的实际能表达的精度对尾数进行了截断。

BigDecimal recommend1 = new BigDecimal("0.1");

BigDecimal recommend2 = BigDecimal.valueOf(0.1);

  • 【强制】所有的POJO类属性必须使用包装数据类型。
  • 【强制】RPC方法的返回值和参数必须使用包装数据类型。
  • 【推荐】所有的局部变量使用基本数据类型。
  • 【强制】构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在init方法中。
  • 【推荐】 类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter / setter 方法。

说明:公有方法是类的调用者和维护者最关心的方法,首屏展示最好;保护方法虽然只是子类关心,也可能是“模板设计模式”下的核心方法;而私有方法外部一般不需要特别关心,是一个黑盒实现;因为承载的信息价值较低,所有Service和DAO的getter/setter方法放在类体最后。

  • 【推荐】setter方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名。在getter/setter方法中,不要增加业务逻辑,增加排查问题的难度。。
  • 【强制】循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展。

说明:下例中,反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。

String str = "start";

for (int i = 0; i < 100; i++) {

str = str + "hello";

}

集合处理
  • 【强制】关于hashCode和equals的处理,遵循如下规则:

1 只要重写equals,就必须重写hashCode

2) 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。

3) 如果自定义对象作为Map的键,那么必须复写hashCode和equals。

说明:String因为重写了hashCode和equals方法,所以我们可以愉快地使用String对象作为key来使用。

  • 【强制】判断所有集合内部的元素是否为空,使用isEmpty()方法,而不是size()==0的方式。

说明:前者的时间复杂度为O(1),而且可读性更好。

  • 【强制】使用Map的方法keySet()/values()/entrySet()返回集合对象时,不可以对其进行添加元素操作,否则会抛出UnsupportedOperationException异常。
  • 【强制】Collections类返回的对象,如:emptyList()/singletonList()等都是immutable list,不可对其进行添加或者删除元素的操作。
  • 【强制】在使用Collection接口任何实现类的addAll()方法时,都要对输入的集合参数进行NPE判断。
  • 【强制】不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
  • 【推荐】集合泛型定义时,在JDK7及以上,使用diamond语法或全省略。

说明:菱形泛型,即diamond,直接使用<>来指代前边已经指定的类型。

// diamond方式,即<>

HashMap<String, String> userCache = new HashMap<>(16);

// 全省略方式

ArrayList<User> users = new ArrayList(10);

  • 【推荐】集合初始化时,指定集合初始值大小。
  • 【推荐】使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历。

说明:keySet其实是遍历了2次,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value。而entrySet只是遍历了一次就把key和value都放到了entry中,效率更高。如果是JDK8,使用Map.forEach方法。

  • 【推荐】高度注意Map类集合K/V能不能存储null值的情况,如下表格:

集合类

Key

Value

Super

说明

Hashtable

Not null

Not null

Dictionary

线程安全

ConcurrentHashMap

Not null

Not null

AbstractMap

锁分段技术(CAS)

TreeMap

Not null

允许为null

AbstractMap

线程不安全

HashMap

Not null

允许为null

AbstractMap

线程不安全

并发处理
  • 【强制】获取单例对象需要保证线程安全,其中的方法也要保证线程安全。 说明:资源驱动类、工具类、单例工厂类都需要注意。
  • 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
  • 【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

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

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

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

  • 【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
  • 【强制】对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。

日志规范
  • 【强制】应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架 (SLF4J、JCL--Jakarta Commons Logging)中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
  • 【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。

说明:因为String字符串的拼接会使用StringBuilder的append()方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提升性能。

  • 【强制】日志打印时禁止直接用JSON工具将对象转换成String。

正例:打印日志时仅打印出业务相关属性值或者调用其对象的toString()方法。

  • 【推荐】可以使用warn日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出error级别,避免频繁报警。
  • 说明:注意日志输出的级别,error级别只记录系统逻辑出错、异常或者重要的错误信息。

Top