Skip to content
第 80 / 250 章后端⏱ 10 分钟阅读

第 80 章:阿里 Java 开发规约

学习目标

  • 掌握阿里巴巴 Java 开发手册的核心规约
  • 集成 P3C / SpotBugs / SonarQube 做静态检查
  • 养成良好的编码习惯

一、为什么需要编码规约?

没有规约的代价

  • 老代码越改越乱,新人不敢动
  • 命名混乱,三个月后没人看得懂
  • 隐藏的 bug 模式在团队里反复出现
  • 代码 Review 时争论风格而非业务

规约的价值:把"优秀实践"沉淀成"团队默认值",让 80% 的代码无需讨论。

二、《阿里巴巴 Java 开发手册》核心规约

1. 命名规约

java
// ✅ 类名:大驼峰
class UserService { }
class OrderDetailVO { }
class TaskFlowConstants { }

// ❌ 错误
class userservice { }
class USER_SERVICE { }

// ✅ 方法名、参数名、变量名:小驼峰
public void getUserById(Long userId) { }
int maxCount = 100;

// ❌ 错误
public void GetUserById() { }      // 错用大驼峰
public void get_user_by_id() { }   // 错用下划线

// ✅ 常量:全大写下划线
public static final int MAX_RETRY_COUNT = 3;
public static final String DEFAULT_USER_NAME = "anonymous";

// ✅ 抽象类:Abstract 开头
abstract class AbstractValidator { }

// ✅ 异常类:Exception 结尾
class BusinessException extends RuntimeException { }

// ✅ 测试类:Test 结尾
class UserServiceTest { }

// ✅ Boolean 变量:is / has / can 开头
boolean isValid;
boolean hasPermission;
boolean canExecute;

// ✅ 包名:全小写,点分隔
package com.taskflow.modules.user.service;

// ❌ 反例:包名大写或下划线
package com.TaskFlow.user_service;

2. 常量定义

java
// ❌ 反例:魔法值散落各处
if (status == 1) { }
if ("Y".equals(flag)) { }

// ✅ 正例:定义常量
public class OrderStatus {
    public static final int UNPAID = 0;
    public static final int PAID = 1;
    public static final int SHIPPED = 2;
    public static final int COMPLETED = 3;
    public static final int CANCELLED = 4;
}

if (status == OrderStatus.PAID) { }

// ✅ 更规范:用枚举(限定取值范围)
public enum OrderStatus {
    UNPAID(0, "待支付"),
    PAID(1, "已支付"),
    SHIPPED(2, "已发货"),
    COMPLETED(3, "已完成"),
    CANCELLED(4, "已取消");

    private final int code;
    private final String desc;

    OrderStatus(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public int getCode() { return code; }
}

3. OOP 规约

java
// ✅ 避免通过实例引用静态变量(编译器会警告)
UserService.count++;              // ❌
UserService.count++;              // ✅ 用类名访问

// ✅ equals / hashCode 一并重写
@Data
@EqualsAndHashCode(callSuper = true)
class User extends BaseEntity { ... }

// ✅ 所有包装类对象之间值的比较用 equals
Integer a = 128;
Integer b = 128;
a == b;             // ❌ false(-128~127 之外用 == 是陷阱)
a.equals(b);        // ✅

// ✅ BigDecimal 等值比较用 compareTo
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("1.00");
a.equals(b);                    // ❌ false(精度不同)
a.compareTo(b) == 0;            // ✅ true

// ✅ 禁止在子类中随意覆盖父类的非抽象方法
// 反例:覆盖 @PostConstruct / @Transactional / equals 等

// ✅ 构造方法禁止加入业务逻辑(只做初始化)

4. 集合处理

java
// ✅ 在 subList 场景中,高度注意对父集合的修改
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
List<String> sub = list.subList(0, 2);
list.add("d");                    // ❌ subList 视图会抛 ConcurrentModificationException

// ✅ 使用 entrySet 遍历 Map
for (Map.Entry<String, User> entry : map.entrySet()) {
    String key = entry.getKey();
    User value = entry.getValue();
}
// ❌ 反例:先 keySet 再 get(多一次查找)
for (String key : map.keySet()) {
    User value = map.get(key);
}

// ✅ 利用 Set 元素唯一性去重
List<Long> uniqueDeptIds = new ArrayList<>(new HashSet<>(deptIds));

// ✅ 使用 Collection.isEmpty() 检测,不要用 size()==0
if (list.isEmpty()) { ... }       // ✅
if (list.size() == 0) { ... }     // ❌

5. 并发处理

java
// ✅ 线程资源必须通过线程池提供
ExecutorService executor = Executors.newFixedThreadPool(8);   // ❌
ExecutorService executor = new ThreadPoolExecutor(...);        // ✅

// ✅ SimpleDateFormat 非线程安全
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT =
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

// ✅ 高并发下避免用 "等于" 判断作为中断或退出的条件
while (flag) { ... }              // ❌
while (!Thread.currentThread().isInterrupted()) { ... }  // ✅

// ✅ 加锁时考虑锁的粒度
synchronized (this) { ... }       // 粒度太大
// ✅ 用 ConcurrentHashMap 代替 Hashtable

// ✅ 读写分离:CopyOnWriteArrayList 用于读多写少

6. 控制语句

java
// ✅ if/else 嵌套不超过 3 层
// 重构:卫语句 / 策略模式 / 状态机

// ❌ 反例:金字塔
public void process(Order order) {
    if (order != null) {
        if (order.getStatus() == PAID) {
            if (order.getAmount() > 0) {
                if (order.getUser() != null) {
                    // 业务逻辑
                }
            }
        }
    }
}

// ✅ 卫语句
public void process(Order order) {
    if (order == null) return;
    if (order.getStatus() != PAID) return;
    if (order.getAmount() <= 0) return;
    if (order.getUser() == null) return;

    // 业务逻辑
}

// ✅ switch 必须有 default
switch (status) {
    case 1: ...
    case 2: ...
    default: log.warn("未知状态: {}", status);
}

// ✅ 三目运算符注意 NPE
String name = user.getName() == null ? "default" : user.getName();  // 写两遍不优雅
String name = Optional.ofNullable(user).map(User::getName).orElse("default");  // ✅

7. 注释规约

java
// ✅ 类、方法、字段必须有注释
/**
 * 用户服务
 *
 * @author 张三
 * @since 1.0.0
 */
@Service
public class UserService { ... }

// ✅ 复杂的 if/else 必须加注释说明业务规则
if (order.getType() == 1) {
    // VIP 订单享受 9 折优惠
    ...
}

// ❌ 注释与代码不同步(最常见的坏味道)
// 注释:检查用户是否存在
// 实际:检查用户是否被禁用
if (user.getStatus() == DISABLED) { ... }

8. 异常处理

java
// ✅ 异常不要用来做流程控制(catch 后应当处理或抛)

// ✅ catch 必须保留原始异常
catch (IOException e) {
    log.error("读取文件失败", e);             // ✅ 保留
    throw new BusinessException("读取失败");  // ❌ 丢弃
}

// ✅ finally 块必须关闭资源(用 try-with-resources)
try (FileInputStream fis = new FileInputStream(file)) {
    return fis.read();
}

// ✅ 防止 NPE 是程序员的基本修养
// 1. 所有数据库查询返回可能为 null → 先判空
// 2. RPC 调用结果可能为 null
// 3. Session / Cookie / Request 拿到的对象

// ✅ 在代码中使用 "抛异常" 还是 "返回错误码"?
// 跨远程调用:必须抛异常(错误码无法跨越网络)
// 内部调用:建议抛异常(统一处理)

三、静态检查工具

P3C 插件(阿里官方)

xml
<!-- p3c-pmd -->
<dependency>
    <groupId>com.alibaba.p3c</groupId>
    <artifactId>p3c-pmd</artifactId>
    <version>1.10.0</version>
    <scope>test</scope>
</dependency>
bash
mvn p3c:check

集成 Checkstyle

xml
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-checkstyle-plugin</artifactId>
    <version>3.3.1</version>
    <configuration>
        <configLocation>checkstyle.xml</configLocation>
        <failOnViolation>true</failOnViolation>
        <violationSeverity>warning</violationSeverity>
        <includeTestSourceDirectory>true</includeTestSourceDirectory>
    </configuration>
    <executions>
        <execution>
            <id>validate</id>
            <phase>validate</phase>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>
xml
<!-- checkstyle.xml:自定义规则 -->
<module name="Checker">
    <module name="TreeWalker">
        <module name="ConstantName"/>          <!-- 常量大写 -->
        <module name="LocalVariableName"/>     <!-- 局部变量小驼峰 -->
        <module name="MethodName"/>            <!-- 方法小驼峰 -->
        <module name="MagicNumber"/>           <!-- 禁用魔法值 -->
    </module>
</module>

SonarQube(企业级)

yaml
# sonar-project.properties
sonar.projectKey=taskflow
sonar.projectName=TaskFlow
sonar.projectVersion=1.0.0
sonar.sources=src/main/java
sonar.tests=src/test/java
sonar.java.binaries=target/classes
sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
sonar.exclusions=**/dto/**,**/vo/**
bash
mvn sonar:sonar \
  -Dsonar.host.url=http://localhost:9000 \
  -Dsonar.login=your-token

四、IDEA 插件

text
1. Alibaba Java Coding Guidelines     // 阿里规约
2. SonarLint                          // Sonar 实时检查
3. Checkstyle-IDEA                    // Checkstyle 集成
4. SpotBugs                           // Bug 模式检测
5. MyBatis Log                        // SQL 日志
6. Lombok                             // 简化代码
7. GenerateAllSetter                  // 一键生成 setter(测试用)

安装:Settings → Plugins → Marketplace 搜索。

五、规约检查流程

bash
# Git Hook:提交前自动检查
# .git/hooks/pre-commit
#!/bin/sh
mvn p3c:check -q
if [ $? -ne 0 ]; then
    echo "❌ 代码不符合阿里规约,请修复后再提交"
    exit 1
fi

六、规约分层执行

层级检查方式时机
L1IDE 插件实时提示写代码时
L2Git commit hook提交时
L3CI 流水线检查push/PR
L4SonarQube 深度分析每日
L5Code ReviewPR 合并前

七、规约的核心原则

原则含义
清晰优于巧妙一眼能懂的代码优于"炫技"代码
显式优于隐式不要让代码"自动"做你意料之外的事
简单优于复杂能用 Map 不用 Guava,能用 for 不用 stream
一致优于特殊团队统一风格比"我的风格"重要
防御性编程永远不信任外部输入

八、实战:Code Review 清单

markdown
## ✅ 命名
- [ ] 类名大驼峰、方法/变量小驼峰、常量全大写
- [ ] 命名能准确表达意图(不缩写、不拼音)

## ✅ OOP
- [ ] 包装类比较用 equals
- [ ] BigDecimal 比较用 compareTo
- [ ] equals/hashCode 一并重写

## ✅ 并发
- [ ] 线程池显式声明参数
- [ ] SimpleDateFormat / Calendar 用 ThreadLocal

## ✅ 异常
- [ ] 不丢原始异常
- [ ] finally 关闭资源(try-with-resources)
- [ ] 不捕获 Throwable(除非特殊场景)

## ✅ 集合
- [ ] 遍历用 entrySet
- [ ] 初始化时指定容量
- [ ] subList 不可修改父集合

## ✅ 日志
- [ ] 用 SLF4J
- [ ] 占位符 {} 而非拼接
- [ ] 不打印敏感信息

## ✅ 注释
- [ ] 类、方法、字段有 Javadoc
- [ ] 复杂逻辑有解释
- [ ] 不存在 TODO / FIXME(遗留要开 issue)

## ✅ 安全
- [ ] SQL 用 #{} 占位
- [ ] 用户输入做校验
- [ ] 密码等敏感字段加密

九、规约例外

不是所有规约都必须严格遵守。比如:

  • 性能优化:可以用一些"反规约"的写法(如牺牲可读性换取性能)
  • 遗留代码对接:用反射、绕过类型检查
  • 框架特殊需求:如 @Override 在接口方法上是 IDE 提示的,规约不强制

原则:理解规约背后的为什么,再决定是否打破。

十、本章小结

要点关键
命名类大驼峰、方法/变量小驼峰、常量大写下划线
OOPequals/hashCode、BigDecimal.compareTo
并发线程池、ThreadLocal、ConcurrentHashMap
异常try-with-resources、保留原始异常
集合entrySet、指定容量、不可变
检查IDE 插件 + Git Hook + CI + SonarQube
核心清晰 / 显式 / 简单 / 一致 / 防御

动手练习

练习 1:基础题

在你的项目中安装阿里规约插件(IDEA),扫描现有代码,列出 Top 10 问题并修复。

练习 2:进阶题

集成 P3C + Checkstyle + SonarLint,在 CI 流水线中加入规约检查,不通过的 PR 不允许合并。

练习 3:思考题

团队成员对"是否允许在 Controller 用 Lombok 的 @Accessors(chain = true)"有分歧。如何用规约和 Review 流程化解?


下一章第 81 章:Git 规范与协作

本站基于 VitePress 构建 · 由 Codebook 团队维护