Appearance
代码规范
编码规范与质量保障,主要参考阿里巴巴 Java 开发手册(Alibaba Java Coding Guidelines)。
一、为什么需要代码规范
1.1 代码规范的价值
| 维度 | 价值 |
|---|---|
| 可读性 | 任何人都能快速理解代码意图,降低认知负担 |
| 可维护性 | 统一的风格使重构和修改更安全 |
| 团队协作 | 减少 Code Review 的风格争论,聚焦于逻辑正确性 |
| 缺陷预防 | 规范本身包含大量最佳实践,能提前规避常见坑 |
| 职业素养 | 整洁的代码是专业程序员的标志 |
1.2 坏代码的代价
java
// 这样一段代码,你需要多久才能理解?
public class A {
public void m(List<String> l) {
for(int i=0;i<l.size();i++){
String s=l.get(i);
if(s!=null&&s.length()>0){
System.out.println(s);
}
}
}
}可读性 = 0,维护性 = 0,团队协作 = 灾难。
二、阿里巴巴 Java 开发手册核心规则
阿里巴巴 Java 开发手册(P3C)分为三级规则:
| 级别 | 含义 | 说明 |
|---|---|---|
| 强制 (Mandatory) | 必须遵守,违反可能导致严重问题 | 如:equals 和 hashCode 必须同时重写 |
| 推荐 (Recommended) | 应当遵守,特殊情况需说明 | 如:集合初始化时指定容量 |
| 参考 (Reference) | 建议遵守,提升代码质量 | 如:使用 StringBuffer 拼接字符串 |
三、命名风格
3.1 命名基础规则
java
// ❌ 坏命名
class userservice { } // 类名应大驼峰
String UserName; // 变量应小驼峰
final int max_size = 100; // 常量应大写下划线
void DoSomething() { } // 方法应小驼峰
// ✅ 好命名
class UserService { } // 类名:大驼峰
String userName; // 变量:小驼峰
final int MAX_SIZE = 100; // 常量:大写下划线
void doSomething() { } // 方法:小驼峰3.2 命名风格速查表
| 元素 | 命名风格 | 示例 | 附加规则 |
|---|---|---|---|
| 类/接口 | UpperCamelCase | OrderService, Serializable | 接口不加 I 前缀 |
| 抽象类 | UpperCamelCase | AbstractRepository | 以 Abstract 或 Base 开头 |
| 测试类 | UpperCamelCase | UserServiceTest | 以被测试类名开头,Test 结尾 |
| 异常类 | UpperCamelCase | OrderNotFoundException | 以 Exception 结尾 |
| 方法 | lowerCamelCase | getUserById, saveOrder | 动词开头 |
| 变量 | lowerCamelCase | userName, orderList | 名词 |
| 常量 | UPPER_SNAKE_CASE | MAX_RETRY_COUNT | static final 修饰 |
| 包名 | 全小写 | com.example.order.service | 点分隔,单数形式 |
| 枚举 | UpperCamelCase | OrderStatus | 枚举值 UPPER_SNAKE |
| 布尔变量 | is/has/can 开头 | isDeleted, hasChildren | 不要用 flag 之类 |
3.3 命名禁忌
java
// ❌ 禁止的命名方式
String _name; // 不能以下划线开头
String $name; // 不能以美元符号开头
String name_; // 不能以下划线结尾
class _UserService { } // 类名同上
String userName; // 不要拼音与英文混用(除非是专有名词如 Alibaba)
String yonghuming; // 严禁纯拼音四、代码格式化
4.1 缩进与空格
java
// ✅ 标准格式
public class FormatExample {
// 4 个空格缩进(不用 Tab)
private String name;
// 运算符两侧加空格
int result = a + b * c;
// 逗号后加空格
public void method(String arg1, int arg2) {
// 左大括号不换行,右大括号独占一行
if (arg1 != null) {
doSomething();
}
}
}4.2 大括号与换行
java
// ❌ 错误格式
if (condition)
doSomething(); // 即使一行也必须加括号
if (condition)
{ // 左大括号不应该换行
doSomething();
}
// ✅ 正确格式
if (condition) {
doSomething();
}
// 空代码块可以简写
if (condition) {
// 说明为什么是空的
}4.3 行长度与换行
java
// ❌ 单行过长(超过 120 字符)
public void processOrder(String orderId, String userId, BigDecimal amount, OrderStatus status, PaymentMethod paymentMethod) {
// ✅ 参数换行 -- 8 个空格缩进对齐
public void processOrder(
String orderId,
String userId,
BigDecimal amount,
OrderStatus status,
PaymentMethod paymentMethod) {
// 方法体
}
// ✅ 运算符换行 -- 运算符在下一行行首
String message = "Hello " + userName
+ ", your order " + orderId
+ " has been shipped.";4.4 空行与分段
java
// ✅ 用空行分隔逻辑块
public class OrderService {
// 静态常量
private static final int MAX_RETRY = 3;
// 成员变量
private final OrderRepository orderRepository;
private final PaymentService paymentService;
// 构造函数
public OrderService(OrderRepository orderRepository,
PaymentService paymentService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
}
// 公开方法
public Order createOrder(CreateOrderRequest request) {
// 校验
validateRequest(request);
// 创建
Order order = buildOrder(request);
// 保存
return orderRepository.save(order);
}
// 私有方法
private void validateRequest(CreateOrderRequest request) {
// ...
}
}五、注释规则
5.1 Javadoc 注释
java
/**
* 订单服务 -- 处理订单的创建、查询、取消等核心操作。
*
* <p>该服务假设所有订单都已通过前置校验,调用方应确保数据完整性。
*
* @author iRain93
* @since 1.0.0
*/
public class OrderService {
/**
* 根据订单 ID 查询订单。
*
* @param orderId 订单 ID,不能为 null
* @return 订单对象,如果不存在返回 null
* @throws IllegalArgumentException 如果 orderId 为 null
*/
@Nullable
public Order findById(@NonNull Long orderId) {
Objects.requireNonNull(orderId, "orderId must not be null");
return orderRepository.findById(orderId);
}
}5.2 注释原则
| 原则 | 说明 |
|---|---|
| 解释"为什么" | 注释要说清楚为什么这么做,而不是重复代码做了什么 |
| 不写废话 | // 获取用户 这种注释毫无意义 |
| 及时更新 | 代码改了注释也要改,错误的注释比没有注释更糟糕 |
| 特殊逻辑必须注释 | 看似奇怪的代码一定要解释原因 |
| TODO 标记 | 临时方案用 // TODO: 原因 标记,并尽快修复 |
java
// ❌ 废话注释
// 获取用户名
String userName = user.getName();
// ✅ 有意义的注释
// 订单金额使用 BigDecimal 而非 double,避免浮点数精度丢失(如 0.1 + 0.2 != 0.3)
BigDecimal amount = order.getAmount();
// ✅ 特殊逻辑注释
// 由于上游系统在 OOM 时会返回空对象而非 null
// 这里需要同时检查 null 和 isEmpty()
if (data == null || data.isEmpty()) {
return fallback();
}
// ✅ TODO 注释
// TODO: 临时用内存缓存,Q2 迁移到 Redis
private final Map<String, Config> cache = new ConcurrentHashMap<>();六、控制语句
6.1 嵌套不超过 3 层
java
// ❌ 深层嵌套 -- 难以阅读
public void process(Order order) {
if (order != null) {
if (order.isPaid()) {
for (OrderItem item : order.getItems()) {
if (item.getStock() > 0) {
if (item.getWarehouse() != null) {
// 核心逻辑 -- 嵌套 5 层!
ship(item);
}
}
}
}
}
}
// ✅ 使用卫语句和提取方法
public void process(Order order) {
if (order == null || !order.isPaid()) {
return;
}
shipPaidOrder(order);
}
private void shipPaidOrder(Order order) {
for (OrderItem item : order.getItems()) {
shipItemIfPossible(item);
}
}
private void shipItemIfPossible(OrderItem item) {
if (item.getStock() <= 0 || item.getWarehouse() == null) {
return;
}
ship(item);
}6.2 switch 语句规范
java
// ✅ switch 必须包含 default
switch (status) {
case CREATED:
handleCreated(order);
break;
case PAID:
handlePaid(order);
break;
case SHIPPED:
handleShipped(order);
break;
default:
throw new IllegalArgumentException("未知订单状态: " + status);
}
// ✅ 更推荐:使用枚举 + 策略模式替代大段 switch6.3 括号与可读性
java
// ❌ 依赖运算符优先级 -- 可读性差
if (a && b || c && d) { }
// ✅ 用括号明确运算顺序
if ((a && b) || (c && d)) { }七、OOP 规则
7.1 禁止使用原始类型
java
// ❌ 原始类型(Raw Type)
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 需要强制转换,不安全
// ✅ 泛型
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 类型安全,无需转换7.2 必须使用 @Override
java
public class UserService extends BaseService {
// ❌ 没有 @Override
public void init() {
// 如果父类没有 init() 方法,编译器不会报错
// 你以为在重写,实际上是个新方法 -- 隐蔽 bug
}
// ✅ 加上 @Override
@Override
public void init() {
// 如果父类没有 init(),编译器直接报错
}
}7.3 equals 和 hashCode 契约
java
// ✅ 重写 equals 必须同时重写 hashCode
public class User {
private Long id;
private String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return Objects.equals(id, user.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
// 契约:
// 1. 如果 a.equals(b) == true,则 a.hashCode() == b.hashCode()
// 2. 如果重写 equals 不重写 hashCode,HashMap 等集合会出 bug7.4 不可变对象
java
// ✅ 设计不可变对象
public final class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
this.amount = amount;
this.currency = currency;
}
public BigDecimal getAmount() {
return amount;
}
public Money add(Money other) {
// 返回新对象,不修改自身
return new Money(this.amount.add(other.amount), this.currency);
}
}7.5 静态方法和字段
java
// ❌ 通过对象引用调用静态方法
User user = new User();
user.staticMethod(); // 误导性 -- 看起来像实例方法
// ✅ 通过类名调用静态方法
User.staticMethod();八、代码质量检查
8.1 开发工具集成
| 工具 | 用途 | 集成方式 |
|---|---|---|
| Checkstyle | 代码风格检查 | Maven/Gradle 插件,IDE 集成 |
| PMD / SpotBugs | 静态代码分析 | CI/CD 流水线 |
| SonarQube | 代码质量平台 | 持续集成 |
| Alibaba P3C | 阿里巴巴规范检查 | IDEA 插件 Alibaba Java Coding Guidelines |
8.2 Code Review 检查清单
- [ ] 命名是否清晰、符合规范?
- [ ] 方法是否过长?(>50 行)
- [ ] 是否有深层嵌套?(>3 层)
- [ ] 异常是否被正确处理?
- [ ] equals/hashCode 是否成对重写?
- [ ] 资源是否关闭?(IO、连接)
- [ ] 是否有线程安全问题?
- [ ] 是否有必要的单元测试?
- [ ] 日志是否合适?(级别、参数化)
- [ ] 硬编码是否提取为常量?
面试常见问题
Q1: 为什么重写 equals 必须重写 hashCode?
A: 这是 Java 规范中 equals 和 hashCode 的契约要求。HashMap/HashSet 等散列集合先通过 hashCode 定位桶,再用 equals 比较。如果两个对象 equals 相等但 hashCode 不同,同一个对象可能在 HashMap 中存储了两份,导致逻辑错误。反之,如果只重写 hashCode 不重写 equals,不同的对象可能因为哈希冲突无法正确区分。
Q2: 代码规范中为什么推荐使用卫语句(Guard Clause)?
A: 卫语句通过提前返回或抛出异常,将异常情况在所有核心逻辑之前处理掉。它消除了深层嵌套的 if-else,使代码的主要逻辑路径保持扁平化,大幅提升可读性和可维护性。遵循"快乐路径优先"原则。
Q3: 为什么禁止使用原始类型(Raw Type)?
A: 原始类型绕过了编译器的泛型类型检查,可能导致 ClassCastException。使用泛型可以在编译期发现类型错误,避免运行时异常,同时消除不必要的强制类型转换,使代码更安全、更简洁。
Q4: 阿里巴巴开发手册中强制规则和推荐规则的区别是什么?
A: 强制规则必须严格遵守,违反可能导致严重问题(如 equals/hashCode 必须同时重写、不能在 finally 块中 return)。推荐规则应当遵守,但特殊情况可以例外并需要注释说明(如集合初始化时指定容量)。参考规则是建议性的,用于提升代码质量。
Q5: 注释应该写"做什么"还是"为什么"?
A: 应该写"为什么"。代码本身已经清楚地表达了"做什么",好的命名和清晰的代码结构不需要注释来重复。注释的价值在于解释"为什么这么做"——包括业务背景、设计决策、边界条件和特殊处理的原因。
