# Java后端开发规范

> 拟稿：[黎润豪](/lirh)<a href="https://blog.lroyia.top" target="_blank">（个人博客）</a>
>
> <div><span>版本号 1.0.0 </span></div>
>

## 目录

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Java后端开发规范](#java%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91%E8%A7%84%E8%8C%83)

- [前言](#%E5%89%8D%E8%A8%80)
- [编程约束](#%E7%BC%96%E7%A8%8B%E7%BA%A6%E6%9D%9F)
  - [命名风格](#%E5%91%BD%E5%90%8D%E9%A3%8E%E6%A0%BC)
    - [类命名](#%E7%B1%BB%E5%91%BD%E5%90%8D)
    - [方法命名](#%E6%96%B9%E6%B3%95%E5%91%BD%E5%90%8D)
    - [变量命名](#%E5%8F%98%E9%87%8F%E5%91%BD%E5%90%8D)
  - [泛型](#%E6%B3%9B%E5%9E%8B)
  - [代码结构](#%E4%BB%A3%E7%A0%81%E7%BB%93%E6%9E%84)
  - [OOP规约](#oop%E8%A7%84%E7%BA%A6)
  - [控制语句](#%E6%8E%A7%E5%88%B6%E8%AF%AD%E5%8F%A5)
  - [注释规范](#%E6%B3%A8%E9%87%8A%E8%A7%84%E8%8C%83)
- [系统分层规范](#%E7%B3%BB%E7%BB%9F%E5%88%86%E5%B1%82%E8%A7%84%E8%8C%83)
  - [controller层](#controller%E5%B1%82)
  - [service层](#service%E5%B1%82)
  - [Dao/Mapper层](#daomapper%E5%B1%82)
- [接口管理规范](#%E6%8E%A5%E5%8F%A3%E7%AE%A1%E7%90%86%E8%A7%84%E8%8C%83)
- [异常处理规范](#%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E8%A7%84%E8%8C%83)
  - [异常处理](#%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86)
  - [异常处理原则](#%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E5%8E%9F%E5%88%99)
  - [异常处理的编码规范](#%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E7%9A%84%E7%BC%96%E7%A0%81%E8%A7%84%E8%8C%83)
- [日志规约](#%E6%97%A5%E5%BF%97%E8%A7%84%E7%BA%A6)
- [工程结构](#%E5%B7%A5%E7%A8%8B%E7%BB%93%E6%9E%84)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## 前言

本文为公司Java后端开发的代码规范，规范分如下两级：  

- ![强制](../img/icon/强制.png) ：开发者必须遵循的开发规范要求
- ![推荐](../img/icon/推荐.png) ：开发者选择性遵循的开发规范要求

原则：约定至上；Keep Simple。（周志尧指定）  
约定优于配置。

## 编程约束

- ![强制](../img/icon/强制.png) 禁止在个人编写的代码，注释或文档中添加任何非业务或说明相关的外部链接。

### 命名风格

- ![强制](../img/icon/强制.png) 代码中的命名不能以下划线或美元符号开始，也不能以下划线或美元符号结束。

- ![强制](../img/icon/强制.png) 杜绝完全不规范的缩写，避免望文不知义。

> ![反例](../img/icon/反例.png) ：AbstractClass缩写成AbsClass；condition缩写成condi；Function缩写成Fu。

- ![强制](../img/icon/强制.png) 严禁使用非全世界都知道的拼音命名。

> ![正例](../img/icon/正例.png) ：alibaba/youku/hangzhou  
> ![反例](../img/icon/反例.png) ：suanFen【算分】，getGTHTypeCode【获取个体户类型编码】

#### 类命名

- ![强制](../img/icon/强制.png) 采用大驼峰（UpperCamelCase）命名法，禁止使用数字命名。

> ![正例](../img/icon/正例.png) ：KeyPair，GlobalConfig  
> ![反例](../img/icon/反例.png) ：areaSlice，AreaSlice1，AreaSlice2

- ![推荐](../img/icon/推荐.png) 采用【名词】，【形容词+名词】的格式进行命名

> ![正例](../img/icon/正例.png) ：SMSSender，GlobalConfig  
> ![反例](../img/icon/反例.png) ：SendSMS

- ![强制](../img/icon/强制.png) 抽象类必须以Abstract或Base开头（工具类例外：如StringUtils）；异常类名明必须以Exception结尾；测试类必须以Test结尾。

- ![强制](../img/icon/强制.png) 接口的参数类必须要以VO或Dto结尾命名；接口与接口之间的传输参数传输类必须以Dto结尾命名。

#### 方法命名

- ![强制](../img/icon/强制.png) 采用小驼峰（LowerCamelCase）命名法，禁止使用拼音命名

- ![推荐](../img/icon/推荐.png) 方法参数不多于5个，多于5个后改用DTO进行传输。

#### 变量命名

- ![强制](../img/icon/强制.png) 常量与枚举均使用大写+下划线组合的方式命名

> ![正例](../img/icon/正例.png) LIMIT_SIZE，ENTITY_TYPE

- ![强制](../img/icon/强制.png) 采用小驼峰（LowerCamelCase）命名法，禁止使用拼音命名

- ![强制](../img/icon/强制.png) 局部非循环与下标变量，不可使用单字母命名（特殊数学含义例外，如斜截式的斜率a，常数b）

### 泛型

- ![强制](../img/icon/强制.png) 涉及泛型的类，严禁不写前泛。

![正例](../img/icon/正例.png) ：

```java
List<String> resultList = new ArrayLisy<>();
```  

![反例](../img/icon/反例.png) ：

```java
List resultList = new ArrayLisy<String>();
Map resultList = new HashMap();
```  

### 代码结构

- ![强制](../img/icon/强制.png) 采用 4 个空格缩进，禁止使用 tab 字符。IDEA 设置 tab 为 4 个空格时，请勿勾选 `Use tab character`；（设置界面 `editor -> code style -> java`）

- ![强制](../img/icon/强制.png) 单行字符数限制不超过120个，超出需要换行; （设置界面 `File -> Settings -> Code Sytle` 中勾选`Wrap on typing`选项,`Right Margin`为设置的字符数限制）

- ![推荐](../img/icon/推荐.png) `{}`代码块嵌套不应超过4层

- ![推荐](../img/icon/推荐.png) 单个方法的总行数不超过 100 行。

> 说明：除注释之外的方法签名、左右大括号、方法内代码、空行、回车及任何不可见字符的总行数不超过 100行。代码逻辑分清红花和绿叶，个性和共性，绿叶逻辑单独出来成为额外方法，使主干代码更加清晰；共性逻辑抽取成为共性方法，便于复用和维护。

### OOP规约

- ![强制](../img/icon/强制.png) 构造方法里面禁止加入任何业务逻辑，如果有初始化逻辑，放在 init 方法中。

- ![推荐](../img/icon/推荐.png) 当一个类有多个构造方法，或者多个同名方法，这些方法应该按顺序放置在一起，便于阅读。

- ![推荐](../img/icon/推荐.png) 类内方法定义的顺序依次是：公有方法或保护方法 > 私有方法 > getter / setter 方法。

> 说明：公有方法是类的调用者和维护者最关心的方法，首屏展示最好；保护方法虽然只是子类关心，也可能是“模板设计模式”下的核心方法；而私有方法外部一般不需要特别关心，是一个黑盒实现；因为承载的信息价值较低，所有 Service 和 DAO 的 getter/setter 方法放在类体最后。

- ![推荐](../img/icon/推荐.png) `setter`方法中，参数名称与类成员变量名称一致，`this.成员名 = 参数名`。在`getter/setter`方法中，不要增加业务逻辑，增加排查问题的难度。

- ![推荐](../img/icon/推荐.png) 循环体内，字符串的连接方式，使用 StringBuilder 的 append 方法进行扩展。

> 说明：下例中，反编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象，然后进行 append 操作，最后通过 toString 方法返回 String 对象，造成内存资源浪费。

### 控制语句

- ![推荐](../img/icon/推荐.png) 除常用方法（如`getXxx/isXxx`）等外，不要在条件判断中执行其它复杂的语句，将复杂逻辑判断的结果赋值给一个有意义的布尔变量名，以提高可读性。

> 说明：很多 if 语句内的逻辑表达式相当复杂，与、或、取反混合运算，甚至各种方法纵深调用，理解成 本非常高。如果赋值一个非常好理解的布尔变量名字，则是件令人赏心悦目的事情。

- ![推荐](../img/icon/推荐.png) 不要在其它表达式（尤其是条件表达式）中，插入赋值语句。

> 说明：赋值点类似于人体的穴位，对于代码的理解至关重要，所以赋值语句需要清晰地单独成为一行。

- ![推荐](../img/icon/推荐.png) 循环体中的语句要考量性能，以下操作尽量移至循环体外处理，如定义对象、变 量、获取数据库连接，进行不必要的`try - catch`操作（这个`try - catch`是否可以移至循环体外）。

### 注释规范

- ![强制](../img/icon/强制.png) 类、类属性、类方法的注释必须使用 Javadoc 规范，使用/*内容/格式，不得使用 // xxx 方式。

> 说明：在 IDE 编辑窗口中，Javadoc 方式会提示相关注释，生成 Javadoc 可以正确输出相应注释；在 IDE 中，工程调用方法时，不进入方法即可悬浮提示方法、参数、返回值的意义，提高阅读效率。

- ![强制](../img/icon/强制.png) 所有的抽象方法 （ 包括接口中的方法 ） 必须要用 Javadoc 注释、除了返回值、参数、异常说明外，还必须指出该方法做什么事情，实现什么功能。

- ![强制](../img/icon/强制.png) 所有的类都必须添加创建者和创建日期。

- ![强制](../img/icon/强制.png) 方法内部单行注释，在被注释语句上方另起一行，使用//注释。方法内部多行注释 使用/**/注释，注意与代码对齐。

- ![强制](../img/icon/强制.png) 所有的枚举类型字段必须要有注释，说明每个数据项的用途。
- ![推荐](../img/icon/推荐.png) 代码修改的同时，注释也要进行相应的修改，尤其是参数、返回值、异常、核心逻辑等的修改。

- ![推荐](../img/icon/推荐.png) 谨慎注释掉代码。在上方详细说明，而不是简单地注释掉。如果无用，则删除。

- ![推荐](../img/icon/推荐.png) 对于注释的要求：第一、能够准确反映设计思想和代码逻辑 ； 第二、能够描述业务含义，使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同天书，注释是给自己看的，即使隔很长时间，也能清晰理解当时的思路 ； 注释也是给继任者看的，使其能够快速接替自己的工作。

- ![推荐](../img/icon/推荐.png) 好的命名、代码结构是自解释的，注释力求精简准确、表达到位。

## 系统分层规范

### controller层

- ![强制](../img/icon/强制.png) 仅接收参数和拼凑返回数据,不写具体的业务逻辑, 所有业务逻辑必须写到service层里。

### service层

- ![推荐](../img/icon/推荐.png) 数据查询命名规则

a)获取单个对象的方法用`get`作前缀。  
b)获取多个对象的方法用`list`作前缀。  
c)获取统计值的方法用`count`作前缀。  
d)插入的方法用`save`作前缀。  
e)删除的方法用`remove`作前缀。  
f)修改的方法用`modify/update`作前缀。  

### Dao/Mapper层

- ![推荐](../img/icon/推荐.png) 数据查询命名规则

a)获取单个对象的方法用`select`作前缀。  
b)获取多个对象的方法用`select`作前缀，`List`作后续，如：`selectXxxList`。  
c)获取统计值的方法用`count`作前缀。  
d)插入的方法用`insert`作前缀。  
e)删除的方法用`delete`作前缀。  
f)修改的方法用`update`作前缀。  

## 接口管理规范

- ![强制](../img/icon/强制.png) `controller`返回类型必须为`RestResult`。

- ![强制](../img/icon/强制.png) `controller`返回的`RestResult`必须写泛型，若泛型无法确定，请写`<?>`。

- ![推荐](../img/icon/推荐.png) 一般编码中只使用get（接口只做查询，不修改数据库）和post（修改数据库）。

- ![推荐](../img/icon/推荐.png) 一般编码中只使用get（接口只做查询，不修改数据库）和post（修改数据库）。

- ![推荐](../img/icon/推荐.png) 避免出现复杂的输入参数，一般情况下，不允许出现例如json字符串这样的参数，这种参数可读性极差。应该定义对应的bean。

- ![强制](../img/icon/强制.png) `controller`接口的方法参数不应超过5个。如果超过，则应定义相应的Bean进行传输。

## 异常处理规范

### 异常处理

异常类型主要有业务异常和应用系统异常两类：

1. 业务异常，由业务编码人员在ResultCode预先定义，根据业务逻辑进行设定，视作正常的功能返回（HTTP Status Code 200 OK）；
2. 业务系统异常，影响业务正常运转的异常，均视为系统功能性异常。

![Exception](../img/Exception.png)

### 异常处理原则

异常处理，总的原则是所有出现的异常必须要有处理；

1. 所有业务异常，前端必须要有相应的响应；
2. 所有的系统异常，后端必须要有日志记录与统一结果返回。

### 异常处理的编码规范

![Exception1](../img/Exception1.png)

1. 后端编码不要捕获任何异常  
不要在业务代码中编写捕获异常的代码, 即 dao、service、controller 层的所有的异常都全部抛出到上层；
业务代码中只处理正确的功能, 不对异常做任何判断,后端切忌通过异常类型、或者异常出现与否来决定业务逻辑；  
2. 后端统一处理异常  
业务代码中不捕获异常, 系统所有异常都会抛出到 Controller 层，系统基础框架会统一拦截，由日志框架记录异常栈的信息，并统一返回标准的结果信息RestResult；
3. 前端统一处理  
前端组件对后端返回结果集进行统一处理，对不同类型的异常进行相应业务逻辑的处理；

- ![强制](../img/icon/强制.png) 方法若有显式的RuntimeException异常抛出，必须使用`throws`关键字将其标识到方法上

- ![强制](../img/icon/强制.png) 异常的打印均用日志对象的error等级输出

- ![强制](../img/icon/强制.png) IDEA发现的空指针问题必须要处理掉

- ![强制](../img/icon/强制.png) 捕获异常和跑异常必须完全匹配，或者捕获异常是抛异常的父类

- ![强制](../img/icon/强制.png) 若异常被捕获，如果数据需要回滚，一定要注意进行手动回滚

## 日志规约

- ![强制](../img/icon/强制.png) 日志对象在项目引用了lombok的情况下，请用@SLF4J注解声明

- ![强制](../img/icon/强制.png) 所有日志文件至少保存15天

- ![强制](../img/icon/强制.png) 日志信息拼接使用`{}`占位符，以提高可读性和拼接性能

- ![强制](../img/icon/强制.png) 生产环境进制直接使用`System.out`，`System.err`或`e.printStackTrace()`充当日志输出

- ![强制](../img/icon/强制.png) 避免重复打印日志，浪费磁盘空间，务必在日志配置文件中的子logger配置additivity=false

```xml
<logger name="com.chinaweal.dao" level="debug" additivity="false"/>
```

## 工程结构

- ![强制](../img/icon/强制.png) 包名统一使用小写，点分隔符之间有且仅有一个自然语义的英语词。包名统一使用单数形式，但是类名如果有复数含义，类名可以使用复数形式。

- ![正例](../img/icon/正例.png)：用工具类包名为`com.alibaba.ai.util`、类名为`MessageUtils`

- ![推荐](../img/icon/推荐.png) 目录结构com.chinaweal.{项目简称}.{通用配置名/数据源缩写}.{业务板块名}.{controller/service/entity/mapper}
