开发规约总结
1.POJO类中的任何布尔类型的变量,都不要加is前缀,否则部分框架解析会引起序列列化错误。
2.接⼝类中的⽅方法和属性不不要加任何修饰符号(public也不不要加),保持代码的简洁性,并加上有效的 javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,确定与接⼝⽅法相关,并且是整个应⽤的 基础常量。
3.枚举类名带上Enum后缀,枚举成员名称需要全大写,单词间⽤下划线隔开。
4.各层命名规约:
A) Service/DAO层⽅方法命名规约
1) 获取单个对象的方法用get作前缀。
2) 获取多个对象的方法用list作前缀,复数结尾,如:listObjects。
3) 获取统计值的方法用count作前缀。
4) 插⼊入的⽅法用save/insert作前缀。
5) 删除的⽅法用remove/delete作前缀。
6) 修改的⽅法用update作前缀。
B) 领域模型命名规约
1) 数据对象:xxxDO,xxx即为数据表名。
2) 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
3) 展示对象:xxxVO,xxx⼀一般为⽹网⻚页名称。
4) POJO是DO/DTO/BO/VO的统称,禁⽌止命名成xxxPOJO。
5.不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
反例:String key = "Id#taobao_" + tradeId;
cache.put(key, value);
本例中同学A定义了缓存的key,然后缓存提取的同学B使⽤了Id#taobao来提取,少了下划线,导致故障。
6.要使⽤一个常量类维护所有常量,要按常量功能进行归类,分开维护。
正例:缓存相关的常量放在类CacheConsts下;系统配置相关的常量放在类ConfigConsts下。
7.不同逻辑、不同语义、不同业务的代码之间插⼊一个空⾏分隔开来以提升可读性。
8.所有整型包装类对象之间值的比较,全部使用equals⽅法比较。
说明:对于Integer var=?在-128⾄127之间的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使⽤==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是⼀个⼤坑,推荐使用equals⽅法进⾏判断。
9.浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用equals来判断。
因为浮点数采⽤用“尾数+阶码”的编码⽅方式,二进制⽆法精确表示大部分的十进制小数
float精度计算原理:链接
正例:
(1) 指定⼀一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
float diff = 1e-6f;
if (Math.abs(a - b) < diff) {
System.out.println("true");
}
(2) 使⽤用BigDecimal来定义值,再进⾏行行浮点数的运算操作
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
if (x.equals(y)) {
System.out.println("true");
}
10.禁⽌使用构造方法 BigDecimal(double) 的方式把 double值 转化为 BigDecimal对象
说明:BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。
如:BigDecimal g = new BigDecimal(0.1f); 实际的存储值 为:0.100000001490116119384765625
优先推荐入参为String的构造方法,或使用BigDecimal的valueOf⽅方法,此方法内部其实执行了Double的 toString,而Double的toString按double的实际能表达的精度对尾数进行了截断。
BigDecimal good1 = new BigDecimal("0.1");
BigDecimal good2 = BigDecimal.valueOf(0.1);
11.使用索引访问用String的split⽅法得到的数组时,需做最后一个分隔符后有⽆内容的检查,否则会有抛IndexOutOfBoundsException的风险。
说明:
String str = "a,b,c,,";
String[] ary = str.split(",");
// 预期⼤大于3,结果是3
System.out.println(ary.length);
12.循环体内,字符串的联接⽅式,使用StringBuilder的append方法进⾏行扩展。
反例:
String str = "start";
for (int i = 0; i < 100; i++) {
str = str + "hello";
}
说明:反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作, 最后通过toString方法返回String对象,造成内存资源浪费。
String和StringBuilder对比原理:链接
13.慎⽤Object的clone⽅法来拷贝对象。
说明:对象的clone方法默认是浅拷贝,若想实现深拷贝需要重写clone方法实现域对象的深度遍历式拷⻉。
clone拷贝:链接
14.Collections类返回的对象,如:emptyList()/singletonList()等都是immutable list,不可对其进行添加或者 删除元素的操作。
反例:某⼆⽅库的⽅法中,如果查询无结果,返回Collections.emptyList()空集合对象,调⽤方⼀旦进行了添加 元素的操作,就会触发UnsupportedOperationException异常。
15.ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常: java.util.RandomAccessSubList cannot be cast to java.util.ArrayList ;
说明: subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,⽽是 ArrayList 的一个视图,对于 SubList子列表的所有操作最终会反映到原列表上。
16.在subList场景中,⾼度注意对父集合元素的增加或删除,均会导致子列表的遍历、增加、删除产⽣ConcurrentModification Exception 异常。
List list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
List ll = list.subList(0, 3);
list.add(5);
//会抛出ConcurrentModificationException异常
System.out.println(ll.size());
17.使⽤集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一样的数组,大⼩就是 list.size()。
反例:
直接使⽤用toArray⽆无参⽅方法存在问题,此⽅方法返回值只能是Object[]类,若强转其它类型数组将出现ClassCastException错误。
正例:
List list = new ArrayList<>(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);
18.在使用Collection接口任何实现类的addAll()方法时,都要对输入的集合参数进行NPE判断。
说明:在ArrayList.addAll⽅法的第⼀⾏代码即 Object[] a = c.toArray();
其中c为输⼊集合参数,如果为null,则直接抛出异常。
19.使⽤⼯具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear
方法会抛出UnsupportedOperationException异常。
说明:asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器式, 只是转换接口,后台的数据仍是数组。
String[] str = new String[] { "a", "b" };
List list = Arrays.asList(str);
第一种情况:list.add("c"); 运行时异常。
第二种情况:str[0]= "changed"; 那么list.get(0)也会随之修改,反之亦然。
20.不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator⽅式,如果并发操作,需要对Iterator对象加锁。
反例:
List list = new ArrayList<>();
list.add("1");
list.add("2");
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
说明:把上面代码中的"1".equals(item)换成"2"会抛出ConcurrentModificationException异常
抛出异常原因和foreach原理:链接
正例:
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (删除元素的条件) {
iterator.remove();
}
}
21.在JDK7版本以上,Comparator要满⾜如下三个条件,不然Arrays.sort,Collections.sort会抛IllegalArgumentException异常。
说明:
1) x,y的⽐比结果和y,x的⽐比结果相反。
2) x>y,y>z,则x>z。
3) x=y,则x,z⽐比结果和y,z⽐比结果相同。
JDK7/JDK8的Collections.Sort,Arrays.sort方法实现中,如果两个值是相等的,那么compare方法需要返回0,否则可能会在排序时抛错,而JDK6是没有这个限制的。
反例:下例例中没有处理理相等的情况,实际使⽤用中可能会出现异常:
new Comparator() {
@Override
public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : -1;
} }
22.集合初始化时,指定集合初始值⼤小。
说明:如果暂时⽆无法确定集合⼤大⼩小,那么指定默认值(16)即可:
反例:HashMap需要放置1024个元素,由于没有设置容量初始⼤小,随着元素不断增加,容量7次被迫扩⼤,
需要重建hash表,严重影响性能。
23.表达异常分支时,少⽤if-else方式,这种⽅式可以改写成:
if (condition) {
...
return obj;
}
// 接着写else的业务逻辑代码;
说明:如果非得使用if()...else if()...else...⽅式表达逻辑,
【强制】请勿超过3层,超过请使⽤用状态设计模式。
正例:超过3层的 if-else 的逻辑判断代码可以使⽤卫语句、策略模式、状态模式等来实现,其中卫语句 示例如下:
public void today() {
if (isBusy()) {
System.out.println("change time.");
return;
}
if (isFree()) {
System.out.println("go to travel.");
return;
}
System.out.println("stay at home to learn Alibaba Java Coding Guidelines.");
return;
}
24.避免⽤ApacheBeanutils进行属性的copy。
说明:Apache BeanUtils性能差,可以使⽤其他方案⽐如Spring BeanUtils, Cglib BeanCopier, 注意均是浅拷贝。
25.获取当前毫秒数:System.currentTimeMillis();
⽽不是new Date().getTime();
说明:如果想获取更更加精确的纳秒级时间值,使⽤用System.nanoTime的⽅方式。
在JDK8中,针对统计时间等场景,推荐使用Instant类。
26.防⽌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。
27.应⽤中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架(SLF4J、JCL--Jakarta Commons Logging)中的API。什么是日志框架和日志系统,请参考webx作者宝宝的⽂文章
(链接),文章里 也详细说明了为什么不能直接依赖使用⽇志系统⽽是⽇志框架,以及应用的pom中如何做 dependencyManagement。
说明:⽇日志框架(SLF4J、JCL--Jakarta Commons Logging)的使⽤用⽅方式(推荐使⽤用SLF4J)
28.日志⽂件推荐⾄少保存15天,因为有些异常具备以“周”为频次发生的特点。对于当天日志,以“应⽤用 名.log”来保存,保存在/home/admin/应⽤用名/logs/⽬目录下,过往⽇日志格式为: {logname}.log.{保存 ⽇日期},⽇日期格式:yyyy-MM-dd
说明:以mppserver应⽤用为例例,日志保存在/home/admin/mppserver/logs/mppserver.log,历史⽇日志 名称为
mppserver.log.2016-08-01
29.在日志输出时,字符串变量之间的拼接使用占位符的⽅式。
说明:因为String字符串的拼接会使用StringBuilder的append()方式,有一定的性能损耗。使用占位符仅是替换 动作,可以有效提升性能。
正例:
logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);
30.表达是与否概念的字段,必须使用is_xxx的⽅式命名,数据类型是unsigned tinyint(1表示是,0表示否),
此规则同样适⽤用于odps建表。
说明:任何字段如果为⾮负数,必须是unsigned。
注意:POJO类中的任何布尔类型的变量量,都不不要加is前缀,所以,需要在
正例:表达逻辑删除的字段名 is_deleted ,1表示删除,0表示未删除。
31.表名、字段名必须使用⼩写字母或数字,字段命名可参考附2;禁止出现数字开头,禁⽌两个下划线中间只
出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
说明:MySQL在Windows下不区分大小写,但在Linux下默认是区分⼤小写。因此,数据库名、表名、字段名, 都不允许出现任何⼤写字母,避免节外⽣枝。
正例:getter_admin,task_config,level3_name
32.禁⽤保留字,如desc、range、match、delayed等,参考官方保留字 链接
33.唯一索引名为uk_字段名;普通索引名则为idx_字段名。
说明:uk_ 即 unique key;idx_ 即index的简称。
索引知识点:链接
explain使用字段说明:链接
加深最左原则理解:链接
不能用到索引情况:链接
34.小数类型为decimal,禁止使用float和double。
说明:float和double在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果 存储的数据范围超过decimal的范围,建议将数据拆成整数和小数分开存储。
35.varchar是可变长字符串,不预先分配存储空间,长度不要超过5000,如果存储长度大于此值,定义字段类 型为TEXT,独⽴出来一张表,用主键来对应,避免影响其它字段索引效率。
36.业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
说明:不要以为唯一索引影响了insert速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应 ⽤层做了非常完善的校验和控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
37.超过三个表禁止join。需要join的字段,数据类型保持绝对一致;多表关联查询时,保证被关联的字段需要 有索引。
38.在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索 引⻓度。
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20的索引,区分度会高达90%以 上,可以使用count(distinct left(列列名, 索引⻓长度))/count(*)的区分度来确定。0.31为黄金值
39.如果有order by的场景,请注意利⽤索引的有序性。order by最后的字段是组合索引的一部分,并且放在索 引组合顺序的最后,避免出现file_sort的情况,影响查询性能。
正例:where a=? and b=? order by c; 索引:a_b_c
反例:索引中有范围查找,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引a_b⽆法排序。
40.利用覆盖索引来进行查询操作,来避免回表操作。
说明:如果一本书需要知道第11章是什么标题,会翻开第11章对应的那⼀⻚吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。如果索引包含所有满足查询需要的数据的索引成为覆盖索引(Covering Index),也就是 平时所说的不需要回表操作
正例:IDB能够建⽴索引的种类分为【主键索引、唯一索引、普通索引】,而覆盖索引是一种查询的一种效果, ⽤ explain的结果,extra列列会出现:using index.
41.利用延迟关联或者子查询优化超多分页场景。
说明:MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别 大的时候,效率就⾮常的低下,要么控制返回的总⻚数,要么对超过特定阈值的页数进行SQL改写。
正例:先快速定位需要获取的id段,然后再关联:
SELECT a.* FROM 表1 a, (select id from 表1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
42.建组合索引的时候,区分度最高的在最左边。
正例:如果where a=? and b=? ,a列的几乎接近于唯一值,那么只需要单建idx_a索引即可。
说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。
如:where c>? and d=? 那么即使c的区分度更高,也必须把d放在索引的最前列,即建立组合索引idx_d_c。
43.不要使用count(列名)或count(常量)来替代count(),count()就是SQL92定义的标准统计行数的语法,跟 数据库⽆关,跟NULL和⾮NULL⽆关。
说明:count(*)会统计值为NULL的行,⽽count(列名)不会统计此列为NULL值的⾏。
44.count(distinct col) 计算该列除NULL之外的不重复数量。
注意:count(distinct col1, col2) 如果其中一列全为NULL,那么即使另一列有不同的值,也返回为0。
45.当某一列的值全是NULL时,count(col)的返回结果为0,但sum(col)的返回结果为NULL,因此使用sum() 时需注意NPE问题。
正例:可以使用如下⽅式来避免sum的NPE问题:SELECT IFNULL(SUM(column), 0) FROM table;
46.使用ISNULL()来判断是否为NULL值。
说明: NULL 与任何值的直接比较都为 NULL 。
1) NULL<>NULL 的返回结果是 NULL ,⽽不是 false 。
2) NULL=NULL 的返回结果是 NULL ,⽽不是 true 。
3) NULL<>1 的返回结果是 NULL ,⽽不是 true 。
47.在代码中写分页查询逻辑时,若count为0应直接返回,避免执行后面的分⻚语句。
48.不要用resultClass当返回参数,即使所有类属性名与数据库字段⼀一对应,也需要定义;反过来,每一个表 也必然有一个与之对应。
说明:配置映射关系,使字段与DO类解耦,⽅方便便维护。
49.工具类二⽅库已经提供的,不要在本应⽤中编程实现。
json操作: fastjson md5操作:commons-codec
⼯具集合:Guava包
数组操作:ArrayUtils(org.apache.commons.lang3.ArrayUtils)
集合操作:CollectionUtils(org.apache.commons.collections4.CollectionUtils)
除上面以外还有NumberUtils、DateFormatUtils、DateUtils等优先使⽤用org.apache.commons.lang3 这个包下的,不要使用org.apache.commons.lang包下⾯的。
原因是commons.lang这个包是从JDK1.2 开始支持的所以很多1.5/1.6的特性是不⽀持的,例如:泛型。