跳转至

编码规范

基于《阿里巴巴Java开发手册》,结合项目实际情况制定

📋 目录

📝 命名规范

基本原则

  1. 见名知义:命名要能准确表达其含义
  2. 简洁明了:在表达清楚的前提下尽量简短
  3. 统一风格:项目内保持一致的命名风格

包名规范

cn.zhangziming.auth.{模块名}.{层级}

示例:
cn.zhangziming.auth.common.exception
cn.zhangziming.auth.security.jwt
cn.zhangziming.auth.server.controller

规则: - ✅ 全部小写 - ✅ 单词间用点号分隔 - ✅ 不使用下划线或大写字母 - ❌ cn.zhangziming.Boot.Common (错误:使用了大写) - ❌ cn.zhangziming.auth_common (错误:使用了下划线)

类名规范

普通类

// ✅ 正确示例
public class UserController { }
public class OrderService { }
public class ProductMapper { }

// ❌ 错误示例
public class usercontroller { }  // 应使用大驼峰
public class User_Service { }    // 不要使用下划线

特殊类命名

类型 规范 示例
Controller XxxController UserController
Service接口 IXxxServiceXxxService IUserService
Service实现 XxxServiceImpl UserServiceImpl
Mapper/DAO XxxMapper UserMapper
Entity/DO Xxx User, Role
DTO XxxDTO UserDTO, LoginDTO
VO XxxVO UserVO
Request XxxRequest LoginRequest
Response XxxResponse TokenResponse
Exception XxxException BusinessException
Utils XxxUtilXxxUtils StringUtil
Config XxxConfig RedisConfig
Constant XxxConstant CacheConstant

方法名规范

// ✅ 正确示例
public void saveUser() { }           // 保存
public void deleteUser() { }         // 删除
public void updateUser() { }         // 更新
public User getUserById(Long id) { } // 查询单个
public List<User> listUsers() { }    // 查询列表
public boolean isAdmin() { }         // 判断
public boolean hasPermission() { }   // 判断是否拥有
public void validateToken() { }      // 验证
public String convertToJson() { }    // 转换

// ❌ 错误示例
public void SaveUser() { }           // 首字母不应大写
public void get_user() { }           // 不应使用下划线
public void user() { }               // 不清楚具体操作

变量名规范

// ✅ 正确示例
private String username;
private Long userId;
private List<User> userList;
private Map<String, Object> dataMap;
private boolean isDeleted;
private boolean hasPermission;

// ❌ 错误示例
private String UserName;      // 应使用小驼峰
private Long user_id;         // 不应使用下划线
private List<User> list;      // 命名太泛化
private boolean deleted;      // boolean应以is/has等开头

常量名规范

// ✅ 正确示例
public static final String DEFAULT_CHARSET = "UTF-8";
public static final int MAX_PAGE_SIZE = 100;
public static final long TOKEN_EXPIRE_TIME = 7200L;

// ❌ 错误示例
public static final String defaultCharset = "UTF-8";  // 应全大写
public static final int maxPageSize = 100;           // 应使用下划线分隔

枚举类命名

// ✅ 正确示例
public enum UserStatus {
    NORMAL(1, "正常"),
    DISABLED(0, "禁用"),
    LOCKED(2, "锁定");

    private final Integer code;
    private final String desc;

    // getter...
}

public enum LoginType {
    PASSWORD,      // 密码登录
    SMS,          // 短信登录
    WECHAT        // 微信登录
}

🎨 代码格式

缩进

  • 使用 4个空格 缩进,不使用Tab
  • IDE配置:Tab转换为4个空格
// ✅ 正确
public class UserService {
    public void saveUser() {
        if (user != null) {
            userMapper.insert(user);
        }
    }
}

// ❌ 错误(缩进不一致)
public class UserService {
  public void saveUser() {
      if (user != null) {
    userMapper.insert(user);
      }
  }
}

单行长度

  • 单行字符数不超过 120个字符
  • 超过需要换行,遵循以下原则:
// ✅ 正确:在逗号后换行
String message = String.format("用户[%s]执行了操作[%s],"
        + "结果为[%s],耗时[%d]ms",
        username, operation, result, time);

// ✅ 正确:方法参数过多时换行
public void updateUser(Long userId, 
                      String username, 
                      String email,
                      String phone) {
    // ...
}

// ✅ 正确:链式调用换行
List<String> names = userList.stream()
        .filter(user -> user.getStatus() == 1)
        .map(User::getUsername)
        .collect(Collectors.toList());

空格规范

// ✅ 正确:关键字与括号之间要有空格
if (condition) { }
for (int i = 0; i < 10; i++) { }
while (condition) { }

// ✅ 正确:运算符左右要有空格
int result = a + b;
boolean flag = (a > b) && (c < d);

// ✅ 正确:逗号、分号后要有空格
method(arg1, arg2, arg3);

// ❌ 错误:缺少空格
if(condition){ }
int result=a+b;
method(arg1,arg2,arg3);

空行规范

public class UserService {

    private UserMapper userMapper;

    /**
     * 保存用户
     */
    public void saveUser(User user) {
        validateUser(user);

        userMapper.insert(user);

        sendNotification(user);
    }

    /**
     * 验证用户
     */
    private void validateUser(User user) {
        // ...
    }
}

规则: - 方法之间空一行 - 逻辑块之间空一行 - 成员变量和方法之间空一行

大括号规范

// ✅ 正确:K&R风格(推荐)
if (condition) {
    doSomething();
} else {
    doOtherThing();
}

public void method() {
    // ...
}

// ❌ 错误:不推荐的风格
if (condition) 
{
    doSomething();
}

// ❌ 错误:单行也要加大括号
if (condition) doSomething();  // 不推荐

💬 注释规范

类注释

/**
 * 用户服务类
 * 
 * <p>提供用户的增删改查等基础功能
 * <p>包含用户密码加密、权限验证等逻辑
 * 
 * @author zhangziming
 * @since 1.0.0
 */
public class UserService {
    // ...
}

方法注释

/**
 * 保存用户信息
 * 
 * <p>保存前会进行以下验证:
 * <ul>
 *   <li>用户名不能为空</li>
 *   <li>用户名不能重复</li>
 *   <li>密码会自动加密</li>
 * </ul>
 * 
 * @param user 用户对象,不能为null
 * @return 保存后的用户ID
 * @throws BusinessException 当用户名重复时抛出
 */
public Long saveUser(User user) {
    // ...
}

字段注释

public class User {

    /**
     * 用户ID
     */
    private Long id;

    /**
     * 用户名(唯一)
     */
    private String username;

    /**
     * 密码(BCrypt加密存储)
     */
    private String password;

    /**
     * 用户状态
     * 0-禁用 1-正常 2-锁定
     */
    private Integer status;
}

代码注释

public void processOrder(Order order) {
    // 1. 验证订单
    validateOrder(order);

    // 2. 计算价格
    BigDecimal totalPrice = calculatePrice(order);

    // 3. 扣减库存
    reduceStock(order);

    // 4. 创建订单
    orderMapper.insert(order);

    // TODO: 发送订单通知
    // FIXME: 需要增加事务处理
}

特殊标记

标记 含义 使用场景
TODO 待办事项 需要实现但暂时未实现的功能
FIXME 需要修复 已知的Bug或问题
XXX 需要注意 存在风险或需要特别注意的地方
@deprecated 已废弃 不推荐使用的方法或类
// TODO: [zhangziming] 2024-10-28 需要增加缓存逻辑
public User getUser(Long id) {
    return userMapper.selectById(id);
}

// FIXME: [zhangziming] 2024-10-28 并发情况下可能有问题
public void updateUserCount() {
    int count = getCount();
    setCount(count + 1);
}

/**
 * @deprecated 使用 {@link #saveUser(UserDTO)} 代替
 */
@Deprecated
public void save(User user) {
    // ...
}

📐 编程规约

常量定义

// ✅ 正确:定义在常量类中
public class CacheConstant {
    /** 用户缓存前缀 */
    public static final String USER_CACHE_PREFIX = "user:";

    /** 用户缓存过期时间(秒) */
    public static final long USER_CACHE_EXPIRE = 1800L;
}

// ❌ 错误:魔法值
userCache.set("user:" + userId, user, 1800);

// ✅ 正确:使用常量
userCache.set(USER_CACHE_PREFIX + userId, user, USER_CACHE_EXPIRE);

集合处理

// ✅ 正确:使用isEmpty判断
if (CollectionUtils.isEmpty(list)) {
    // ...
}

// ❌ 错误:使用size() == 0判断(性能差)
if (list.size() == 0) {
    // ...
}

// ✅ 正确:使用ArrayList初始化时指定大小
List<User> userList = new ArrayList<>(100);

// ✅ 正确:使用Arrays.asList初始化
List<String> list = Arrays.asList("a", "b", "c");

// ❌ 错误:Arrays.asList的列表不能增删
list.add("d");  // 运行时异常

// ✅ 正确:需要增删时用new ArrayList包装
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
list.add("d");  // OK

字符串处理

// ✅ 正确:使用equals判断相等
if ("admin".equals(username)) {
    // ...
}

// ❌ 错误:可能空指针
if (username.equals("admin")) {
    // ...
}

// ✅ 正确:字符串拼接使用StringBuilder
StringBuilder sb = new StringBuilder();
for (String item : list) {
    sb.append(item).append(",");
}

// ❌ 错误:循环中使用+拼接(性能差)
String result = "";
for (String item : list) {
    result += item + ",";
}

// ✅ 正确:使用StringUtils工具类
if (StringUtils.isNotBlank(username)) {
    // ...
}

条件判断

// ✅ 正确:避免复杂的条件判断
boolean isValidUser = user != null 
    && StringUtils.isNotBlank(user.getUsername())
    && user.getStatus() == 1;

if (isValidUser) {
    // ...
}

// ✅ 正确:提前返回,减少嵌套
public void process(User user) {
    if (user == null) {
        return;
    }

    if (StringUtils.isBlank(user.getUsername())) {
        return;
    }

    // 正常处理逻辑
    doProcess(user);
}

// ❌ 错误:多层嵌套
public void process(User user) {
    if (user != null) {
        if (StringUtils.isNotBlank(user.getUsername())) {
            if (user.getStatus() == 1) {
                // 正常处理逻辑
            }
        }
    }
}

空值处理

// ✅ 正确:返回空集合而不是null
public List<User> listUsers() {
    List<User> users = userMapper.selectList();
    return users != null ? users : Collections.emptyList();
}

// ❌ 错误:返回null
public List<User> listUsers() {
    return userMapper.selectList();  // 可能为null
}

// ✅ 正确:使用Optional
public Optional<User> getUser(Long id) {
    return Optional.ofNullable(userMapper.selectById(id));
}

// 使用
userService.getUser(1L).ifPresent(user -> {
    // 处理用户
});

对象比较

// ✅ 正确:包装类使用equals
Long userId1 = 1L;
Long userId2 = 1L;
if (userId1.equals(userId2)) {
    // ...
}

// ❌ 错误:包装类使用 ==
if (userId1 == userId2) {  // 可能不相等
    // ...
}

// ✅ 正确:枚举使用 == 或 equals都可以
if (UserStatus.NORMAL == user.getStatus()) {
    // ...
}

⚠️ 异常处理

异常分类

/**
 * 业务异常
 * 可预期的异常,需要给用户友好提示
 */
public class BusinessException extends RuntimeException {
    private Integer code;

    public BusinessException(String message) {
        super(message);
        this.code = 400;
    }

    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }
}

/**
 * 系统异常
 * 不可预期的异常,记录日志并给用户通用提示
 */
public class SystemException extends RuntimeException {
    public SystemException(String message) {
        super(message);
    }

    public SystemException(String message, Throwable cause) {
        super(message, cause);
    }
}

异常使用规范

// ✅ 正确:抛出具体的异常
public User getUser(Long userId) {
    if (userId == null) {
        throw new BusinessException("用户ID不能为空");
    }

    User user = userMapper.selectById(userId);
    if (user == null) {
        throw new BusinessException("用户不存在");
    }

    return user;
}

// ❌ 错误:吞掉异常
try {
    doSomething();
} catch (Exception e) {
    // 什么都不做
}

// ✅ 正确:记录日志或重新抛出
try {
    doSomething();
} catch (Exception e) {
    log.error("操作失败", e);
    throw new SystemException("系统异常,请稍后重试", e);
}

// ❌ 错误:捕获Exception
try {
    doSomething();
} catch (Exception e) {
    // ...
}

// ✅ 正确:捕获具体异常
try {
    doSomething();
} catch (IOException e) {
    // 处理IO异常
} catch (SQLException e) {
    // 处理SQL异常
}

全局异常处理

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public Result<?> handleBusinessException(BusinessException e) {
        log.warn("业务异常: {}", e.getMessage());
        return Result.error(e.getCode(), e.getMessage());
    }

    /**
     * 参数校验异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> handleValidationException(MethodArgumentNotValidException e) {
        String message = e.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(error -> error.getField() + ": " + error.getDefaultMessage())
                .collect(Collectors.joining(", "));
        return Result.error(400, message);
    }

    /**
     * 系统异常
     */
    @ExceptionHandler(Exception.class)
    public Result<?> handleException(Exception e) {
        log.error("系统异常", e);
        return Result.error(500, "系统异常,请稍后重试");
    }
}

📋 日志规范

日志级别

级别 使用场景 示例
ERROR 错误信息 系统异常、业务异常
WARN 警告信息 参数不合法、降级处理
INFO 重要信息 启动信息、关键业务操作
DEBUG 调试信息 开发调试
TRACE 跟踪信息 详细的调试信息

日志使用规范

@Slf4j
public class UserService {

    public void saveUser(User user) {
        // ✅ 正确:使用占位符
        log.info("保存用户,username={}, email={}", 
                user.getUsername(), user.getEmail());

        // ❌ 错误:使用字符串拼接(即使不输出也会执行拼接)
        log.info("保存用户,username=" + user.getUsername());

        // ✅ 正确:异常日志包含堆栈
        try {
            userMapper.insert(user);
        } catch (Exception e) {
            log.error("保存用户失败,userId={}", user.getId(), e);
        }

        // ✅ 正确:敏感信息脱敏
        log.info("用户登录,username={}, password={}", 
                user.getUsername(), "******");
    }
}

日志格式

# logback-spring.xml
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>

输出示例:

2024-10-28 10:00:00.123 [http-nio-8080-exec-1] INFO  c.z.b.s.UserService - 保存用户,username=admin, email=admin@example.com

🗄️ 数据库规范

表命名规范

  • 表名全部小写,单词间用下划线分隔
  • 表名使用复数形式或业务含义明确的名词
  • 不要使用MySQL保留字
-- ✅ 正确
CREATE TABLE sys_user (...);
CREATE TABLE sys_role (...);
CREATE TABLE sys_user_role (...);

-- ❌ 错误
CREATE TABLE User (...);        -- 不要使用大写
CREATE TABLE user (...);        -- MySQL保留字
CREATE TABLE sys-user (...);   -- 不要使用连字符

字段命名规范

CREATE TABLE sys_user (
  -- ✅ 正确:主键使用id
  id BIGINT PRIMARY KEY AUTO_INCREMENT,

  -- ✅ 正确:字段名全小写,下划线分隔
  username VARCHAR(64) NOT NULL,
  password VARCHAR(128) NOT NULL,
  create_time DATETIME NOT NULL,
  update_time DATETIME NOT NULL,

  -- ✅ 正确:布尔字段使用is_前缀
  is_deleted TINYINT DEFAULT 0,
  is_enabled TINYINT DEFAULT 1
);

-- ❌ 错误示例
CREATE TABLE sys_user (
  userId BIGINT,           -- 不要使用驼峰
  user_name VARCHAR(64),   -- username更简洁
  createTime DATETIME      -- 不要使用驼峰
);

索引规范

-- ✅ 正确:索引命名规范
-- 唯一索引:uk_字段名
CREATE UNIQUE INDEX uk_username ON sys_user(username);

-- 普通索引:idx_字段名
CREATE INDEX idx_create_time ON sys_user(create_time);

-- 联合索引:idx_字段1_字段2
CREATE INDEX idx_username_status ON sys_user(username, status);

-- ❌ 错误:索引名不规范
CREATE INDEX index_1 ON sys_user(username);
CREATE INDEX user_index ON sys_user(username);

SQL编写规范

// ✅ 正确:使用预编译SQL
@Select("SELECT * FROM sys_user WHERE username = #{username}")
User selectByUsername(@Param("username") String username);

// ❌ 错误:SQL拼接(有注入风险)
String sql = "SELECT * FROM sys_user WHERE username = '" + username + "'";

// ✅ 正确:分页查询
@Select("SELECT * FROM sys_user LIMIT #{offset}, #{limit}")
List<User> selectByPage(@Param("offset") int offset, 
                       @Param("limit") int limit);

// ✅ 正确:批量插入
@Insert("<script>" +
        "INSERT INTO sys_user (username, password) VALUES " +
        "<foreach collection='list' item='item' separator=','>" +
        "(#{item.username}, #{item.password})" +
        "</foreach>" +
        "</script>")
int batchInsert(@Param("list") List<User> users);

🔒 安全规范

输入验证

// ✅ 正确:使用Validation注解
public class UserDTO {

    @NotBlank(message = "用户名不能为空")
    @Length(min = 3, max = 20, message = "用户名长度为3-20个字符")
    @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
    private String username;

    @NotBlank(message = "密码不能为空")
    @Length(min = 6, max = 20, message = "密码长度为6-20个字符")
    private String password;

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

@PostMapping("/register")
public Result<Void> register(@Valid @RequestBody UserDTO userDTO) {
    // @Valid会自动验证
    userService.register(userDTO);
    return Result.success();
}

密码处理

// ✅ 正确:使用BCrypt加密
@Service
public class UserService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    public void saveUser(User user) {
        // 加密密码
        String encryptedPassword = passwordEncoder.encode(user.getPassword());
        user.setPassword(encryptedPassword);

        userMapper.insert(user);
    }

    public boolean validatePassword(String rawPassword, String encodedPassword) {
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }
}

// ❌ 错误:明文存储密码
user.setPassword(rawPassword);
userMapper.insert(user);

// ❌ 错误:使用MD5加密(不安全)
String md5Password = DigestUtils.md5Hex(password);

SQL注入防护

// ✅ 正确:使用参数化查询
@Select("SELECT * FROM sys_user WHERE username = #{username}")
User selectByUsername(String username);

// ✅ 正确:MyBatis Plus
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
userMapper.selectOne(wrapper);

// ❌ 错误:字符串拼接(有注入风险)
@Select("SELECT * FROM sys_user WHERE username = '${username}'")
User selectByUsername(String username);

XSS防护

// ✅ 正确:对输出进行HTML转义
@GetMapping("/user/{id}")
public String getUserInfo(@PathVariable Long id, Model model) {
    User user = userService.getUser(id);

    // Thymeleaf会自动转义
    model.addAttribute("username", user.getUsername());

    return "user-info";
}

// 在前端使用th:text(自动转义)
// <span th:text="${username}"></span>

// ❌ 错误:不转义输出(有XSS风险)
// <span th:utext="${username}"></span>

🧪 单元测试

测试类命名

// ✅ 正确:测试类以Test结尾
public class UserServiceTest {
    // ...
}

// ✅ 正确:测试方法命名清晰
@Test
public void testSaveUser_Success() {
    // ...
}

@Test
public void testSaveUser_WhenUsernameExists_ShouldThrowException() {
    // ...
}

测试规范

@SpringBootTest
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @MockBean
    private UserMapper userMapper;

    /**
     * 测试保存用户 - 成功场景
     */
    @Test
    public void testSaveUser_Success() {
        // Given (准备测试数据)
        User user = new User();
        user.setUsername("test");
        user.setPassword("123456");

        when(userMapper.insert(any())).thenReturn(1);

        // When (执行测试)
        Long userId = userService.saveUser(user);

        // Then (验证结果)
        assertNotNull(userId);
        verify(userMapper, times(1)).insert(any());
    }

    /**
     * 测试保存用户 - 用户名已存在
     */
    @Test
    public void testSaveUser_WhenUsernameExists_ShouldThrowException() {
        // Given
        User user = new User();
        user.setUsername("admin");

        when(userMapper.selectByUsername("admin"))
            .thenReturn(new User());

        // When & Then
        assertThrows(BusinessException.class, () -> {
            userService.saveUser(user);
        });
    }
}

测试覆盖率

  • 单元测试覆盖率目标:80%以上
  • 核心业务逻辑覆盖率:100%
# 使用JaCoCo生成覆盖率报告
mvn clean test jacoco:report

🛠️ 工具配置

IDEA配置

1. 代码格式化

Settings → Editor → Code Style → Java

  • Tab size: 4
  • Indent: 4
  • Continuation indent: 8
  • 勾选 "Use tab character" 取消

2. 自动导入优化

Settings → Editor → General → Auto Import

  • 勾选 "Add unambiguous imports on the fly"
  • 勾选 "Optimize imports on the fly"

3. 保存时格式化

Settings → Tools → Actions on Save

  • 勾选 "Reformat code"
  • 勾选 "Optimize imports"

Maven插件

<build>
    <plugins>
        <!-- Checkstyle代码规范检查 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-checkstyle-plugin</artifactId>
            <version>3.3.0</version>
            <configuration>
                <configLocation>checkstyle.xml</configLocation>
            </configuration>
        </plugin>

        <!-- PMD代码质量检查 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-pmd-plugin</artifactId>
            <version>3.21.0</version>
        </plugin>

        <!-- JaCoCo测试覆盖率 -->
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>0.8.10</version>
            <executions>
                <execution>
                    <goals>
                        <goal>prepare-agent</goal>
                    </goals>
                </execution>
                <execution>
                    <id>report</id>
                    <phase>test</phase>
                    <goals>
                        <goal>report</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Git Hooks

创建 .git/hooks/pre-commit:

#!/bin/bash

# 格式化检查
mvn checkstyle:check

if [ $? -ne 0 ]; then
    echo "代码格式检查失败,请修复后再提交"
    exit 1
fi

# 单元测试
mvn test

if [ $? -ne 0 ]; then
    echo "单元测试失败,请修复后再提交"
    exit 1
fi

📚 参考资料

  1. 阿里巴巴Java开发手册
  2. Google Java Style Guide
  3. Effective Java
  4. Clean Code

✅ 检查清单

提交代码前请确认:

  • 代码符合命名规范
  • 代码格式正确(缩进、空格、换行)
  • 添加了必要的注释
  • 没有硬编码(使用常量)
  • 异常处理正确
  • 日志使用规范
  • 通过单元测试
  • 测试覆盖率达标
  • 没有编译警告
  • 代码已格式化

遵守规范,编写优雅的代码! 💎

本文档持续更新,最后更新时间: 2024-10-28