Java项目规约
# 编程规约
目前大部分团队是使用的阿里巴巴Java开发规范,不过在日常开发中难免遇到覆盖不到的场景,本文在阿里巴巴Java开发规范基础上,补充一些常用的规范,用于提升代码质量及增强代码可读性。阿里巴巴开发手册 (opens new window)
# 基础类型及操作
基本类型转换
字符串转数字时,使用
org.apache.commons.lang3.math.NumberUtils
, 优点是可以设置默认值,转换出错时可以返回默认值(默认返回0)。Integer i = NumberUtils.toInt("1"); Integer i = NumebrUtils.toInt("1", 0);
1
2拆箱:当包装类转换为基本类型时需要判断是否为 null:
Integer i = paras.get(0); int j = i != null ? i : 0;
1
2对象类型转换
使用MapStruct工具(官方文档 (opens new window)),转换类后缀Convertor,所有转换操作都在转换类中操作,禁止在业务代码中编写大量set代码。或者看这篇 (opens new window)
判断
枚举判定
使用枚举判等,而不是枚举中的数字。因为枚举更加直观,方便查看以及调试,而数字容易出错。使用
==
优于equals
,具体可参考这两个链接:https://medium.com/danonrockstar/comparing-enum-values-in-java-85e484a9190c 和 https://stackoverflow.com/questions/1750435/comparing-java-enum-members-or-equals;而从 Enums 的源码中可以看到其重写了 equals 方法,也是使用 == 进行判等。判空
各种对象判空:
// 对象判空和非空 Objects.isNull(); Objects.nonNull(); //String判空&非空 StringUtils.isEmpty() //可匹配null和空字符串 StringUtils.isNotEmpty() StringUtils.isBlank() //可匹配null、空字符串、多个空白字符 StringUtils.isNotBlank() StringUtils.hasText() // 非null,非空字符串,非空白字符(如空格、tab、换行符等) //集合判空&非空 CollectionUtils.isEmpty() CollectionUtils.isNotEmpty() //Map判空&非空 MapUtils.isEmpty() MapUtils.isNotEmpty()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18断言
使用 Guava 工具中的 Preconditionis 工具类:
// 如果为 null 则抛出异常 Preconditions.checkNotNull(...) //通用判断 Preconditions.checkArgument(...)
1
2
3
4
# 集合处理
Map 快捷操作
推荐:
// 如果值不存则计算 map.computeIfAbsent("key", k -> execValue(k)); // 默认值 map.getOrDefault("key", "default");
1
2
3
4反例:
//如果值不存在则计算 String v = map.get("key"); if(v == null){ v = execValue("key"); map.put("key", v); } //默认值 map.containsKey("key") ? map.get("key") : DEFAULT_VALUE
1
2
3
4
5
6
7
8创建对象
构造方法或 Builder 模式,超过 3 个参数对象创建,使用 Builder 模式
//Java11+: List.of(1, 2, 3) Set.of(1, 2, 3) Map.of("a", 1) //Java8中不可变集合(需引入Guava) ImmutableList.of(1,2,3) ImmutableSet.of(1,2,3) ImmutableMap.of("key","value") //多值情况 ImmutableMap.builder() .put("key", "value") .put("key2", "value2") .build() //Java8中可变集合(需引入Guava) Lists.newArrayList(1, 2, 3) Sets.newHashSet(1, 2, 3) Maps.newHashMap("key", "value")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19反例:
new ArrayList<>(){{ add(1); add(2); }};
1
2
3
4集合嵌套
集合里的值如果是基础类型必须加上注释,说明集合里存的是什么,比如:
//返回值: Map(key: 姓名, value: List(商品)) Map<String, List<String>> res;
1
2超过 2 层集合对象封装必须封装成自定义类:
//推荐 Map<String, List<Node>> res; @Value public static class Node { /** * 备注说明字段 */ String name; /** * 备注说明字段2 */ List<Integer> subjectIds; } //反例 Map<String, List<Pair<String, List<Integer>>>> res;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 异常
# 统一异常处理
# 错误码设计
# 日志
# 统一日志记录格式
# 日志文件
# 日志追踪字段 traceId
微服务全链路
# 日志归档
日志查看 ELK
# 工具篇
JSON
推荐:使用 Gson 或 Jackson
不推荐:FastJson,bug 多
对象
推荐:
- MapStruct,根据注解编译成 Java 代码,没有反射,速度快;行为可预测,可查看编译后的 Java 代码查看转换逻辑;运行速度与硬编码差不多,因为在编译期间就生成了 Java Bean 属性复制的代码,运行期间就无需使用反射或者字节码技术,所以确保了高性能。支持深拷贝
- Spring BeanUtils 内部使用了缓存,加快转换的速度,浅拷贝
- Cglib BeanCopier 使用字节码技术动态生成一个代理类,代理类实现get 和 set方法,缓存代理类重复使用;性能优于 Spring BeanUtils;浅拷贝
- orika 重量级;底层其使用了 javassist 生成字段属性的映射的字节码,然后直接动态加载执行字节码文件;性能好;支持深拷贝
不推荐:
- Apache BeanUtils 大量反射,性能差;
- Dozer 重量级;使用反射;性能差;不再维护;深拷贝
不推荐:超过3个字段手动转换;
模板代码
推荐:Lombok,减少代码行数,提升开发效率,自动生成 Java 代码,没有性能损耗;
不推荐:手动生成大量 set、get 方法;
参数校验
推荐:hibernate Validation、spring-boot-starter-validation,可通过注解自动实现参数拦截;
不推荐:每个入口(比如Controller)都 copy 大量重复的校验逻辑;
缓存
推荐:Spring Cache,通过注解控制缓存逻辑,适合常用的加缓存场景。
# 设计篇
正向语义
正向语义的好处在于使代码容易理解。 比如:if(judge()){…},很容易理解,即:判定成功则执行代码块。
相反,如果是负向语义,思维还要转换一下,一般用于方法前置的参数校验。
正向语义的应用场景有:
- 方法定义:方法名推荐:canPass、checkParam,返回 true 代表成功。 不推荐:比如 isInvalidParam 返回 true代表失败,增加理解成本;
- Lambda 表达式:filter 操作符中返回 true 是可以通过的元素;
- if 和三目运算符:condition ? doSomething() : doSomething2() , 条件判定后紧跟的是判定成功后执行的操作。
防御式编程
1)外部传过来数据都需要校验,一般分为两类:
- 数据流入:用户 Http 请求、RPC 请求、MQ 消费者等
- 数据依赖:依赖的第三方 RPC、数据库等
如果是数据流入,一定要首先校验数据合法性再往下执行,推荐 hibernate Validation 这类工具,可以很方便的做数据校验
如果是数据依赖,一定要考虑各种网络、限流、背压等场景,做好熔断、降级保障。推荐建立防腐层(Anti-Corruption Layer),将第三方的限界上下文语义转换为当前上下文语义,避免理解上的歧义;
2)Null 处理
对于强依赖,没有返回值不行(比如查询数据库):直接抛异常;
需要反馈给上层处理:
(1)可能返回 null 的场景:使用 Optional;
(2)上层需要感知信息异常信息:使用 vavr 中的 Either;
可降级:
(1)返回值是默认值:集合类返回,数字返回 0 或 -1,字符串返回空字符串,其他场景自定义
集合默认值:
Collections.emptyList() // 空 List Collections.emptySet() // 空 Set Collections.emptyMap() // 空 Map
1
2
3
# 参考
- 作者:木小丰 https://lesofn.com/archives/java-coding-standard