一、命名风格
-
【强制】类名使用 UpperCamelCase 风格,必须遵从驼峰形式,但以下情形例外:DO / BO / DTO / VO / AO
-
【强制】方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从 驼峰形式。
-
【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
正例:MAX_STOCK_COUNT 反例:MAX_COUNT
-
【强制】抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。
-
【强制】Model 类中布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误。
反例:定义为基本数据类型 Boolean isDeleted;的属性,它的方法也是 isDeleted(),RPC框架在反向解析的时候,“以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。
-
【参考】枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。
说明:枚举其实就是特殊的常量类,且构造方法被默认强制是私有。
正例:枚举名字为 ProcessStatusEnum 的成员名称:SUCCESS / UNKOWN_REASON。 -
【参考】各层命名规约:
A) Service/DAO 层方法命名规约
1) 获取单个对象的方法用 get 做前缀。
2) 获取多个对象的方法用 list 做前缀。
3) 获取统计值的方法用 count 做前缀。
4) 插入的方法用 save/insert 做前缀。
5) 删除的方法用 remove/delete 做前缀。
6) 修改的方法用 update 做前缀。
二、变量定义
- 【推荐】不要使用一个常量类维护所有常量,按常量功能进行归类,分开维护。 说明:大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。
正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下。
三、代码格式
-
【强制】大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果 是非空代码块则:
1) 左大括号前不换行。
2) 左大括号后换行。
3) 右大括号前换行。
4) 右大括号后还有 else 等代码则不换行 表示终止的右大括号后必须换行。 -
【强制】 左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格。
反例:if (空格a == b空格) -
【强制】if/for/while/switch/do 等保留字与括号之间都必须加空格。
-
【强制】任何二目、三目运算符的左右两边都需要加一个空格。
说明:运算符包括赋值运算符=、逻辑运算符&&、加减乘除符号等。 -
【强制】采用 4 个空格缩进,禁止使用 tab 字符。
-
【强制】注释的双斜线与注释内容之间有且仅有一个空格。
正例:// 注释内容,注意在//和注释内容之间有一个空格。 -
【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。
正例:下例中实参的"a",后边必须要有一个空格。method("a", "b", "c"); -
【推荐】方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。
说明:没有必要插入多个空行进行隔开。 -
代码提交之前请格式化上传。idea有相应的对以上格式化的设置。快捷键window系统 Ctril+Alt+L,Mac:Command+Alt+L
四、异常处理
- 永远不要catch中吞掉异常,否则在系统发生错误时,你永远不知道到底发生了什
catch (SomeException e) { return null; }
- 尽量使用特定的异常而不是一律使用Exception。
这样太泛泛的异常一味的使用Exception,这样就违背了可检查异常的设计初衷, 因为调用都不知道Exception到底是什么,也不知道该如何处理。捕获异常时, 也不要捕获范围太大,例如捕获Exception,相反,只捕获你能处理的异常, 应该处理的异常。即然方法的声明者在方法上声明了不同类型的可检查异常, 他是希望调用者区别对待不同异常的。
- Never catch Throwable class。
永远不要捕获Throwable,因为Error也是继承自它,Error是Jvm都处理不了的错误,你能处理? 所以基于有些Jvm在Error时就不会让你catch住。
- 正确的封装和传递异常。不要丢失异常栈,
catch (SomeException e) { throw new MyServiceException("Some information: " + e.getMessage()); //错误的做法 } 一定要保留原始的异常: catch (SomeException e) { throw new MyServiceException("Some information: " , e); //正确的打开方式 }
- 要打印异常,就不要抛出,不要两者都做
catch (SomeException e) { LOGGER.error("Some information", e); throw e; } 这样的log没有任何意义,只会打印出一连串的error log,对于定位问题无济于事。
- 不要在finally块中抛出异常
如果在finally中抛出异常,将会覆盖原始的异常,如果finally中真的可能会发生异常,那一定要处理并记录它,不要向上抛。
- 不要使用printStackTrace
要给异常添加上有用的上下文信息,单纯的异常栈,没有太大意义
- Throw early catch late
异常界著名的原则,错误发生时及早抛出,然后在获得所以全部信息时再捕获处理. 也可以理解为在低层次抛出的异常,在足够高的抽象层面才能更好的理解异常,然后捕获处理。
- 对于使用一些重量级资源的操作,发生异常时,一定记得清理
如网络连接,数据库操作等,可以用try finally来做clean up的工作。
- 不要使用异常来控制程序逻辑流程
我们总是不经意间这么做了,这样使得代码变更丑陋,使得正常业务逻辑和错误处理混淆不清; 而且也可能会带来性能问题,因为异常是个比较重的操作
- 及早校验用户的输入
在最边缘的入口校验用户的输入,这样使得我们不用再更底层逻辑中处处校验参数的合法性, 能大大简化业务逻辑中不必要的异常处理逻辑;相反,在业务中不如果担心参数的合法性, 则应该使用卫语句抛出运行时异常,一步步把对参数错误的处理推到系统的边缘,保持系统内部的清洁。
- 在打印错误的log中尽量在一行中包含尽可能多的上下文
五、集合使用
1.关于hsahCode和equals的处理,遵循如下规则:
只要重写 equals,就必须重写 hashCode。
因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。
如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals。
说明:String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地使用 String 对象作为 key 来使用。
2.ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出 ClassCastException 异常,即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList.
说明:subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是ArrayList 的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。
3.在 subList 场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均会产生 ConcurrentModificationException 异常。
4.不要在 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);
}
}
5.集合初始化时,指定集合初始值大小。
说明:HashMap 使用 HashMap(int initialCapacity) 初始化,减少hashMap多次进行扩容机制。其他集合类同理。
正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loader factor)默认为 0.75,如果暂时无法确定初始值大小,请设置为 16(即默认值)。
反例:HashMap 需要放置 1024 个元素,由于没有设置容量初始大小,随着元素不断增加,容量7 次被迫扩大,resize 需要重建 hash 表,严重影响性能。
6.使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。
说明:keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.foreach 方法。
正例:values()返回的是 V 值集合,是一个 list 集合对象;keySet()返回的是 K 值集合,是一个 Set 集合对象;entrySet()返回的是 K-V 值组合集合。
7.对集合进行操作时,用java 8的特性Stream操作,这样对于编码来说效率增加。代码量减少
//使用Java8
List<RuleDTO> ruleDTOS = accountRules.stream().map(
rule -> new RuleDTO(rule.getId(), rule.getRuleType(), rule.getRuleTitle(),
rule.getGmtCreate(), rule.getGmtModified(), rule.getIsDeleted(), rule.getStatus())).collect(
Collectors.toCollection(() -> new ArrayList<>(accountRules.size())));
推荐使用Stream操作集合类
stream 相对于 Collection 的优点
- 无存储
- 惰性求值
- 无需上界
- 代码简练
ps:以上参考阿里Java开发规