第 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六、规约分层执行
| 层级 | 检查方式 | 时机 |
|---|---|---|
| L1 | IDE 插件实时提示 | 写代码时 |
| L2 | Git commit hook | 提交时 |
| L3 | CI 流水线检查 | push/PR |
| L4 | SonarQube 深度分析 | 每日 |
| L5 | Code Review | PR 合并前 |
七、规约的核心原则
| 原则 | 含义 |
|---|---|
| 清晰优于巧妙 | 一眼能懂的代码优于"炫技"代码 |
| 显式优于隐式 | 不要让代码"自动"做你意料之外的事 |
| 简单优于复杂 | 能用 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 提示的,规约不强制
原则:理解规约背后的为什么,再决定是否打破。
十、本章小结
| 要点 | 关键 |
|---|---|
| 命名 | 类大驼峰、方法/变量小驼峰、常量大写下划线 |
| OOP | equals/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 规范与协作 →