后端开发规约

240 阅读11分钟

前言

本文大部分公约来自于阿里巴巴《Java开发手册》嵩山版。主要目的是为了使开发人员以一种可接受的、统一的方式编码,提升协作效率,减少沟通成本,由于阿里巴巴的《Java开发手册》是作为业界被认可的公约,也有助于开发人员养成良好的开发习惯。 本文作为组为Coding Conventions 的第一版,主要内容包括:编程规约、数据库规约,业务约束,阅读者有其他需要补充欢迎在评论区作答,方便继续完善本文档。

1. 编程规约

1. 命名规范

1. 命名语言

避免使用中文拼音、中英混合命名变量,国际通用的中文拼音可以作为变量名 正例:chengdu/biaoguo 反例:String spName = "香蕉" int rihuo = 10000

2. 多词命名

类名使用 UpperCamelCase 方法名、参数名、成员变量、局部变量使用 lowerCamelCase 常量、枚举成员使用全大写并且单词之间使用下划线隔开

3. 布尔类型变量

任何POJO对象的布尔型变量不得以is开头

4. 包命名

包名使用多个单个单词加点分隔符连接,包名不得使用复数格式,比如:com.biaoguo.work.starfruit.seller

5. 缩写

避免不规范的缩写,比如:absStock 应该命名成:abstractStock

6. 名词类型

推荐在命名是,把类型的名称放在最后,比如:startCreateTime

7. 接口类的方法、变量

注意:接口类的方法和变量不要加任何修饰符号,接一般情况下不要定义变量,即使要定义变量也要确保是整个应用的基础变量。

2. 常量定义

1. 魔法值不被允许

未经预先定义的常量不能直接出现代码中。

2. Long与long

Long与long型变量的数值后面都只能跟L,避免l和1混淆。

3. 常量类单一职责

不要把所有的业务的常量放在一个常量类中,比如:系统配置常量类

3. OOP规约

1. 类名访问静态方法、属性

避免使用一个类对象访问静态方法和属性,无畏增加编译器解析成本。

2. 可变参数

尽量避免使用可变参数,即使使用也应该是相同参数类型、相同业务含义才可以使用,避免使用Object,建议可变参数放置参数列表最后。 正例:

public List<User> listUsers(String type, Long... ids) {...}

3. 过时接口

过时接口必须打 @Deprecated 注解,建议注释为什么停用以及新接口地址,并且也不能调用过时接口

4. equals方法顺序

已知非空变量放于equals前面 或者: 使用 JDK7 引入的工具类 java.util.Objects#equals(Object a, Object b)

5. 整型包装类型变量比较

包装类型变量比较建议都使用equals方法, 对于 Integer var = ? 在-128 至 127 之间的赋值,Integer 对象是在 IntegerCache.cache 产生, 会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都 会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。

6. 浮点类型数据比较

浮点数据基本数据类型不能使用==比较,包装数据类型不能使用equals比较,因为有可能丢失精度。可以使用BigDecimal来定义值比较,或者定义误差范围

7. BigDecimal使用

要求精度的变量都必须使用BigDecimal,并且使用字符串作为构造函数传入参数,或者BigDecimal.ValueOf( );必须使用CompareTo比较。

8. 基础数据类型与包装数据类型

所有对象属性必须使用包装数据类型,局部变量可以使用基础数据类型

9. DTO、VO对象属性

所有DTO、VO属性不能设定任何默认值,当对象作为更新使用时,会附带更新数据库该属性的值。

10. 属性提取

POJO类中不能同时存在isXXX()和getXXX()方法,框架针对该属性提取时,不确定哪一个方法会被调到。

11. 循环体内连接字符串

循环体内连接字符串使用StringBuilder.append(XXX),避免字符串加号追加,因为每次都会new一个新的StringBuilder对象,造成资源浪费。

4. 日期时间

1. 格式化

pattern传入格式化字符串年分字段必须小写

2. 年天数

一年天数禁止写死,闰年会有问题。 推荐:

 // 获取今年的天数 
int daysOfThisYear = LocalDate.now().lengthOfYear();
 // 获取指定某年的天数 
LocalDate.of(2011, 1, 1).lengthOfYear();

5. 集合处理

1. hashCode与equals

只要复写equals必须复写hashCode, Set集合存储对象时,对象必须复写equals和hashCode方法 自定义对象作为Map健时,必须复写equals和hashCode方法

2. 集合判空

集合判定是否为空必须使用isEmpty而不是List ==null或者size()==0

3. 集合转键值对

在使用 java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,一定要使 用含有参数类型为 BinaryOperator,参数名为 mergeFunction 的方法,否则当出现相同 key 值时会抛出 IllegalStateException 异常。 必须注意:value不能null,不然会抛空指针。 推荐:

List<Pair<String, Double>> pairArrayList = new ArrayList<>(3);
pairArrayList.add(new Pair<>("version", 12.10));
pairArrayList.add(new Pair<>("version", 12.19));
pairArrayList.add(new Pair<>("version", 6.28));
Map<String, Double> map = pairArrayList.stream().collect(Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));

4. subList

subList不可强转ArrayList,subList是ArrayList的视图和ArrayList一样继承了AbstractList,因此不能强转。

5. Map遍历

使用 Map 的方法 keySet()/values()/entrySet()返回集合对象时,不可以对其进行添 加元素操作,否则会抛出 UnsupportedOperationException 异常。

6. Collections返回对象

emptyList()/singletonList() 返回的集合不能进行新增、删除操作,因为emptyList是被EmptyList创建的,继承AbstractList,并且EmptyList没有实现add、remove方法,调用add、remove方法时调用父类的add、remove方法,会直接抛异常UnsupportedOperationException。singletonList()返回的是SingletionList,与EmptyList一样

7. Arrays.asList()

Arrays.asList()不能调用集合add/remove/clear,Arrays.asList()返回是Arrays内部类,不支持写操作。

8. 循环内集合写操作

不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。

6. 并发处理

1. 线程池创建(一般不需要)

创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。

2. 线程使用

线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明: 线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

3. SimpleDateFormat使用

SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static, 必须加锁,或者使用 DateUtils 工具类。

反例:
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 
正例:
private static final ThreadLocal<DateFormat> sdf = new ThreadLocal<DateFormat>() {
    @Override
    protected DateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd");
    }
};

4. Lock方式获取锁

在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代 码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁。

7. 控制语句

1. Switch

在一个 switch 块内,每个 case 要么通过 continue/break/return 等来终止,要么 注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default语句并且放在最后,即使它什么代码也没有。 当 switch 括号内的变量类型为 String 并且此变量为外部参数时,必须先进行 null 判断。

2. 三目运算符

condition? 表达式 1 : 表达式 2 中,高度注意表达式 1 和 2 在类型对齐 时,可能抛出因自动拆箱导致的 NPE 异常。 说明:以下两种场景会触发类型对齐的拆箱操作: 1) 表达式 1 或表达式 2 的值只要有一个是原始类型。 2) 表达式 1 或表达式 2 的值的类型不一致,会强制拆箱升级成表示范围更大的那个类型。

3. 并发退出条件

在高并发场景中,避免使用”等于”判断作为中断或退出的条件。 如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件 来代替。

8. 注释规约

1. 格式

类、类属性、类方法 : /内容/ 方法内部单行注释 ://内容 方法内部多行注释 : / 内容*/

2. 数据库规约

1. 建表规约

1. 表名、字段名

表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只 出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。 表名与字段名不能为空

2. 索引名

主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。

3. 字段类型

小数类型为 decimal,禁止使用 float 和 double。

4. 超长字符串

varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度 大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效 率。

5. 建表必备三字段

表必备三字段:id, create_time, update_time。

6. 冗余字段

冗余字段应该遵循: 不是频繁修改的字段。 不是唯一索引的字段。 不是 varchar 超长字段,更不能是 text 字段

2. 索引规约

1. varchar字段建立索引

在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据 实际文本区分度决定索引长度。 说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90% 以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度来确定。

2. 索引有序性

如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索 引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。

3. 超多分页

先快速定位需要获取的 id 段,然后再关联: SELECT t1.* FROM 表 1 as t1, (select id from 表 1 where 条件 LIMIT 100000,20 ) as t2 where t1.id=t2.id

4. 区分度

建组合索引的时候,区分度最高的在最左边,但需要结合最左原则考虑实际情况。

3. SQL规约

1. 模糊查询

页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。

2. count函数

不要使用 count(列名)或 count(常量)来替代 count(),count()是 SQL92 定义的标 准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。

3. count(distinct col)

计算该列除 NULL 之外的不重复行数,注意 count(distinct col1, col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0

4. sum函数

当某一列的值全是 NULL 时,sum(col)的返回结果为 NULL,因此使用 sum()时需注意 NPE 问题。 正例: 可以使用如下方式来避免 sum 的 NPE 问题:SELECT IFNULL(SUM(column), 0) FROM table;

5. 判空

使用 ISNULL()来判断是否为 NULL 值。

6. getone

禁止使用mybatisplus getone方法,严重依赖数据索引唯一性

3. 业务约束

1. 增强代码健壮性

1. 逻辑校验

逻辑校验必须后端兜底,不能根据前端按钮展示隐藏控制逻辑

2. 远程调用

远程调用出现非集合参数超过2个或者一个集合参数、对象封装参数一律使用对象将参数封装起来,增强扩展性和可读性

3. 消息发送

发消息使用异步方式发送,不阻塞业务流程。

4. 循环内请求

禁止循环内远程调用以及调用数据库。

5. RPC循环调用

尽量避免两个及以上服务循环调用。