Skip to content

代码规范

编码规范与质量保障,主要参考阿里巴巴 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 命名风格速查表

元素命名风格示例附加规则
类/接口UpperCamelCaseOrderService, Serializable接口不加 I 前缀
抽象类UpperCamelCaseAbstractRepositoryAbstractBase 开头
测试类UpperCamelCaseUserServiceTest以被测试类名开头,Test 结尾
异常类UpperCamelCaseOrderNotFoundExceptionException 结尾
方法lowerCamelCasegetUserById, saveOrder动词开头
变量lowerCamelCaseuserName, orderList名词
常量UPPER_SNAKE_CASEMAX_RETRY_COUNTstatic final 修饰
包名全小写com.example.order.service点分隔,单数形式
枚举UpperCamelCaseOrderStatus枚举值 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);
}

// ✅ 更推荐:使用枚举 + 策略模式替代大段 switch

6.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 等集合会出 bug

7.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: 应该写"为什么"。代码本身已经清楚地表达了"做什么",好的命名和清晰的代码结构不需要注释来重复。注释的价值在于解释"为什么这么做"——包括业务背景、设计决策、边界条件和特殊处理的原因。