Java 后端开发规范(审查维度)

前言

后端开发中,统一的编码规范能显著提升代码质量和团队协作效率。
本文按代码评审维度整理一套 Java 后端开发规范,便于 Review 时逐项对照检查。
适用于 Spring Boot + Java 17+ 技术栈,其他框架可按需调整。

审查维度概览

维度 分值 核心要求
命名规范 10 类/方法/变量命名风格统一,见名知意
项目结构与分层 15 目录清晰,Controller / Service / Repository 职责分明
代码风格 10 格式、注释、导入规范一致
业务逻辑与接口设计 25 职责边界清晰,参数校验完备,接口文档齐全
异常与错误处理 25 错误码统一,全局异常处理,信息对用户友好
工程实践 15 日志规范,无 N+1 / 魔法值等反模式
合计 100

命名规范

命名风格对照

命名方式 规则 示例 常见用途
PascalCase(大驼峰) 每个单词首字母大写 UserServiceOrderController 类名、接口名、枚举名
camelCase(小驼峰) 首单词小写,后续首字母大写 userNamegetOrderList 变量、方法名、参数
kebab-case(短横线) 全小写,单词间用 - 连接 user-serviceorder-detail 配置 key、URL 路径
snake_case(下划线) 全小写,单词间用 _ 连接 user_namecreate_time 数据库字段
UPPER_SNAKE_CASE 全大写,单词间用 _ 连接 MAX_COUNTAPI_BASE_URL 常量

类、接口与文件

类型 规则 示例
类 / 接口 PascalCase UserServiceUserRepository
抽象类 Abstract 开头 AbstractBaseService
实现类 Impl 结尾 UserServiceImpl
Controller 类名 + Controller UserController.java
Service 接口 + Service,实现 + ServiceImpl UserServiceUserServiceImpl
数据访问 RepositoryMapper 结尾 UserRepository
DTO / VO Dto / Vo 结尾 UserDtoUserVo
1
2
3
4
5
// 推荐
public class UserServiceImpl implements UserService { ... }

// 不推荐
public class userService { ... }

方法与变量

方法命名(camelCase,动词开头):

场景 前缀 示例
查询 getlistfind getUserByIdfindUsersByStatus
新增 addcreate createUser
修改 update updateUser
删除 deldelete deleteUser
判断 ishascan isAdmin

变量与常量

  • 变量使用 camelCase;常量使用 UPPER_SNAKE_CASE。
  • 布尔变量以 ishascan 开头,如 isDeletedhasPermission
  • 集合变量使用复数形式,如 userListorderIds
1
2
3
4
private static final int MAX_RETRY_COUNT = 3;
private String userName;
private boolean isDeleted;
private List<Order> orderList;

项目结构与分层

目录约定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
src/main/java/com/example/project/
├── controller/ # 控制器层
├── service/ # 服务层
│ └── impl/ # 服务实现
├── mapper/ # 数据访问层
├── model/ # 数据模型
│ ├── entity/ # 数据库实体
│ ├── dto/ # 数据传输对象
│ └── vo/ # 视图对象
├── config/ # 配置类
├── constant/ # 常量
├── enums/ # 枚举
├── exception/ # 自定义异常
├── util/ # 工具类
└── aspect/ # 切面

分层职责

层级 职责
Controller 接收请求、参数校验、调用 Service、返回响应
Service 业务逻辑处理、事务管理
Repository / Mapper 数据访问、SQL 编写
Model Entity、DTO、VO 等数据模型定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestController
@RequestMapping("/api/users")
public class UserController {

private final UserService userService;

@GetMapping("/{id}")
public Result<UserVO> getUser(@PathVariable Long id) {
return Result.success(userService.getUserById(id));
}
}

@Service
public class UserServiceImpl implements UserService {

private final UserRepository userRepository;

@Override
public UserVO getUserById(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
return convertToVO(user);
}
}

代码风格

格式规范

  • 使用 4 空格缩进,不使用 Tab。
  • 每行不超过 120 个字符,超长时换行对齐。
  • 左大括号 { 不换行,右大括号 } 独占一行。
  • 方法之间、逻辑块之间空一行。

注释规范

  • 类和公共方法必须添加 Javadoc,说明用途、参数、返回值、异常。
  • 复杂逻辑添加行内注释,说明意图而非复述代码。
  • TODO 格式:// TODO(作者): 说明

导入规范

  • 使用显式导入,禁止 import xxx.*
  • 导入顺序:java.*javax.* → 第三方库 → 项目内包。
  • 静态导入仅用于常量和工具方法。

业务逻辑与接口设计

职责边界

  • 单个接口不应把分支逻辑交给前端判断后分别处理,后端应封装完整业务语义。
  • 接口中不写跨域逻辑,跨域统一在 Nginx 等网关层处理。

参数校验

使用 @Valid / @Validated 配合 JSR 303 注解,在 Controller 入口完成校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UserDTO {

@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度2-20个字符")
private String userName;

@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
}

@PostMapping
public Result<Void> createUser(@Valid @RequestBody UserDTO dto) {
userService.createUser(dto);
return Result.success();
}

接口文档

  • 所有对外接口必须有文档,不允许存在未文档化的接口。
  • 接口按应用模块归类,而非按数据库表归类;多端差异明显时应拆分接口。
  • 接口要有业务意义,避免机械式无意义的增删改查。
  • 描述需说明业务场景与注意事项;参数标注必填与类型并提供示例值。
  • 响应字段说明含义与类型,枚举值列出可选项。
  • 接口变更时同步更新文档,返回值类型、必填项、字段完整性须与代码一致。

异常与错误处理

异常体系

  • 业务异常继承 RuntimeException,信息通过枚举或常量管理,禁止硬编码。
  • 参数校验异常使用 IllegalArgumentException
  • 不要捕获异常后静默吞掉,至少记录日志。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class BusinessException extends RuntimeException {

private final String code;

public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}

public BusinessException(ErrorCode errorCode, String message) {
super(message);
this.code = errorCode.getCode();
}

public String getCode() {
return code;
}
}

错误码设计

格式:{模块}_{编号},如 USER_001ORDER_002

类型 前缀 范围 说明
成功 SUCCESS 0 请求成功
系统错误 SYSTEM 1xxx 服务器内部错误、第三方异常
参数错误 PARAM 2xxx 参数校验失败、格式错误
认证授权 AUTH 3xxx 登录过期、权限不足
业务错误 按模块定义 各模块自定义 具体业务逻辑异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public enum ErrorCode {

SUCCESS("0", "成功"),

SYSTEM_ERROR("SYSTEM_1001", "系统繁忙,请稍后重试"),
PARAM_INVALID("PARAM_2002", "参数无效"),
AUTH_NO_PERMISSION("AUTH_3003", "没有操作权限"),

USER_NOT_FOUND("USER_001", "用户不存在"),
ORDER_NOT_FOUND("ORDER_001", "订单不存在");

private final String code;
private final String message;

ErrorCode(String code, String message) {
this.code = code;
this.message = message;
}

public String getCode() { return code; }
public String getMessage() { return message; }
}

统一响应与全局处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Data
public class Result<T> {

private String code;
private String message;
private T data;

public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(ErrorCode.SUCCESS.getCode());
result.setMessage(ErrorCode.SUCCESS.getMessage());
result.setData(data);
return result;
}

public static <T> Result<T> fail(ErrorCode errorCode) {
Result<T> result = new Result<>();
result.setCode(errorCode.getCode());
result.setMessage(errorCode.getMessage());
return result;
}
}

@RestControllerAdvice
public class GlobalExceptionHandler {

private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
log.warn("业务异常: code={}, message={}", e.getCode(), e.getMessage());
Result<Void> result = new Result<>();
result.setCode(e.getCode());
result.setMessage(e.getMessage());
return result;
}

@ExceptionHandler(IllegalArgumentException.class)
public Result<Void> handleIllegalArgumentException(IllegalArgumentException e) {
log.warn("参数异常: {}", e.getMessage());
return Result.fail(ErrorCode.PARAM_INVALID);
}

@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常", e);
return Result.fail(ErrorCode.SYSTEM_ERROR);
}
}

评审要点:错误码统一枚举管理;各模块前缀不同便于定位;错误信息对用户友好,不暴露技术细节;新增错误码同步更新枚举。


工程实践

日志规范

  • 使用 SLF4J + Logback,禁止 System.out.println()
  • 生产环境默认 INFO,开发环境可调至 DEBUG
  • 日志输出至 logs/ 目录,格式包含时间、级别、类名、方法名、行号。
1
2
3
4
5
6
7
8
9
10
11
12
private static final Logger log = LoggerFactory.getLogger(UserService.class);

public void createUser(UserDTO dto) {
log.info("创建用户开始, userName={}", dto.getUserName());
try {
User user = userRepository.save(convertToEntity(dto));
log.info("创建用户成功, userId={}", user.getId());
} catch (Exception e) {
log.error("创建用户失败, userName={}", dto.getUserName(), e);
throw e;
}
}

工具类设计

  • 类使用 final 修饰,构造函数私有化,方法使用 static
1
2
3
4
5
6
7
8
9
10
public final class StringUtils {

private StringUtils() {
throw new UnsupportedOperationException("工具类不允许实例化");
}

public static boolean isBlank(String str) {
return str == null || str.trim().isEmpty();
}
}

禁止项与反模式

禁止项 推荐做法
魔法值 提取为常量,如 MAX_PAGE_SIZE = 100
循环内单条查询(N+1) 批量查询后 Map 关联
Service 层操作 HttpServletRequest 在 Controller 层提取参数后传入
过深嵌套(> 3 层) 拆分为独立方法
多表关联写法不当 评估 JOIN / 分步查询,避免性能隐患
1
2
3
4
5
6
7
8
// 推荐:批量查询
List<Long> userIds = orders.stream().map(Order::getUserId).collect(Collectors.toList());
Map<Long, User> userMap = userRepository.findByIds(userIds);

// 不推荐:循环查询
for (Order order : orders) {
User user = userRepository.findById(order.getUserId());
}