(一)命名规范
-
【强制】类名使用 UpperCamelCase 风格,但以下情形例外:DAO / DTO / POJO 等。
正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
-
【强制】方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式。
-
【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
正例:MAX_STOCK_COUNT
反例:MAX_COUNT / MaxCount / maxCount
-
【强制】抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Tests 结尾。单元测试方法以测试场景+Test 结尾。
正例:AdminLoginTests / adminLoginSuccessTest() / adminLoginFailedTest()
反例:AdminLoginTest / testAdminLoginSuccess()
-
【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。
正例:org.springframework.util.CollectionUtils
-
【强制】杜绝完全不规范的缩写,避免望文不知义。
-
【强制】对于 Service ,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。
正例:CacheServiceImpl 实现 CacheService 接口。
-
【推荐】为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。
正例:在 JDK 中,表达原子更新的类名为:AtomicReferenceFieldUpdater。
反例:变量 int temp 的随意命名方式。
-
【推荐】枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。
正例:枚举名字为 ProcessStatusEnum 的成员名称:SUCCESS / UNKNOWN_REASON。
-
【推荐】各层命名规约:
10.1 Service/DAO 层方法命名规约
1)获取单个对象的方法用 get 做前缀。
2)获取多个对象的方法用 list 做前缀,复数形式结尾如:listObjects。
3)获取分页对象的方法用 page 做前缀,复数形式结尾如:pageObjects。
4) 获取统计值的方法用 count 做前缀。
5) 插入的方法用 save/insert 做前缀。
6) 删除的方法用 remove/delete 做前缀。
7) 修改的方法用 update 做前缀。
10.2 领域模型命名规约
1)数据传输对象:xxxDTO,xxx 为业务领域相关的名称。
(二)常量规范
-
【强制】不允许任何 Magic Number(即未经预先定义的常量)直接出现在代码中。
-
【强制】在 long 或者 Long 赋值时,数值后使用大写的 L。
正例:Long a = 2L
反例:Long a = 2l / Long a = 21
-
【推荐】不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
-
【推荐】常量复用层次:
4.1 包内共享常量:即在当前包下单独的 constant 目录下。
4.2 类内共享常量:直接在类内部 private static final 定义。
-
【强制】同一类型不同意义的常量必须加上前缀,例如 ORDER_STATUS_XXX , XXX_TYPE_XXX。
正例:CARD_TYPE_CASH = 1; CARD_TYPE_MEMBER_CARD = 2; CARD_TYPE_DISCOUNT = 3;
反例:CASH = 1; MEMBER_CARD = 2; DISCOUNT = 3;
(三)代码格式
-
【强制】单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:
1) 第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进,参考示例。
2) 运算符与下文一起换行。
3) 方法调用的点符号与下文一起换行。
4) 方法调用中的多个参数需要换行时,在逗号后进行。
5) 在括号前不要换行,见反例。
正例:
StringBuffer sb = new StringBuffer();
sb.append("zi").append("xin")...
// 超过 120 个字符的情况下,换行缩进 4 个空格,点号和方法名称一起换行
.append("huang")...
.append("huang")...
.append("huang");
反例:
StringBuffer sb = new StringBuffer();
// 超过 120 个字符的情况下,不要在括号前换行
sb.append("zi").append("xin")...append
("huang");
// 参数很多的方法调用可能超过 120 个字符,不要在逗号前换行
method(args1, args2, args3, ...
, argsX);
- 【强制】类的属性定义要留空行,不然加上一堆注解,分不清哪一段是哪一段。
正例:
@ApiModelProperty(value = "账号", required = true, example = "admin")
@NotBlank(message = "账号不能为空")
private String account;
@ApiModelProperty(value = "密码", required = true, example = "123456")
@NotBlank(message = "密码不能为空")
private String password;
反例:
@ApiModelProperty(value = "账号", required = true, example = "admin")
@NotBlank(message = "账号不能为空")
private String account;
@ApiModelProperty(value = "密码", required = true, example = "123456")
@NotBlank(message = "密码不能为空")
private String password;
@ApiModelProperty(value = "确认密码", required = true, example = "123456")
@NotBlank(message = "确认密码不能为空")
private String confirmPassword;
- 【强制】在if / else / for / while / do语句中必须使用大括号,即便是只有一行代码,也需要添加大括号。
反例:
if (condition)
statements;
正例:
if (condition) {
statements;
}
-
【强制】禁用 import *,使包的引入更清晰明了,避免 IDE format 时相互修改 imports。
-
【强制】SQL 文件名称使用下划线分割,内容缩进使用4个空格。
-
【推荐】定义好 Reformat code 的快捷键,养成经常 format 代码的习惯。
-
【推荐】单个方法的总行数不超过 80 行。说明:包括方法签名、结束右大括号、方法内代码、注释、空行、回车及任何不可见字符的总行数不超过 80 行。
说明:代码逻辑分清红花和绿叶,个性和共性,绿叶逻辑单独出来成为额外方法,使主干代码更加清晰;共性逻辑抽取成为共性方法,便于复用和维护。
-
【推荐】单行代码行宽应尽量避免超出 100 个字符(IDE 中的标准线),禁止出现一行必须滚动才能看清全部的代码。
-
【推荐】不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。
说明:任何情形,没有必要插入多个空行进行隔开。
(四)Api 文档规约
-
【强制】每个 Api 参数字段都需要注明对应的中文注释,如果是 code 类型,则需要写上备注,注明每个 code 对应的注释。
-
【强制】禁止直接返回 POJO 作为 Api 返回参数。因为 JOOQ 生成的 POJO 无法添加 Swagger 注解,而且不是所有字段都对前端有用,造成无谓的序列化,浪费内存。
-
【强制】请求参数和返回参数都需要写上
example
和position
。
(五)OOP 规约
-
【强制】外部正在调用或者第三方服务依赖的接口,不允许 api 路径,避免对接口调用方产生影响。接口过时或不再使用时,必须加
@Deprecated
注解,并清晰地说明采用的新接口或者新服务是什么。 -
【强制】所有的覆写方法,必须加@Override 注解。
-
【强制】不能使用过时的类或方法。
说明:作为调用方,有义务去考证过时方法的新实现是什么。
-
【强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
说明:应使用
java.util.Objects.equals()
-
【强制】所有的相同类型的包装类对象之间值的比较,全部使用
java.util.Objects.equals()
方法比较。说明:对于
Integer var = ?
在-128 至 127 范围内的赋值,Integer 对象是在 IntegerCache.cache 产生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用java.util.Objects.equals()
方法进行判断。 -
关于基本数据类型与包装数据类型的使用标准如下:
1) 【强制】所有的 POJO 类属性必须使用包装数据类型。
2) 【强制】RPC 方法的返回值和参数必须使用包装数据类型。
3) 【推荐】所有的局部变量使用基本数据类型。
说明:POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,都由使用者来保证。
正例:数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。
反例:比如显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。
-
【强制】POJO 类必须写 toString 方法。可使用 Lombok
@ToString
注解。 -
【强制】当列表类型返回值为空时,避免返回 null 或 new ArrayList<>(),应返回
Collections.emptyList()
。说明:
Collections.emptyList()
返回一个不可变的静态列表。 -
【推荐】 类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法。
说明:公有方法是类的调用者和维护者最关心的方法,首屏展示最好;保护方法虽然只是子类关心,也可能是“模板设计模式”下的核心方法;而私有方法外部一般不需要特别关心,是一个黑盒实现;因为承载的信息价值较低,所有 Service 和 DAO 的 getter/setter 方法放在类体最后。
(六) 集合处理
-
【强制】关于 hashCode 和 equals 的处理,遵循如下规则:
1) 只要重写 equals,就必须重写 hashCode。
2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。
3) 如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals。
-
【强制】使用集合转数组的方法,必须使用集合的
toArray(T[] array)
,传入的是类型完全一样的数组,大小就是 list.size()。说明:使用 toArray 带参方法,入参分配的数组空间不够大时,toArray 方法内部将重新分配内存空间,并返回新数组地址;如果数组元素个数大于实际所需,下标为[ list.size() ]的数组元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。
正例:
List<String> list = new ArrayList<String>(2);
list.add("guan"); list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);
反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。
-
【强制】使用工具类
Arrays.asList()
把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。说明:
asList()
方法实质调用了new ArrayList<>(a)
返回一个不可变的Arrays.ArrayList
内部类。 -
【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。
正例:
List<String> list = new ArrayList<>();
list.add("1"); list.add("2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (删除元素的条件) {
iterator.remove();
}
}
反例:
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
(七)DAO 规约
-
【强制】禁止在 DAO 层定义
store
方法(即插入/更新逻辑都在同一方法处理),应分别定义insertXXXX
,updateXXXX
方法,并在Service
层处理相关业务逻辑。 -
【强制】禁止使用 DAO 的
store
方法插入或更新数据,应显式调用insert
或update
-
【推荐】查询分页时,若 fetchCount 为 0,则直接返回空列表的分页数据,可避免执行后面的分页语句。
-
【推荐】查询分页时,应避免使用联表查询,当数据量上万或十万级别时,单表查询的速度会比联表查询分页快2-10倍。
-
【推荐】当需要联超过3个以上的表,应考虑拆分成单表查询。
(八)建表规约
-
【强制】表必备三字段:
id
,created_at
,updated_at
。 -
【强制】表名、字段名必须使用小写字母或数字,并以下划线分割。
-
【强制】表结构修改必须按 Flyway 的版本去更新,禁止直接在旧版本的迁移脚本中修改字段。应由项目负责人定期合并版本。
-
【强制】表、字段必须添加注释
COMMENT xxxx
,字段名修改时也应该同时更新对应注释。 -
【强制】任何字段如果为非负数,必须是
unsigned
。 -
【推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:
1)不是频繁修改的字段。
2)不是 varchar 超长字段,更不能是 text 字段。
(九)数据映射(MapStruct)
-
【强制】无需映射的字段,应显式 ignore 声明,避免编译期出现 Warning
正例:
@Mapping(target = "name", ignore = true)