ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Java编码规范:阿里规范/findbug 整理总结

Java编码规范:阿里规范/findbug 整理总结

文章目录

  • 阿里规范
    • JAVA编程规约
      • 命名风格
        • 【强制】 类型与中括号紧挨相连来表示数组。
        • 【强制】 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。
        • 【参考】 枚举类名带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。
        • 【强制】 异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。
        • 【强制】 POJO 类必须写 toString 方法。使用 IDE 中的工具:source> generate toString时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。
      • 集合处理
        • 【强制】 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。
        • 高度注意 Map 类集合 K/V 能不能存储 null 值的情况
        • 【强制】 使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
      • OOP 规约
        • 【强制】 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中
        • 【强制】 所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
    • MySQL 数据库
      • 建表规约
        • 【强制】 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsignedtinyint(1 表示是,0 表示否)。
        • 【强制】 不要使用 count(列名)或 count(常量)来替代 count(*),count(*)是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
      • 索引使用规则
      • explain
      • 索引规约
        • 【强制】 页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决
      • 代码格式
        • 【强制】 单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:
        • 【强制】 IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式,不要使用 Windows 格式。
      • 其他
        • 【强制】 在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度
        • 【强制】 SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类。
        • 【强制】 后台输送给页面的变量必须加$!{var}——中间的感叹号。
      • 异常
        • 【推荐】 定义时区分 unchecked / checked 异常,避免直接抛出 new RuntimeException(),更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException 等
        • 【强制】 不要在 finally 块中使用 return。
        • 【强制】 catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。
        • 【强制】 异常不要用来做流程控制,条件控制。
        • 【推荐】 防止 NPE,是程序员的基本修养,注意 NPE 产生的场景
      • 注释规约
        • 【参考】 特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描,经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。
        • 【强制】 所有的类都必须添加创建者和创建日期。
        • 【强制】 所有的枚举类型字段必须要有注释,说明每个数据项的用途。
      • 控制语句
        • 【推荐】 接口入参保护,这种场景常见的是用作批量操作的接口。
      • 单元测试
        • 【强制】 好的单元测试必须遵守 AIR 原则。
        • 【推荐】 编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。
      • 并发处理
        • 【强制】 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
      • 日志规约
        • 【强制】 对于 trace/debug/info 级别的日志输出,必须进行日志级别的开关判断。
      • ORM 映射
        • 【强制】 不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;反过来,每一个表也必然有一个 POJO 类与之对应。
  • findbugs
    • Boxing/unboxing to parse a primitive
    • invokes inefficient new String(String) constructor
    • makes inefficient use of keySet iterator instead of entrySet iterator

阿里规范

JAVA编程规约

命名风格

【强制】 类型与中括号紧挨相连来表示数组。

正例:定义整形数组 int[] arrayDemo;
反例:在 main 参数中,使用 String args[]来定义。

@Testpublic void array(){String[] fruit={"apple","orange"};String fruit2[]={"apple","orange"};}
【强制】 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。
【参考】 枚举类名带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。

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

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

【强制】 异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。

正例:logger.error(各类参数或者对象 toString() + “_” + e.getMessage(), e);

总结:意思就是说,除了异常堆栈信息,还需要记录案发现场的一些打印信息,方便排查问题。
比如:在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题。

【强制】 POJO 类必须写 toString 方法。使用 IDE 中的工具:source> generate toString时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。

说明:在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题。

集合处理

【强制】 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。
正例:
List<String> list = new ArrayList<>(2);
list.add("guan");
list.add("bao");
String[] array = list.toArray(new String[0]);
说明:使用 toArray 带参方法,数组空间大小的 length:
1) 等于 0,动态创建与 size 相同的数组,性能最好。
2) 大于 0 但小于 size,重新创建大小等于 size 的数组,增加 GC 负担。Java 开发手册
12/44
3) 等于 size,在高并发情况下,数组创建完成之后,size 正在变大的情况下,负面影响与上相同。
4) 大于 size,空间浪费,且在 size 处插入 null 值,存在 NPE 隐患

总结:这里比较特殊的一个点,就是长度为 0 的空数组。

高度注意 Map 类集合 K/V 能不能存储 null 值的情况

HashMap key和value都容许为null。
ConcurrentHashMap key和value都不容许为null。

treemap的key可以为null。

【强制】 使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。

说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。 Arrays.asList 体现的是适
配器模式,只是转换接口,后台的数据仍是数组。
String[] str = new String[] { “yang”, “hao” };
List list = Arrays.asList(str);
第一种情况:list.add(“yangguanbao”); 运行时异常。
第二种情况:str[0] = “changed”; 也会随之修改,反之亦然。

第二种情况说明总结: 对数组元素的修改,会影响到转化过来的集合。因为asList它只是转化接口,后台的数据仍是数组。

OOP 规约

【强制】 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中
【强制】 所有整型包装类对象之间值的比较,全部使用 equals 方法比较。

对于 Integer var = ? 在-128 至 127 范围内的赋值,Integer 对象是在 IntegerCache.cache 产
生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。

MySQL 数据库

建表规约

【强制】 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsignedtinyint(1 表示是,0 表示否)。

说明:任何字段如果为非负数,必须是 unsigned。
注意:POJO 类中的任何布尔类型的变量,都不要加 is 前缀,所以,需要在设置从 is_xxx
到 Xxx 的映射关系。数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的命名方式是为了明确其取
值含义与取值范围。
正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。

【强制】 不要使用 count(列名)或 count(常量)来替代 count(),count()是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。

说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。

总结: count(列名)不会统计此列为 NULL 值的行。比如某一列都是null,count(列名)返回0。

索引使用规则

  1. 若查询条件中不包含索引的最左列,无法使用索引
  2. 对于范围查询,只能利用索引的最左列
  3. 对于order by A语句,在A上建立索引,可以避免排序
  4. 对于group by A语句,在A上建立索引,可以避免排序
  5. 对于多列排序,需要所有所有列排序方向一致,才能利用索引。

explain

  1. type列
    官方的说法,说这列表示的是“访问类型”,更通俗一点就是:mysql找到需要的数据行的方式。

效率从最差到最好的顺序如下:

  • ALL
    全表扫描,没有用到任何的索引,这是一种非常原始的查找方法,非常的耗时二号抵消。
  • index
    这种类型只是另一种形式的全部扫描,只不过它的扫描顺序是按照索引的顺序,然后根据索引回表取数据。和All相比,他们都是取得了全表的数据,而且index要先读索引再回表取数据。
    如果连接类型为type,而且extra列中的值为‘Using index’,那么称这种情况为索引覆盖
  • rang
    rang指的是有范围的索引扫描,相较于index的全索引扫描,它有范围限制,因此要优于index。
    它是基于索引的,表示WHERE条件中出现了BETWEEN AND、>、 <、 IN、OR。
  • ref
    查找条件使用索引而且不为主键或unique。表示虽然使用了索引,但该索引列的值不唯一,有重复。这样即使使用索引找到了第一条数据,仍然需要进行目标值附近的小范围扫描,但它的好处是不用全表扫描。
  • ref_eq
    进行精确查询,结果集唯一,表示使用了主键或唯一性索引查找的情况。
  • const,system
    当MySQL能对查询部分进行优化,并且转换为一个常量。比如,where条件使用主键查询。
  1. possible-keys列

possible-keys: 显示可能应用在这张表上的索引,一个或者多个。
查询设计到的字段上若存在索引,则该索引被列出,但是不一定被实际使用。

总结:possible_keys显示可能在这张表中的索引,如果为空,表示没有可能的索引。

  1. key列
    key:实际使用的索引,如果为null,则没有使用索引。
  2. ref列
    ref:显示使用哪个列或常数与key一起从表中选择行。
    总结:显示索引的哪一列被使用了。

索引规约

【强制】 页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决

说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。

代码格式

【强制】 单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:

1)第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进,参考示例。
2)运算符与下文一起换行。
3)方法调用的点符号与下文一起换行。
4)方法调用中的多个参数需要换行时,在逗号后进行。
5)在括号前不要换行,见反例。

正例:
StringBuilder sb = new StringBuilder();
// 超过 120 个字符的情况下,换行缩进 4 个空格,点号和方法名称一起换行
sb.append("Jack").append("Ma")...
.append("alibaba")...
.append("alibaba")...
.append("alibaba");
反例:
StringBuilder sb = new StringBuilder();
// 超过 120 个字符的情况下,不要在括号前换行
sb.append("Jack").append("Ma")...append
("alibaba");
// 参数很多的方法调用可能超过 120 个字符,不要在逗号前换行
method(args1, args2, args3, ...
, argsX);`

注意: 不要在括号前换行,不要在逗号前换行

【强制】 IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式,不要使用 Windows 格式。

其他

【强制】 在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度

说明:不要在方法体内定义:Pattern pattern = Pattern.compile(“规则” );

【强制】 SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类。

正例:注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal df = new ThreadLocal() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat(“yyyy-MM-dd”);
}
};
说明:如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,
DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:simple beautiful strong immutable
thread-safe。

总结:在JDK8中,针对时间等场景,推荐使用Instant类。Java8使用LocalDate、LocalTime、LocalDateTime、ZonedDateTime分别操作日期、时间、日期和时间。
这四个类的默认使用系统时区。

日期相关参考:
Java8 时间日期类操作
参考URL: https://www.jianshu.com/p/45c00e7a28e7

【强制】 后台输送给页面的变量必须加$!{var}——中间的感叹号。

说明:如果 var 等于 null 或者不存在,那么${var}会直接显示在页面上。

异常

【推荐】 定义时区分 unchecked / checked 异常,避免直接抛出 new RuntimeException(),更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException 等
【强制】 不要在 finally 块中使用 return。

说明:try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存
在 return 语句,则在此直接返回,无情丢弃掉 try 块中的返回点。

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

说明:对大段代码进行 try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。
正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程
序上作出分门别类的判断,并提示给用户。

总结: 这段话2个意思,一个是:不要所有直接try catch一整块,只try catch非稳定代码, 一个是:异常返回细化分类,不要一个返回一个很粗的异常。一个是:

【强制】 异常不要用来做流程控制,条件控制。

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

【推荐】 防止 NPE,是程序员的基本修养,注意 NPE 产生的场景

1) 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。
反例:public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。
2) 数据库的查询结果可能为 null。
3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
5) 对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。
6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
正例:使用 JDK8 的 Optional 类来防止 NPE 问题。

总结: 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。这就是说 别人的数据要判空。

注释规约

【参考】 特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描,经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。

1) 待办事宜(TODO):(标记人,标记时间,[预计处理时间])
表示需要实现,但目前还未实现的功能。这实际上是一个 Javadoc 的标签,目前的 Javadoc 还没
有实现,但已经被广泛使用。只能应用于类,接口和方法(因为它是一个 Javadoc 标签)。
2) 错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间])
在注释中用 FIXME 标记某代码是错误的,而且不能工作,需要及时纠正的情况。

总结:注意 FIXME 标记某代码是错误的,不能工作,不是说有待商榷,将来改进。

【强制】 所有的类都必须添加创建者和创建日期。
【强制】 所有的枚举类型字段必须要有注释,说明每个数据项的用途。

控制语句

【推荐】 接口入参保护,这种场景常见的是用作批量操作的接口。

总结:接口入参保护,“保护”的是服务端应用,即接口提供方,最常见的做法就是校验入参的有效值范围和设置批量操作白名单;批量操作的批量数应该有上限,而不是无限的。
假如客户端请求一次批量操作10W笔转账订单,服务器应该果断拒绝。入参保护,一般都是通过if语句实现:if(请求记录>10000条) return;直接返回。

单元测试

【强制】 好的单元测试必须遵守 AIR 原则。

说明:单元测试在线上运行时,感觉像空气(AIR)一样并不存在,但在测试质量的保障上,却是非常关
键的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点。
⚫ A:Automatic(自动化)
⚫ I:Independent(独立性)
⚫ R:Repeatable(可重复)

【推荐】 编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。
  • B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
  • C:Correct,正确的输入,并得到预期的结果。
  • D:Design,与设计文档相结合,来编写单元测试。
  • E:Error,强制错误信息输入(如:非法数据、异常流程、业务允许外等),并得到预期的结果。

并发处理

【强制】 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。

说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。

日志规约

【强制】 对于 trace/debug/info 级别的日志输出,必须进行日志级别的开关判断。
正例:
// 如果判断为真,那么可以输出 trace 和 debug 级别的日志
if (logger.isDebugEnabled()) {
logger.debug("Current ID is: {} and name is: {}", id, getName());
}`

总结: 就是说即使我们设置了日志输出级别,为了提升性能考虑,我也要进行日志级别开关判断。

ORM 映射

【强制】 不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;反过来,每一个表也必然有一个 POJO 类与之对应。

说明:配置映射关系,使字段与 DO 类解耦,方便维护。

总结:
Batis的返回值参数类型有:resultMap与resultClass。
一、当结果集列名和类的属性名完全相对应的时候,则可直接用resultClass直接指定查询结果类型。

二、当查询结果集与属性名对应不上时,可以采用resultMap指定列名与对象属性名之间的对应关系,否则对应不上的属性将为null或0。

根据这条规则,即 不要用 resultClass 用 resultMap。 在性能方面,resultMap要比resultClass高,所以建议尽量使用resultMap。

findbugs

FindBugs-IDEA汇总(整理中)
参考URL: http://www.imooc.com/article/301948

Boxing/unboxing to parse a primitive

sonarQube插件扫描出来的bug-Boxing/unboxing to parse a primitive
参考URL: https://blog.csdn.net/u011137371/article/details/88724865

具体的问题出在
Integer.valueOf(“String”) >2
这句话的逻辑对应:String => int => Integer => int

Integer.parseInt(“String”) >2
使用Integer.paseInt()可以节约int 转Integer的过程。

其实就是指出了不必要的装箱转换

总结:
Integer.parseInt(String)的作用就是将String字符类型数据转换为Integer整型数据。
Integer.parseInt(String)遇到一些不能被转换为整型的字符时,会抛出异常。

invokes inefficient new String(String) constructor

使用java.lang.String(String)构造函数会浪费内存因为这种构造方式和String作为参数在功能上容易混乱。只是使用String直接作为参数的形式

makes inefficient use of keySet iterator instead of entrySet iterator

keySet 与entrySet 遍历HashMap性能差别
参考URL: https://www.iteye.com/blog/kim-miao-736143
FindBugs-IDEA汇总(整理中)
参考URL: http://www.imooc.com/article/301948

findbug:
遍历HashMap时,遍历keySet返回的Key,再通过Key取得想要的Value;
这种遍历方式很低效。它意思遍历 entrySet 迭代器 效率高。

原代码:

 Map<Integer, String> userMap = new HashMap<>(16);//填充数据//进行遍历key  和value,组装数据for (Integer key : userMap.keySet()) {list.add(new Test(key, userMap.get(key)));}`

修改代码:

 Map<Integer, String> userMap = new HashMap<>(16);//填充数据//进行遍历key  和value,组装数据for (Map.Entry<Integer, String> entry : userMap.entrySet()) {list.add(new TestData(entry.getKey(), entry.getValue()));}
返回列表