编码规范 - 养成良好的Java编码习惯
最近在整理公司编码规范方面的内容,2017
年阿里巴巴
发布了编码规范插件,强烈建议大家安装使用,好的编码习惯是通往成功的阶梯。
- SpringBoot配套源码地址:https://gitee.com/hengboy/spring-boot-chapter
- SpringCloud配套源码地址:https://gitee.com/hengboy/spring-cloud-chapter
- SpringBoot相关系列文章请访问:目录:SpringBoot学习目录
- QueryDSL相关系列文章请访问:QueryDSL通用查询框架学习目录
- SpringDataJPA相关系列文章请访问:目录:SpringDataJPA学习目录
- SpringBoot相关文章请访问:目录:SpringBoot学习目录,感谢阅读!
技术群二维码在底部,欢迎进群学习!!!
简书整套文档以及源码解析
专题 | 专题名称 | 专题描述 |
---|---|---|
001 | Spring Boot 核心技术 | 讲解SpringBoot一些企业级层面的核心组件 |
002 | Spring Cloud 核心技术 | 对Spring Cloud核心技术全面讲解 |
003 | QueryDSL 核心技术 | 全面讲解QueryDSL核心技术以及基于SpringBoot整合SpringDataJPA |
004 | SpringDataJPA 核心技术 | 全面讲解SpringDataJPA核心技术 |
文档目录
- 注释规范
- 类注释
- 方法注释
- 行级注释
- DTO/Param注释
- 编码规范
- 命名风格
- 常量定义
- 代码格式
- OOP 规约
- 集合处理
- 控制语句
- 异常日志规范
- 异常处理
- 日志规约
一、注释规范
类注释
类、类属性使用Javadoc
规范,类上描述该类的主要作用,注释尽可能详细,推荐把使用该类地方使用@see
注解进行标注,类属性详细描述该属性的保存内容。
类注释示例
:
/**
* 统一资源Aop切面定义
* 根据自定义注解配置自动设置配置的资源类型到指定的字段
* @author:于起宇 <br/>
* ===============================
* Created with IDEA.
* Date:2017/12/15
* Time:14:05
* ================================
*/
类属性注释示例
:
/**
* 资源处理业务逻辑
*/
Autowired
ResourcePushService resourcePushService;
方法注释
接口方法、实现类方法、抽象方法等,详细描述该方法主要作用,尽可能的描述出方法的主要流程步骤,方法定义的每一个参数都需要有详细的注释描述,建议添加方法返回值描述。
方法注释示例
:
/**
* 资源设置切面方法
* 拦截配置了@ResourceMethod注解的class method
* cglib仅支持class 方法切面,接口切面不支持
* @param proceedingJoinPoint 切面方法实例
* @param resourceMethod 方法注解实例
* @return 原方法返回对象
*/
@Around(value = "@annotation(resourceMethod)")
public Object resourcePutAround(ProceedingJoinPoint proceedingJoinPoint, ResourceMethod resourceMethod)
throws Throwable
{
//...
}
如果是业务逻辑方法注释建议添加步骤,如下所示:
/**
* 创建帖子
* - 转换参数实体为
* - 保存帖子基本信息
* @param param 创建帖子请求参数实体
* @return 帖子编号
* @throws LogicException
*/
public String create(CreateTopicParam param) throws LogicException {
//...
}
在上面代码中-
分隔符代表主要执行步骤,每一个步骤以-
分隔符开始,如果方法内存在逻辑分支处理,请看下面行注释
。
行注释
行级注释一般都是方法内使用到,分为单行注释、多行注释,单行注释采用//
设置,多行注释采用/* */
设置,如下所示:
单行注释
:
// 执行方法,获取返回值
多行注释
:
/*
* 执行方法,获取返回值
* 获取返回值进行后续逻辑处理
*/
DTO/Param注释
我们在实际开发过程中数据库对应的实体是不允许直接拿出来添加一些附加字段的,也就是禁止添加非该数据表对应实体内的字段,这种情况我们需要定义DTO/Param
。
DTO注释
DTO
是数据返回实体定义,如果我们在查询数据库时需要关联其他表的数据并且返回给前端,那么我们可以创建XxxDTO
,注意:DTO
全部大写,只需要继承查询逻辑的主表实体就可以完成附加字段的添加,要为每一个附加字段添加javadoc
详细注释,如下所示:
/**
* 帖子列表数据转换实体
* @author:于起宇 <br/>
* ===============================
* Created with IDEA.
* Date:2018/1/8
* Time:10:52
* ================================
*/
@Data
public class TopicListDTO
extends CommunicationTopicInfoEntity {
/**
* 用户昵称
*/
private String userNickName;
/**
* 所属机构名称
*/
private String orgName;
//...
}
Param注释
对于接口、后台来说接受请求时一般都是带着一些参数,目前我们系统是前台完全分离,所以后台其实变相的也是接口,在上面DTO
也有说到数据实体不允许添加附加参数,我们的参数也不可能都是数据实体内的字段,这时需要创建对应的参数实体XxxParam
,参数实体内的所有字段都需要添加javadoc
注释,如下所示:
/**
* 查询帖子列表
* - 用于查询自己、他人、关键字、首页帖子请求参数
*
* @author:于起宇 <br/>
* ===============================
* Created with IDEA.
* Date:2018/1/8
* Time:10:31
* ================================
*/
@Data
public class SelectTopicParam extends PagerParam {
/**
* 用户编号
*/
private String userId;
/**
* 查询关键字
*/
@Length(max = 30)
private String keyWord;
}
二、编码规范
命名风格
- 代码中命名不能以下划线或美元符号开始,也不能以下划线或美元符合结束。
错误示例
:
_name / name_ / $name / name$ / __name / name__
- 命名严禁出现中文拼音与英文混合方式出现,不允许直接使用中文方式命名
错误示例
:
WenZhang[文章] / WenZhangInfo[文章信息] / getTopicLieBiao()[获取文章列表] / int 数量 = 0
- 类名使用
UpperCamelCase
风格,DTO
、VO
除外
错误示例
:
QRCode / UserInfoDto / XMLService
正确示例
:
QrCode / UserInfoDTO / XmlService
- 方法名、参数名、成员变量、局部变量统一使用
lowerCamelCase
风格,必须使用驼峰命名方式。
错误示例
:
localvalue / GetUserInfo() / userid
正确示例
:
localValue / getUserInfo() / userId
- 常量命名全部大写,单词间下划线隔开,完整的表达含义,名字可以过长。
错误示例
:
MAX_COUNT / NAME
正确示例
:
MAX_STOCK_COUNT / DEFAULT_ORG_NAME
- 抽象类命名使用
Abstract
或者Base
开头,异常类命名使用Exception
结尾,测试类命名时以将要测试的类全名 +Test
。
正确示例
:
AbstractCodeMessageService / BaseCodeMessageService / LogicException / UserControllerTest
- 包名统一使用小写,每个分隔符必须为自然语义的英文单词,另外包名统一使用单数含义,如果需要复数含义,则可以在类名上体现。
正确示例
:
com.sanmi.framework.core / com.sanmi.framework.orm
- 杜绝不规范的缩写,避免词义表达不清楚。
错误示例
:
AbstractClass = > AbsClass
condition => condi
- 接口中的方法和属性不要添加任何修饰符(public也不要添加),为了保持代码的简洁性,加上有效的
javadoc
注释。
错误示例
:
public abstract void commit();
正确示例
:
void commin(); / String COMPANY_NAME = "sanmi";
- 对于
Service
和DAO
类命名时实现类后缀要以Impl
结尾,Mapper
接口不存在实现类,无需处理。
正确示例
:
UserInfoService / UserInfoServiceImpl
- 枚举类名统一带上
Enum
后缀,枚举成员名称需要全大写,单词间使用下划线隔开。
正确示例
:
枚举名称 => UserStatusEnum
成员名称 => ENABLE / DISABLED / DELETE
- 各层方法命名规约
Service/DAO/Mapper
层方法命名规约如下:- 获取单个对象的方法用
get
作为前缀 - 获取多个对象的方法用
list
作为前缀 - 获取统计值的方法用
count
作为前缀 - 插入方法用
save / insert
作为前缀 - 删除方法用
delete / remove
作为前缀 - 修改方法用
update
作为前缀
- 获取单个对象的方法用
常量定义
- 不允许任何魔法值(未经过预先定义的常量)直接出现在代码中
错误示例
:
if("enable".equals(user.getStatus())) {
//..
}
- 使用封装类型代替基本数据类型
正确示例
:
Integer => int
Double => double
Long => long
...
- 如果常量仅在一个范围内变化,使用
enum
来代替定义
正确示例
:
@Getter
public enum UserBzEnum
{
/**
* 普通用户
*/
APP_USER("0"),
/**
* 月嫂
*/
NANNY("1"),
/**
* 医生
*/
DOCTOR("2")
;
private String value;
UserBzEnum(String value) {
this.value = value;
}
}
代码格式
- 大括号的使用约定,如果大括号内为空,直接使用
{}
即可,不需要换行;如果非空代码,则需要:- 左大括号前不换行
- 左大括号后换行
- 右大括号换行
- 右大括号后还有else等代码则不换行;表示终止的右大括号后必须换行。
- 左小括号和字符之间不出现空格;右小括号和字符之间也不出现空格。
错误示例
:
if ( a == b )
- if / for / while / switch / do 等保留字与括号之间都必须加空格
- 任何二目、三目运算符的左右两边都需要加一个空格。
正确示例
:
if (ValidateTools.isEmpty(uploadBackEntity) || ValidateTools.isEmpty(uploadBackEntity.getUploadUrl())) {
//...
}
- 采用4个空格缩进,禁止使用
Tab
控制符。 - 行级注释的
//
与注释内容之间有且仅有一个空格。
正确示例
:
// 定义用户名
String userName = user.getName();
- 单行字符不超过120个,超过需要换行,换行原则如下:
- 第二行相对于第一行缩进4哥空格,从第三行开始不再进行缩进
- 运算符一起换行(如:
+
) -
.
符号一起换行 - 方法调用中的多个参数需要换行时,在逗号后进行。
正确示例
:
StringBuffer buffer = new StringBuffer();
// 超过120个字符的情况下,换行缩进4个空格,``.``和方法名一块换行
buffer.append("san").append("mi") ...
.append("ke")
.append("ji");
// 参数过多超120个字符时,在 , 后换行
method(args1, args2, args3, ...
args98, args99
)
- 方法参数在定义和传入时,多个参数后面必须加空格。
正确示例
:
method(args1, args2);
OOP 规约
- 避免通过一个类的对象引用访问此类的静态变量或者静态方法,会造成编译器解析成本,直接用类名访问即可。【JVM堆、栈、静态代码块解析成本是不一样的】
- 所有覆盖方法,必须添加
@Overrider
注解 - 对外部正在调用或者二方库依赖的接口,不允许修改方法签名,以避免对接口调用方产生影响;如果接口已经过时,必须添加
@Deprecated
注解,并清晰的说明替代接口或者替代服务。 - 禁止使用过时类或方法。
-
Object
的equals
方法容易抛出空指针异常,应使用常量或确定值的对象来调用equals
方法。
错误示例
:
object.equals("test");
正确示例
:
"test".equals(object)
- 所有相同类型的封装类之间比较,必须使用
equals
方法。
说明
:int
封装类Integer
在-128 ~ 127
范围内的赋值会在IntegerCache.cache
中产生,该区间的值可以直接使用==
进行比对,但是该区间外的值都会以引用类型在堆
内创建,对象之间是无法使用==
来进行比对的!
- 构造函数内禁止编写任何业务逻辑,如果有业务逻辑请创建
init
方法使用。
集合处理
- 使用集合转数组的方法,必须使用集合的
toArray(T[] array)
,传入的是类型完全一样的数组,大小则是list.size()
。
正确示例
:
List<String< list = new ArrayList();
list.add("jinan");
list.add("sanmi");
// 生成list长度的数组实例
String[] array = new String[list.size()];
// 执行集合转换数据
array = list.toArray(array);
- 在使用工具类
Arrays.asList()
方法时,不能使用修改集合相关的方法,add / remove / clean
方法会抛出异常。 - 禁止在
foreach
循环内进行元素的remove / add
操作,remove
元素请使用Iterator
方式,如果并发操作,需要对Iterator
对象加锁。
正确示例
:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (condition) {
iterator.remove();
}
}
控制语句
- 在一个
switch
内,每个case
要么使用break / return
来终止,要么注释说明程序将继续执行到具体的哪个case
为止;在一个switch
内必须包含default
代码块在所有case
之后,就算业务逻辑为空也要存在! - 在
if / else / for / while / do
语句中必须使用大括号,即便是只有一行代码,也需要添加大括号。
错误示例
:
if (condition) statements;
正确示例
:
if (condition) {
statements;
}
- 在表达异常分支时,尽可能的少用
if / else if
嵌套方式,可以修改成:
if (condition) {
//...
return object;
}
// 继续处理 else / else if 业务逻辑代码
如果不得不使用超过三层的if / else if
逻辑判断,可以将代码改成卫语句
、策略设计模式
、状态设计模式
,下面是卫语句示例:
public void today() {
if (isBuy()) {
System.out.println("do something.");
return;
}
if (isFree()) {
System.out.println("do something.");
return;
}
System.out.println("coding..");
}
在我们系统设计时,一般都会使用throw new XxxException();
将return;
代替,所有逻辑异常判断都采用自定义业务逻辑异常进行处理。
三、异常日志规范
异常规约
- Java 类库中定义的可以预判断来规避
RuntimeException
,不应该采用try {} catch(Exception e){}
来处理。
错误示例
:
// 直接使用不确定对象
object.setXxx(value);
正确示例
:
// 判断非空后使用不确定对象
if (object != null) {
//...
}
- 捕获异常目的是为了处理它,不要捕获了却什么都不处理而抛弃,如果不想处理它,请将该异常抛给它的调用者,最外层的业务使用者必须处理异常,将异常信息转换成用户可以理解的提示信息。
- 有
try
代码块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意rollback
事务。 -
finally
代码块必须对资源对象、刘对象进行关闭操作,即使有异常也要做try-catch
操作。 - 不能在
finally
代码块中使用return
。 - 业务逻辑异常请交付给框架处理,我们将业务逻辑验证使用业务逻辑异常处理的机制进行抛给框架处理。
正确示例
:
if (condition) {
throw new LogicException(ErrorCodeEnum.USER_NOT_FOUND);
}
日志规约
- 代码中不可直接使用日志系统(Log4j、Logback)中的
API
,而依赖使用日志框架SLF4j
中的API
。使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
正确示例
:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// 获取日志对象
private static final Logger logger = LoggerFactory.getLogger(Xxx.class);
日志对象定义一般都会在项目框架搭建初期定义,直接使用即可,如果需要自定义,则按照上面的方式进行声明日志对象。
- 对
trace / debug / info
级别的日志输出,必须使用占位符的方式,如果不使用占位符而是直接拼接,可能会导致变量为null
导致系统异常,还一点日志等级不匹配时虽然不会打印,但是会执行字符串的拼接,浪费服务器系统资源。
正确示例
:
logger.debug("执行查询用户:{},基本信息。",user.getId());
- 针对方法的主要参数需要打印对应的值,方便后期日志调试项目。
正确示例
:
public void today(String userId) {
logger.debug("查询用户:{},今日任务完成进度",userId);
}
- 方法内分支逻辑需要打印对应的日志信息,方便后期日志调试项目。
正确示例
:
if (condition1) {
logger.debug("do something.");
} else if (condition2) {
logger.debug("do other thing.");
}
- 系统异常包括两大类:
案发现场
、异常堆栈信息
.
正确示例
:
logger.error(参数或对象.toString() + "_" + e.getMessage(), e);
写在最后
强烈建议IDEA
开发工具安装使用阿里巴巴
国际编码规约插件,为良好的编码习惯打下基础。
欢迎加入QQ技术交流群,共同进步。