目前大部分团队都在使用阿里巴巴Java开发规范,但在日常开发中难免会遇到无法覆盖的场景。本文在阿里巴巴Java开发规范的基础上,增加了一些常用的规范,以提高代码质量,增强代码可读性。编程协议1.基本类型和操作(1)转换基本类型和将String类型转换为数字:使用apachecommon-lang3包中的工具类NumberUtils。优点:可以设置默认值,转换出错时返回默认值。NumberUtils.toInt("1");拆箱:包装类转换为基本类型时,需要判断null,例如:IntegernumObject=param.get(0);intnum=numObject!=null?对象数:0;使用MapStruct工具进行对象类型转换,转换类后缀Convertor,所有的转换操作都在转换类中进行,禁止在业务代码中大量编写set代码。(2)判断枚举判断用的是枚举判断,不是枚举对应的数。因为枚举比较直观,方便查看代码和调试,数字容易出错。空判断各种对象的Null判断://对象判断null&non-nullObjects.isNull()Objects.nonNull()//String判断null&non-nullStringUtils.isEmpty()//可以匹配null和空字符串StringUtils.isNotEmpty()StringUtils.isBlank()//可以匹配null、空字符串、多个空白字符StringUtils.isNotBlank()//Collection判断空&非空CollectionUtils.isEmpty()CollectionUtils.isNotEmpty()//Map判断Empty&non-emptyMapUtils.isEmpty()MapUtils.isNotEmpty()使用Guava中的Preconditions工具类进行断言,例如://如果为空,则抛出异常Preconditions.checkNotNull()//一般判断Preconditions.checkArgument()2.CollectionHandling(1)Map快捷操作推荐://如果值不存在,计算map.computeIfAbsent("key",k->execValue(k));//默认值map.getOrDefault("key",DEFAULT_VALUE)反例://判断值是否不存在Stringv=map.get("key");if(v==null){v=execValue("key");map.put("key",v);}//默认值map.containsKey("key")?map.get("key"):DEFAULT_VALUE(2)创建对象构造方法或Builder模式,使用Builder模式创建3个以上参数对象//Java11+:List.of(1,2,3)Set.of(1,2,3)Map.of("a",1)//Java8中的不可变集合(需要引入Guava)ImmutableList.of(1,2,3)ImmutableSet。的(1,2,3)我utableMap.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")反例:newArrayList<>(){{添加(1);添加(2);}};(3)如果集合嵌套集合中的值是基本类型,则必须添加注释说明集合中存放的是什么,例如://返回值:Map(key:name,value:List(commodity))地图<字符串,列表<字符串>>res;超过2层的集合对象封装必须封装成自定义类://推荐Map>res;@ValuepublicstaticclassNode{/***备注描述字段*/字符串名称;/***注释描述字段2*/ListsubjectIds;}//反例Map>>>res;异常和日志1.异常关于异常和错误码的思考,可以参考笔者的另一篇文章:关于错误码设计的思考有些调用可能会失败,需要上层自行决定处理策略。推荐使用vavr中的Either类。Either使用建议:通常我们用左值表示异常,用右值表示正常调用后的返回结果,即:Either2,log(1)日志文件一般分为4个log根据日志级别的文件:调试.log,info.log,warn.log,error.log;如果有特殊需要,可以根据场景单独创建文件,比如requestlog:request.log,gclog:gc.log等。(2)所有用户日志必须有Tracking字段Tracking字段包括:traceId,userId等。推荐使用MDC,常用的日志框架:Log4j和Logback都支持。(3)日志清理和本地日志持久化根据磁盘大小,必须设置日志存储天数,否则有硬盘满的风险;分布式环境下为了查询方便,需要将日志收集到ES中进行查询;重要日志:如审计日志、B端操作日志需要持久化保存,一般在Hive中;工具1、JSON推荐:使用Gson或Jackson;不推荐:Fastjson。Fastjson存在很多漏洞。2、对象转换推荐:MapStruct,基于注解编译成Java代码,无反射,速度快;可预测的行为,您可以检查已编译的Java代码以查看转换逻辑;不推荐:BeanUtils、Dozer等,需要反射,行为不可预测,需要测试;不推荐:手动转换超过3个字段;3、模板代码推荐:Lombok,减少代码行数,提高开发效率,自动生成Java代码,性能无损;不推荐:手动生成大量set和get方法;4、参数验证推荐:hibernateValidation、spring-boot-starter-validation,可以通过注解自动实现参数拦截;不推荐:每个入口(如Controller)复制大量重复验证的Logic;5、缓存推荐:SpringCache,通过注解控制缓存逻辑,适用于常见的缓存场景。设计1.正向语义正向语义的优点是它使代码易于理解。比如:if(judge()){....},很好理解,就是如果判断成功,就会执行代码块。反之,如果是否定语义,则需要换一种思路,一般用于方法前面的参数验证。积极语义的应用场景是:方法定义:方法名推荐:canPass、checkParam,返回true表示成功。不推荐:比如isInvalidParam返回true表示失败,增加了理解成本;Lambda表达式:过滤运算符中returntrue是可以通过的元素;if和三元运算符:条件?doSomething():doSomething2(),判断条件后后面是判断成功后要执行的操作。反例:if(!judge()){doSomething2()}else{doSomething()}二、防御性编程(1)外部数据校验所有外部数据都需要校验,一般分为两类:数据流入:用户Http请求、RPC请求、MQ消费者等数据依赖:如果依赖的第三方RPC、数据库等是数据流入,首先要验证数据的合法性再进行。推荐使用hibernate等工具很方便Validation数据校验的数据是数据依赖的。必须考虑网络、限流、背压等各种场景,确保熔断和降级。建议建立一个防腐层,将第三方有界上下文语义转换为当前上下文语义,避免理解上的歧义;(2)对于强依赖(比如查询数据库),没有返回值无法进行null处理:直接抛出异常;需要反馈给上层处理:(1)可能返回null的场景:useOptional;(2)上层需要感知信息异常信息:使用vavr中的Either;可降级:(1)返回值为默认值:集合类返回,数字返回0或-1,字符串返回空字符串,其他场景自定义集合默认值:Collections.emptyList()//空ListCollections。emptySet()//emptySetCollections.emptyMap()//emptyMap总结本文总结了Java开发常用的高层规范,暂时想到这么多,如果对文中的观点感兴趣,欢迎留言或加微信进行交流。作者博客链接:Java研发规范(进阶版)作者简介:穆晓峰,美团点评Java技术专家,专注于分享软件研发实践和架构思维。欢迎关注公众号:Java研发更多精彩文章:错误代码设计思维Java线程池高级架构从MVC到DDD的演进平台搭建思路浅谈构建回滚应用和在线Checklist实践