版本记录
设计者
|
版本
|
日期
|
备注
|
无涯
|
V1.0.0
|
2022-04-19
|
初始版本
|
无涯
|
V1.0.1
|
2022-06-06
|
调整文件格式并完善部分章节
|
1.背景
1.1 愿景
编码是一门艺术,优雅编码,规范编码,愿大家早日成为艺术大师。
1.2 适用读者
- 初中级程序员,参考编码的一些规范和建议。
- 代码审批人员,审核代码时,一些参考的指标和方向。
- 本文所举例的代码缺陷,改造参考方向,难免存在理解错误或偏差之处,欢迎批评指正。
- 本文中所举案例,主要是用于记录作者在开发中遇到的一些不规范现象,一些编码技巧及调优方向,随着时间沉淀会持续总结和更新。
2. 非规范案例
Set 集合去重
项目
|
描述
|
描述
|
申明一个对象,如xxxDto,却未申明去重的参考标准。Set xxxSet = new HashSet<>();
|
潜在风险
|
集合不会去重,或去重与业务需求不一样。
|
调整方案
|
xxxDto类,重写equal,hashCode方法。参考:Set去重问题。
|
备注
|
暂无。
|
避免采用反逻辑运算符
项目
|
描述
|
描述
|
代码中存在大量判空集合:if(!CollectionUtils.isEmpty(reqDto.getList())
|
潜在风险
|
反逻辑,容易造成理解偏差;
|
调整方案
|
推荐使用: org.apache.commons.collections.CollectionUtils.isEmpty()
org.apache.commons.collections.CollectionUtils.isNotEmpty()
|
备注
|
《java开发手册》-控制语句-11
|
集合函数为空返回null
项目
|
描述
|
描述
|
参考代码:获取list数据,为空时返回null
|
潜在风险
|
可能导致NullPointExcepetion
|
调整方案
|
推荐使用: return Collections.emptyList();
|
备注
|
明确单个对象,为空返回null;集合时不要返回null;
|
慎用BaseController
项目
|
描述
|
描述
|
每一个controller对外暴露接口时,可能存在异常等情况,或分页等通用的一些功能。编写BaseController,让其他Controller去继承。
|
潜在风险
|
BaseController功能需要业务Controller去继承,造成代码冗余且不便扩展。
|
调整方案
|
编写统一的WebMvc配置类,基于
@RestControllerAdvice(annotations = RestController.class)做异常兜底处理。
|
备注
|
基于BaseController的模式:非常外行。
|
java 8的LocalDate代替Date
项目
|
描述
|
描述
|
日期,时间,大量基于Date做日期处理
|
潜在风险
|
Date是存在多线程不安全性。
|
调整方案
|
建议使用LocalDate
扩展了解java 8的一些新特性,基于java 8编码。
|
备注
|
Java 18都出来了,先进入java 8吧,一定要快!!!
|
枚举类使用
项目
|
描述
|
描述
|
可列举的变量大量使用了静态字符串。如 4表示审批中,直接定义了静态变量值为4。
比较数据是否为某一种类型时,直接使用魔法值处理。如:getStatus()==4。
|
潜在风险
|
避免使用魔法值,避免使用静态字符串,变量混用容易造成业务混乱;
|
调整方案
|
全局统一的一些状态,变量等使用枚举值统一控制。针对状态值,建议使用枚举。
且枚举申明要满足规范,定义变量值时,定义为final。
|
备注
|
枚举扩展性高,便于做类型判断
|
代码注释规范
项目
|
描述
|
描述
|
注释,命名等大量非规范的情况。如:
函数命名不规范,错误的定义返回类型;
代码注释不完整,在接口的实现类上写注释;
|
潜在风险
|
错误或混乱的注释,造成代码可读性变差。
|
调整方案
|
idea安装
Alibaba Java Coding Guidelines 插件
安装阿里巴巴编码规范插件,可以自动识别不规范的编码。
|
备注
|
1.代码注释格式要统一
2.变量命名应遵循规范,统一定义规则,如驼峰。
3.混乱的编写注释,显得外行。
|
命名规范性
项目
|
描述
|
描述
|
外部系统,或部分业务对象,命名混乱。
如is_enable,a_b_ddd;
部分函数,接口,类等命令不具有可读性,命名不规范;
|
潜在风险
|
代码可读性差;
|
调整方案
|
建议统一使用驼峰命名方式。对接外部系统或特殊情况:
1.可在对应变量上编写@JsonProperty做自动转义;
2.若驼峰需要转换下划线,编写统一工具类实现
|
备注
|
命名请参考:《java开发手册》-命名风格-1
|
@ResponseBody使用混乱
项目
|
描述
|
描述
|
在Controller中,定义对外提供的api接口时,在方法上,在类上混乱使用@ResponseBody的情况
|
潜在风险
|
带偏新同事;
|
调整方案
|
参考@restController的作用,可不必写@ResponseBody
默认就是返回json对象。
|
备注
|
使用注解时,了解该注解的使用场景,不要滥用。
|
String字符串非空拦截
项目
|
描述
|
描述
|
String 字符串非空使用@NotNull
|
潜在风险
|
若是空字符串,拦截失效
|
调整方案
|
根据业务场景,建议使用@NotBlank
|
备注
|
暂无
|
验证完整性问题
项目
|
描述
|
描述
|
功能验证逻辑写在前端,后端提供的接口,未编写逻辑验证;
|
潜在风险
|
前端验证失效时,或直接调用接口时,造成业务异常。
|
调整方案
|
验证逻辑:
后端一定要编写严格的业务验证;
前端做第一层验证,后端要编写第二层验证,数据层做第三次验证;
|
备注
|
暂无
|
深拷贝问题
项目
|
描述
|
描述
|
使用序列化做深拷贝对象的处理。
将对象转换为json字符串,将字符串再转换为对象。
|
潜在风险
|
降低了函数的性能。
|
调整方案
|
1.避免在循环中,采用序列化做深拷贝。
2.设计功能时,部分逻辑是可以不用深拷贝实现的,应避免。
|
备注
|
字符串做深拷贝,性能不好。
|
数据库字段类型规范定义
项目
|
描述
|
描述
|
态类型字段避免使用varchar存储;
日期使用date,不做默认值设置等;
|
潜在风险
|
不合理的数据类型,在数据表后续优化时,如建立索引,会造成空间浪费,性能差等情况。
|
调整方案
|
1.类型字段:建议使用tinyint。避免浪费数据库资源。
2.建议非特殊业务字段,所有字段设置默认值,不允许为空。
|
备注
|
新建数据表时,考虑数据的可能情况,用合理的最小的空间,建立数据字段。
新建数据表时,考虑该表的业务使用场景,构建好唯一索引,普通索引。
|
数据表关键字作为字段名称
项目
|
描述
|
描述
|
xxx表中的state字段。
|
潜在风险
|
造成部分场景下,sql识别失败。
|
调整方案
|
避免使用数据库关键字。
|
备注
|
暂无。
|
布尔字段申明为isXXX
项目
|
描述
|
描述
|
isDetete字段,申明类型为Boolean;
|
潜在风险
|
不同序列化工具,产生序列结果不同。
|
调整方案
|
所有字段类型,避免这样的申明。
|
备注
|
《java开发手册》-编程规约-命名风格-8暂无。
|
数据物理删除
项目
|
描述
|
描述
|
数据物理删除,在数据库执行Delete的场景
|
潜在风险
|
物理删除,数据不能留痕。存在误删等巨大的风险。
|
调整方案
|
物理删除,数据不能留痕。存在误删等巨大的风险。
|
备注
|
无
|
字符串代表数组
项目
|
描述
|
描述
|
使用字段如a:
a 表示模板id,且表示多个,用逗号隔开;
用一个变量表示集合字段,如:String a="2,3,4";
|
潜在风险
|
不合理的数据类型,代码可读性差,不便于系统扩展和前后端接口对接。
|
调整方案
|
字段表示集合,数组含义时,不要定义为字符串。
建议使用数组,如 String[] reviewTemplateIds;
|
备注
|
明确参数类型,不要基于字符串做特殊的处理。不便于功能扩展。(如数组数量@size()限制)
|
正确使用事务
项目
|
描述
|
描述
|
事务使用的场景,格式不对,显得混乱;
|
潜在风险
|
编写大事务,系统性能差,占用了数据库资源。
|
调整方案
|
1.避免编写大事务,后续数据量大以后,系统性能不好。
2.单表操作,不使用事务。
3.注解使用@Transactional
propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, 这个配置是系统默认的,可不编写。
4.注意事务传递性等
|
备注
|
注意事务的传递性,慎用。
|
正确创建线程池
项目
|
描述
|
描述
|
代码中,存在周期性任务脚本,线程池构建代码:Executors.newScheduledThreadPool(2);
|
潜在风险
|
创建不会限制最大线程数,会造成OOM风险。
|
调整方案
|
基于ScheduledThreadPoolExecutor,自定义线程池。
|
备注
|
建议修改,后期数据量大,会造成jvm频繁FullGc,影响性能或造成系统死掉。
|
正确使用异步
项目
|
描述
|
描述
|
异步使用,使用的Spring boot 默认的线程。
|
潜在风险
|
造成OOM风险。
|
调整方案
|
配置自定义线程池,代替默认的线程。参考:。
|
备注
|
微服务若使用了@Async,必须配置异步线程池。
|
循环中操作外部资源
项目
|
描述
|
描述
|
直接在循环中,多次操作数据库的情况。如:
for(i=0;i<100;i++){
xxxMapper.select(i);
xxxMapper.update(i);
}
直接在循环中,单次调用远程接口;
|
潜在风险
|
严重拉低系统性能,拖死服务。
|
调整方案
|
1.建议将操作的最小模块,独立出来,判断是否可以异步单个处理最小模块。
2.单条数据查询,更新等考虑一次批量处理,获取数据到内存后,在处理。
|
备注
|
采用批量接口做逻辑处理,避免在循环中单条处理。
|
使用非序列化对象做参数
项目
|
描述
|
描述
|
使用非序列化出入参问题,如使用Map结构:Map data
|
潜在风险
|
非对象模式,容易引发null等不确定问题。不便于对象的扩展和维护。
|
调整方案
|
采用对象的模式。避免前后过程式处理。
|
备注
|
面向对象编程。
|
大函数问题
项目
|
描述
|
描述
|
部分函数代码太长,表示了多个含义,如500行的功能函数。
|
潜在风险
|
函数功能不唯一,函数逻辑复杂,行数多,不便于后期扩展和维护。
|
调整方案
|
建议一个函数至多50行代码。
1.小函数代表一个明确的功能。
2.多用设计方法达到函数复用,函数剥离的效果。
|
备注
|
先把函数写小,控制行数。
|
3.编码调优参考
数据对象设置安全对象
项目
|
描述
|
描述
|
远程调用其他系统后,业务代码存在重复的判空,验证等流程。如:
Response sampleNumRes = xxxFeignClient.query(xxxxTemp);
if(sampleNumRes!=null && sampleNumRes.getData() != null && sampleNumRes.getData().getTotal_num()!=null){
}
|
缺陷
|
调用其他API接口,获取数据对象时,总会要做各种判空处理,代码严重冗余;
|
调整方案
|
编写:ResponseUtils工具类,并提供安全方法,非安全方法。改造如下:
xxxxDto= ResponseUtils.safe(xxxFeignClient.query(xxxTemp));
基于泛型,穷举判断返回是数组,列表,对象等是否为空。
安全性:接口返回成功,返回的对象最终data是一定存在值的。
非安全性:接口返回成功,返回的对象不一定存在值。
请求异常:调用接口失败,或对方返回非成功状态的信息。直接拦截报错。
|
备注
|
参数代码:
/**
* 返回内容解析工具类 参考Objects.requireNonNull(T, String)源码,便于把为空的数据,在一个地方抛出异常 方便在调用的地方,不再做 空(null,empty) 判断
*
* @author wangling
* @date 2022/5/25
*/
public class FeignResponseBodyUtil {
/**
* 获取安全的数据 接口返回的内容一定存在值
*
* @param responseBody 接口返回体
* @param exceptionMessage 可变参数,异常文本描述
* @param 数据类型
* @return 一定存在值的数据
*/
public static T getSaleData(FeignResponse responseBody, String... exceptionMessage) {
String messageInfo = null;
if (exceptionMessage.length > 0) {
messageInfo = exceptionMessage[0];
}
checkDataSuccess(responseBody);
T object = responseBody.getData();
if (object == null
|| (object instanceof Collection && ((Collection<?>) object).isEmpty())
|| (object.getClass().isArray() && Array.getLength(object) < 1)
|| (object instanceof String && StringUtils.isBlank((String) object))) {
String dataType = object.getClass().getTypeName();
String exceptionMess = String.format("返回数据类型 %s 数据为空", dataType);
String message = Optional.ofNullable(messageInfo).orElse(exceptionMess);
throwException(message);
}
return responseBody.getData();
}
public static void throwException(String message) {
throw new BusinessException(String.format("data_null:%s", message));
}
/**
* 获取非安全数据
*
* @param responseBody 接口返回体
* @param 数据类型
* @return 返回数据(数据可能为空)
*/
public static T getUnsaleData(FeignResponse responseBody) {
checkDataSuccess(responseBody);
return responseBody.getData();
}
/**
* 检查接口返回体是否为成功状态
*
* @param responseBody 接口返回体
* @param 数据类型
*/
public static void checkDataSuccess(FeignResponse responseBody) {
if (Objects.isNull(responseBody)) {
throw new BusinessException("404:远程服务返回对象为空");
}
if (!Objects.equals(responseBody.getCode(), ResultCodeEnum.SUCCESS.getCode())) {
String failCode =
Objects.nonNull(responseBody.getCode()) ? String.valueOf(responseBody.getCode())
: "SERVICE_FALL_BACK";
String failMsg =
StringUtils.isNotEmpty(responseBody.getMessage()) ? responseBody.getMessage() : "远程服务不可用";
throw new BusinessException(failCode + "->" + failMsg);
}
}
}
|
判断对象是否为空
项目
|
描述
|
描述
|
eqDto.getStatus() != null等判空
|
缺陷
|
暂无
|
调整方案
|
建议使用: Objects.isNull(),Objects.nonNull()
|
判断对象是否相等
项目
|
描述
|
描述
|
xxx.DISEASE_POSITIVE == xxxDto.getDiseaseType()
|
缺陷
|
避免直接使用equal,==比较,对象为空时,报错。
|
调整方案
|
建议采用:Objects.equals()
|
集成测试用例代码
项目
|
描述
|
描述
|
系统中无测试用例代码;
存在部分单元测试,缺乏对controller提供接口的集成测试;
|
缺陷
|
测试用例未在代码中体现,不能留痕;
|
调整方案
|
为每一个controller编写对应的集成测试。
完整验证该接口是否正常。
|
备注
|
强制编写测试用例,便于提高开发人员的自测工作效率,并为后续开发人员,测试人员提供测试参考的示例。
|
巧用反射
项目
|
描述
|
描述
|
如一个对象中,对所有类型为Decimal的字段,统一加密翻译为另外一个字段;
代码遍历处理对象中的所有Decimal字段;
|
缺陷
|
功能重复,代码可重用度低,不便于扩展。
|
调整方案
|
针对不同对象,属性字段逻辑处理一致的功能,建议采用反射机制代替穷举模式。
基于反射的模式,批量处理一个对象,做逻辑处理。
|
备注
|
统一处理代替逐个处理,编码时,一定不要写死代码。
|
面向对象编程
项目
|
描述
|
描述
|
许多业务代码,基本是面向过程编码,缺乏对象的封装,抽样,导致代码冗余,过程繁杂。如:
基于if/else,switch等模式做过程是分支;
基于方法编程,未做接口封装;
|
缺陷
|
面向过程编码,后期代码缺乏剥离,冗余复杂,难以维护。
|
调整方案
|
建议看看《重构》,《代码整洁之道》,面向对象编码思维。
1.多用设计方法,如模板方法,策略设计模式,代理等替换过程是编码;
2.复杂过程做DDD领域分层,做功能模块化拆分;
|
备注
|
实现一个功能,一定要考虑后期的扩展性和可维护性。
|
函数性能考量
项目
|
描述
|
描述
|
许多业务函数,存在性能不高,如重复计算,轮询获取数据等。
|
缺陷
|
低性能函数,拉低系统性能。
|
调整方案
|
编写函数后,建议思考该函数是否还存在性能优化的空间。
1.内存逻辑代替循环取数逻辑。
2.函数功能单一,避免大而全的函数,导致函数性能下降。
|
备注
|
性能考量应该作为代码评审的一个标准。
|
优雅关闭
项目
|
描述
|
描述
|
系统关闭时,还存在运行的异步线程等如何优雅关闭。
|
缺陷
|
不做处理,系统在特殊条件下,存在问题且难以排查。
|
调整方案
|
1.编写全局配置文件,重新实现SmartLifecycle接口,重写stop()方法实现优雅关闭。
2.系统关闭时,系统内部设置等待时间,便于执行未完成的任务。
3.记录关闭时,执行异常的日志信息。
|
备注
|
单个微服务,应包括优雅关闭模块。
|
4.感谢
- 作为一名程序员,您一定在研发过程中,或多或少有过排雷采坑的经历。欢迎留言,分享更多编码调优的经验。
- 本文参考《java-开发手册-泰山版》,《代码整洁之道》,《重构2》等编码规范,编码理论知识。欢迎留言探讨书中的相关知识。