前言
由于近年来对于代码质量的要求越来越高,特制定部门级Java代码规范规则集X-JAVA-RULE,整体要求规则可用可查、循序渐进。
可用是指考虑目前已有代码的体量,不满足这些规则的代码能否能被修复,如果工作量巨大不能被修复或者实际适用场景不会导致漏洞,暂时没有必要纳入其中。
比如规则【包名统一使用小写】是个很好的规则,但是考虑到老项目中有大量的代码在大小的包名下面,这里没有纳入规则集X-JAVA-RULE中。
可查是指有工具可以扫描出,而不是人工筛查。在规则后面附上了SonaQube中的相应规则名称。要求在SonaQube可查询对应规则。
比如规则1
循序渐进是指以下规则集不是最终版本,根据实际情况后续会不断扩展。规则集会增删,会被继承,但每次不建议加入过多规则,导致无法推进。
比如【包名统一使用小写】可以考虑在后续新项目中加入,此时新建一个继承【X-JAVA-RULE】的规则集【X-JAVA-RULE-EXT】,将这些规则集纳入其中。
以下规则内容完全参考 阿里巴巴Java开发手册《嵩山版》 和 阿里巴巴 Java 开发手册 终极版(1.3.0),二者有出入则参考后者。比如规则14,在两个版本中不一致。
在《嵩山版》中
在终极版中
此处以终极版为准。
规则集X-JAVA-RULE
1. 【强制】 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
反例: _name / _name / $Object / name / name$ / Object$
[p3c]All names should not start or end with an underline or a dollar sign.
2. 【强制】 类名使用 UpperCamelCase 风格,但以下情形例外: DO / BO / DTO / VO / AO /PO / UID 等。
正例: ForceCode / UserDO / HtmlDTO / XmlService / TcpUdpDeal / TaPromotion
反例: forcecode / UserDo / HTMLDto / XMLService / TCPUDPDeal / TAPromotion
[p3c]Class names should be nouns in UpperCamelCase except domain models: DO, BO, DTO, VO, etc.
3. 【强制】方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式。
正例: localValue / getHttpMessage() / inputUserId
[p3c]Method names, parameter names, member variable names, and local variable names should be written in lowerCamelCase.
4. 【强制】 POJO 类中布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误。
反例: 定义为基本数据类型 Boolean isDeleted; 的属性,它的方法也是 isDeleted(), RPC 框架在反向解析的时候, “以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。
[p3c]Do not add ‘is’ as prefix while defining Boolean variable.
5. 【强制】 long 或者 Long 初始赋值时, 使用大写的 L,不能是小写的 l,小写容易跟数字 1 混淆,造成误解。
说明: Long a = 2l; 写的是数字的 21,还是 Long 型的 2?
[p3c]‘L’ instead of ‘l’ should be used for long or Long variable.
6. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
正例
public class TimerTaskThread extends Thread {
public TimerTaskThread() {
super.setName("TimerTaskThread");
...
}
[p3c]A meaningful thread name is helpful to trace the error information,so assign a name when creating threads or thread pools.
7. 【强制】 必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用 try-finally 块进行回收。
正例:
objectThreadLocal.set(userInfo);
try {
// ...
} finally {
objectThreadLocal.remove();
}
[p3c]type ‘ThreadLocal’ must call remove() method at least one times.
8. 【强制】 Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。
说明: 推荐使用 java.util.Objects#equals(JDK7 引入的工具类)
正例: “test”.equals(object);
反例: object.equals(“test”);
[p3c]Equals should be invoked by a constant or an object that is definitely not null.
9. 【强制】所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。
说明: 对于 Integer var = ? 在-128 至 127 范围内的赋值, Integer 对象是在 IntegerCache.cache 产生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。
[p3c]The wrapper classes should be compared by equals method rather than by symbol of ‘==’ directly.
[p3c]To judge the equivalence of floating-point numbers, == cannot be used for primitive types, while equals cannot be used for wrapper classes.
10. 关于基本数据类型与包装数据类型的使用标准如下:
1) 【强制】 所有的 POJO 类属性必须使用包装数据类型。
2) 【强制】 RPC 方法的返回值和参数必须使用包装数据类型。
3) 【推荐】 所有的局部变量使用基本数据类型。
说明: POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE 问题,或者入库检查,都由使用者来保证。
正例: 数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。
反例: 比如显示成交总额涨跌情况,即正负 x%, x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出 。
[p3c]Rules for using primitive data types and wrapper classes.
11. 【强制】 关于 hashCode 和 equals 的处理,遵循如下规则:
1) 只要重写 equals,就必须重写 hashCode。 “equals(Object obj)” and “hashCode()” should be overridden in pairs
2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。
3) 如果自定义对象做为 Map 的键,那么必须重写 hashCode 和 equals。
说明: String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地使用 String 对象作为 key 来使用。
Correctness - Use of class without a hashCode() method in a hashed data structure
12. 【强制】 ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常, 即java.util.RandomAccessSubList cannot be cast to java.util.ArrayList.
说明: subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是ArrayList 的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。
[p3c]Do not cast subList in class ArrayList, otherwise ClassCastException will be thrown.
13. 【强制】在 subList 场景中, 高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均会产生 ConcurrentModificationException 异常。
[p3c]When using subList, be careful to modify the size of original list.
14. 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size()。
说明: 使用 toArray 带参方法,入参分配的数组空间不够大时, toArray 方法内部将重新分配内存空间,并返回新数组地址; 如果数组元素大于实际所需,下标为[ list.size() ]的数组元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。
正例:
List<String> list = new ArrayList<String>(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array) ;
反例: 直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。
[p3c]Do not use toArray method without arguments.
15. 【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出UnsupportedOperationException 异常。
说明: asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。 Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。
String[] str = new String[] { "you", "wu" };
List list = Arrays.asList(str);
第一种情况: list.add(“yangguanbao”); 运行时异常。
第二种情况: str[0] = “gujin”; 那么 list.get(0)也会随之修改。
[p3c]Do not use methods which will modify the list after using Arrays.asList to convert array to list.
16. 【强制】不要在 foreach 循环里进行元素的 remove/add 操作。 remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。
正例:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (删除元素的条件) {
iterator.remove();
}
}
反例:
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
说明: 以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结果吗?
[p3c]Do not remove or add elements to a collection in a foreach loop.
17. 【强制】 SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类。
正例: 注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
说明: 如果是 JDK8 的应用,可以使用 Instant 代替 Date, LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释: simple beautiful strong immutable thread-safe。
[p3c]SimpleDataFormat is unsafe, do not define it as a static variable. If have to, lock or DateUtils class must be used.
18. 【强制】多线程并行处理定时任务时, Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。
[p3c]Use ScheduledExecutorService instead.
19. 【强制】在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
说明: 不要在方法体内定义: Pattern pattern = Pattern.compile(规则);
[p3c]When using regex, precompile needs to be done in order to increase the matching performance.
20. 【强制】不能在 finally 块中使用 return, finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。
[p3c]Never use return within a finally block.
21. 【强制】 日期格式化时,传入 pattern 中表示年份统一使用小写的 y。
说明: 日期格式化时, yyyy 表示当天所在的年,而大写的 YYYY 代表是 week in which year( JDK7 之后引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的 YYYY就是下一年。
正例: 表示日期和时间的格式如下所示:
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
[p3c]Date format string [%s] is error,When doing date formatting, ‘y’ should be written in lowercase for ‘year’.
22. 【强制】 禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象。
说明: BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。如: BigDecimal g = new BigDecimal(0.1F); 实际的存储值为: 0.10000000149
正例: 优先推荐入参为 String 的构造方法,或使用 BigDecimal 的 valueOf 方法,此方法内部其实执行了Double 的 toString,而 Double 的 toString 按 double 的实际能表达的精度对尾数进行了截断。
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1);
[p3c]Avoid using the constructor BigDecimal(double) to convert double value to a BigDecimal object.
23. 【强制】 如上所示 BigDecimal 的等值比较应使用 compareTo()方法,而不是 equals()方法。
说明: equals()方法会比较值和精度( 1.0 与 1.00 返回结果为 false) , 而 compareTo()则会忽略精度。
Correctness - Method calls BigDecimal.equals()
24. 【强制】 判断所有集合内部的元素是否为空,使用 isEmpty()方法,而不是 size()==0 的方式。
说明: 在某些集合中,前者的时间复杂度为 O(1),而且可读性更好。
Map<String, Object> map = new HashMap<>(16);
if(map.isEmpty()) {
System.out.println("no element in this map.");
}
Collection.isEmpty() should be used to test for emptiness
25. 【强制】 在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁。
说明一: 如果在 lock 方法与 try 代码块之间的方法调用抛出异常,那么无法解锁,造成其它线程无法成功获取锁。
说明二: 如果 lock 方法在 try 代码块之内,可能由于其它方法抛出异常,导致在 finally 代码块中, unlock对未加锁的对象解锁,它会调用 AQS 的 tryRelease 方法(取决于具体实现类),抛出IllegalMonitorStateException 异常。
说明三: 在 Lock 对象的 lock 方法实现中可能抛出 unchecked 异常,产生的后果与说明二相同。
正例:
Lock lock = new XxxLock();
// ...
lock.lock();
try {
doSomething();
doOthers();
} finally {
lock.unlock();
}
反例:
Lock lock = new XxxLock();
// ...
try {
// 如果此处抛出异常,则直接执行 finally 代码块
doSomething();
// 无论加锁是否成功, finally 代码块都会执行
lock.lock();
doOthers();
} finally {
lock.unlock();
}
[p3c]Lock operation [%s] must immediately follow by try block, and unlock operation must be placed in the first line of finally block.
26. 【强制】 在一个 switch 块内,每个 case 要么通过 continue/break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代码也没有。
说明: 注意 break 是退出 switch 语句块,而 return 是退出方法体。
行 finally 代码块
{
doSomething();
// 无论加锁是否成功, finally 代码块都会执行
lock.lock();
doOthers();
} finally {
lock.unlock();
}
[p3c]Lock operation [%s] must immediately follow by try block, and unlock operation must be placed in the first line of finally block.
27. 【强制】 在一个 switch 块内,每个 case 要么通过 continue/break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代码也没有。
说明: 注意 break 是退出 switch 语句块,而 return 是退出方法体。
[p3c]In a switch block, each case should be finished by break/return.