文章目录
- 阿里规范
- 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。
索引使用规则
- 若查询条件中不包含索引的最左列,无法使用索引
- 对于范围查询,只能利用索引的最左列
- 对于order by A语句,在A上建立索引,可以避免排序
- 对于group by A语句,在A上建立索引,可以避免排序
- 对于多列排序,需要所有所有列排序方向一致,才能利用索引。
explain
- 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条件使用主键查询。
- possible-keys列
possible-keys: 显示可能应用在这张表上的索引,一个或者多个。
查询设计到的字段上若存在索引,则该索引被列出,但是不一定被实际使用。
总结:possible_keys显示可能在这张表中的索引,如果为空,表示没有可能的索引。
- key列
key:实际使用的索引,如果为null,则没有使用索引。 - 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()));}