引言

好的编码规范不是教条,而是团队协作的润滑剂。一致的命名、合理的异常处理、不可变的设计、防御性的编码,这些习惯能让代码更健壮、更易维护。


一、命名规范

1.1 基本规则

类型 规范 示例
包名 全小写,点分隔 com.example.service
类名 UpperCamelCase UserService, OrderProcessor
方法名 lowerCamelCase findByName, calculateTotal
变量名 lowerCamelCase userName, orderCount
常量 UPPER_SNAKE_CASE MAX_RETRY_COUNT, DEFAULT_TIMEOUT
泛型 单大写字母 T, E, K, V
枚举 UpperCamelCase HttpStatus.OK

1.2 方法命名动词表

动词 含义 示例
get 获取属性 getName()
set 设置属性 setName(String)
is 布尔判断 isActive()
has 是否包含 hasPermission()
can 能力判断 canExecute()
find 查询(可能为空) findById()
list 查询列表 listByStatus()
count 计数 countByRole()
create 创建 createOrder()
update 更新 updateStatus()
delete 删除 deleteById()
save 保存(新建或更新) save(User)
validate 验证 validateInput()
convert 转换 convertToDTO()
build 构建 buildResponse()
compute 计算 computeHash()
resolve 解析/解决 resolveConflict()
handle 处理 handleException()
execute 执行 executeTask()
apply 应用 applyDiscount()

1.3 避免的命名

// ❌ 缩写不清晰
int d;                    // elapsed time in days?
String fn;                // first name? file name?

// ✅ 完整有意义的名称
int elapsedTimeInDays;
String firstName;

// ❌ 无意义后缀
String nameString;        // String 多余
int itemCountInt;         // Int 多余

// ✅ 简洁
String name;
int itemCount;

// ❌ 布尔变量不用 is/has/can
boolean flag;
boolean status;

// ✅ 布尔变量用 is/has/can
boolean isValid;
boolean hasPermission;
boolean canRetry;

// ❌ 数字后缀区分变量
String name1, name2;

// ✅ 有意义的区分
String originalName;
String modifiedName;

二、异常处理

2.1 异常分类

Throwable
├── Error(不应捕获)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── InternalError
└── Exception
    ├── 受检异常(Checked Exception)→ 必须处理
    │   ├── IOException
    │   ├── SQLException
    │   └── InterruptedException
    └── 非受检异常(RuntimeException)→ 可选处理
        ├── NullPointerException
        ├── IllegalArgumentException
        ├── IllegalStateException
        └── IndexOutOfBoundsException

2.2 异常处理原则

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

// ❌ 只打印日志不处理
try {
    doSomething();
} catch (Exception e) {
    e.printStackTrace(); // 不应该用 printStackTrace
}

// ✅ 记录日志并处理
try {
    doSomething();
} catch (IOException e) {
    log.error("Failed to read file: {}", filePath, e);
    throw new BusinessException("File read failed", e);
}

// ❌ 捕获范围过大
try {
    doSomething();
} catch (Exception e) { // 捕获所有异常
    log.error("Error", e);
}

// ✅ 捕获具体异常
try {
    doSomething();
} catch (FileNotFoundException e) {
    log.warn("File not found: {}", filePath);
    return defaultValue;
} catch (IOException e) {
    log.error("IO error reading file: {}", filePath, e);
    throw new BusinessException("File read failed", e);
}

2.3 自定义异常

// 业务异常(非受检)
public class BusinessException extends RuntimeException {
    private final String errorCode;

    public BusinessException(String message) {
        super(message);
        this.errorCode = "GENERIC_ERROR";
    }

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

    public BusinessException(String errorCode, String message, Throwable cause) {
        super(message, cause);
        this.errorCode = errorCode;
    }

    public String getErrorCode() {
        return errorCode;
    }
}

// 使用
if (balance < amount) {
    throw new BusinessException("INSUFFICIENT_BALANCE",
        "Insufficient balance: " + balance + " < " + amount);
}

2.4 try-with-resources

// ✅ 自动关闭资源(实现 AutoCloseable)
try (Connection conn = dataSource.getConnection();
     PreparedStatement stmt = conn.prepareStatement(sql);
     ResultSet rs = stmt.executeQuery()) {
    while (rs.next()) {
        // 处理结果
    }
} // 自动关闭 rs → stmt → conn

// ✅ 自定义 AutoCloseable
public class TempFile implements AutoCloseable {
    private final Path path;

    public TempFile(String prefix) throws IOException {
        this.path = Files.createTempFile(prefix, ".tmp");
    }

    public Path getPath() { return path; }

    @Override
    public void close() {
        try {
            Files.deleteIfExists(path);
        } catch (IOException e) {
            // 记录日志,不抛出
        }
    }
}

try (TempFile temp = new TempFile("data")) {
    Files.writeString(temp.getPath(), content);
    processFile(temp.getPath());
}

2.5 异常链

// ✅ 保留原始异常(cause)
public Data fetchData() {
    try {
        return readFromCache();
    } catch (CacheException e) {
        throw new BusinessException("CACHE_ERROR", "Failed to read cache", e);
    }
}

// ❌ 丢失原始异常
catch (CacheException e) {
    throw new BusinessException("Cache failed"); // 丢失了 e
}

三、不可变对象

3.1 为什么不可变

不可变对象的好处:
├── 线程安全:无需同步
├── 可缓存:hashCode 可预计算
├── 可共享:无需防御性复制
├── 可靠:创建后状态不变
└── 简单:减少状态管理的复杂度

Java 中的不可变示例:
├── String
├── Integer, Long 等包装类
├── BigDecimal
├── LocalDate, LocalDateTime
└── ImmutableList (Guava)

3.2 设计不可变类

public final class Money {
    private final BigDecimal amount;
    private final String currency;

    public Money(BigDecimal amount, String currency) {
        this.amount = amount;
        this.currency = Objects.requireNonNull(currency, "currency must not be null");
    }

    // 不提供 setter
    public BigDecimal getAmount() { return amount; }
    public String getCurrency() { return currency; }

    // 操作返回新对象
    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("Currency mismatch");
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Money)) return false;
        Money money = (Money) o;
        return amount.equals(money.amount) && currency.equals(money.currency);
    }

    @Override
    public int hashCode() {
        return Objects.hash(amount, currency);
    }

    @Override
    public String toString() {
        return amount + " " + currency;
    }
}

3.3 Record 实现不可变(Java 16+)

// Record 天然不可变
public record Money(BigDecimal amount, String currency) {
    public Money {
        Objects.requireNonNull(currency, "currency must not be null");
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("Amount must be non-negative");
        }
    }

    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("Currency mismatch");
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }
}

3.4 集合不可变

// JDK 不可变集合
List<String> list = List.of("a", "b", "c");
Set<String> set = Set.of("a", "b", "c");
Map<String, Integer> map = Map.of("a", 1, "b", 2);

// ❌ 修改会抛异常
list.add("d"); // UnsupportedOperationException

// ⚠️ unmodifiableList 是视图,原集合修改会影响它
List<String> immutable = Collections.unmodifiableList(mutable);
// mutable 修改后 immutable 也会看到变化

// ✅ 真正的不可变副本
List<String> copy = List.copyOf(mutable);

四、防御性编程

4.1 参数校验

public User createUser(String name, int age, String email) {
    Objects.requireNonNull(name, "name must not be null");
    Objects.requireNonNull(email, "email must not be null");

    if (name.isBlank()) {
        throw new IllegalArgumentException("name must not be blank");
    }
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("age must be between 0 and 150");
    }
    if (!email.contains("@")) {
        throw new IllegalArgumentException("invalid email format");
    }

    return new User(name, age, email);
}

4.2 防御性复制

public class Order {
    private final List<Item> items;

    public Order(List<Item> items) {
        // ✅ 防御性复制:不直接引用外部可变对象
        this.items = new ArrayList<>(items);
    }

    public List<Item> getItems() {
        // ✅ 返回不可修改的视图或副本
        return Collections.unmodifiableList(items);
    }
}

4.3 返回空集合而非 null

// ❌ 返回 null
public List<Order> getOrdersByUser(String userId) {
    List<Order> orders = orderDao.findByUser(userId);
    return orders.isEmpty() ? null : orders; // 调用方必须 null 检查
}

// ✅ 返回空集合
public List<Order> getOrdersByUser(String userId) {
    List<Order> orders = orderDao.findByUser(userId);
    return orders; // 空集合也是合法的
}

// ✅ 使用 Optional 表示可能为空的单值
public Optional<User> findById(String id) {
    return Optional.ofNullable(userDao.selectById(id));
}

4.4 快速失败(Fail-Fast)

// 尽早暴露问题,不要让错误传播
public void processOrder(Order order) {
    Objects.requireNonNull(order, "order must not be null");
    if (order.getItems().isEmpty()) {
        throw new IllegalArgumentException("order must have at least one item");
    }
    if (order.getTotal().compareTo(BigDecimal.ZERO) <= 0) {
        throw new IllegalArgumentException("order total must be positive");
    }

    validateInventory(order);
    reserveItems(order);
    processPayment(order);
}

4.5 前置条件工具

// Guava Preconditions
Preconditions.checkNotNull(name, "name must not be null");
Preconditions.checkArgument(age >= 0, "age must be non-negative, got: %s", age);
Preconditions.checkState(isInitialized, "service not initialized");

// JDK Objects
Objects.requireNonNull(name, "name must not be null");

// 自定义验证工具
public class Validate {
    public static void isTrue(boolean expression, String message) {
        if (!expression) {
            throw new IllegalArgumentException(message);
        }
    }

    public static void notEmpty(String value, String message) {
        if (value == null || value.isBlank()) {
            throw new IllegalArgumentException(message);
        }
    }

    public static <T> void notEmpty(Collection<T> collection, String message) {
        if (collection == null || collection.isEmpty()) {
            throw new IllegalArgumentException(message);
        }
    }
}

五、代码设计原则

5.1 SOLID 原则速查

原则 含义 简述
S - 单一职责 一个类只有一个变化原因 小而专注
O - 开闭原则 对扩展开放,对修改关闭 策略/模板模式
L - 里氏替换 子类可以替换父类 不要破坏契约
I - 接口隔离 接口要小而专 不要胖接口
D - 依赖倒置 依赖抽象不依赖实现 面向接口编程

5.2 方法设计

// ✅ 方法短小(不超过 20 行)
// ✅ 单一抽象层次
// ❌ 混合抽象层次
public void process() {
    readData();       // 高层
    String line = reader.readLine(); // 低层,不应该在这里
    validateData();   // 高层
    saveData();       // 高层
}

// ✅ 统一抽象层次
public void process() {
    Data data = readData();
    validateData(data);
    saveData(data);
}

// ✅ 方法参数不超过 3 个
// ❌ 过多参数
public User create(String name, int age, String email, String phone, String address) { }

// ✅ 使用对象封装参数
public User create(CreateUserRequest request) { }

// ✅ 布尔参数用枚举替代
// ❌
sendEmail(to, subject, body, true); // true 是什么意思?
// ✅
sendEmail(to, subject, body, EmailType.HTML);

5.3 注释原则

// ❌ 无用的注释
i++; // i 加 1

// ❌ 代码已经表达的注释
// 获取用户名称
String name = user.getName();

// ✅ 解释为什么(Why),不是做什么(What)
// 使用二分查找而非线性查找,因为列表已排序且数据量大
int index = Collections.binarySearch(sortedList, target);

// ✅ 标注 TODO 和 FIXME
// TODO: 后续优化为批量查询
// FIXME: 并发场景下可能重复创建

// ✅ 公共 API 的 Javadoc
/**
 * 根据用户 ID 查找用户。
 *
 * @param userId 用户 ID,不能为 null
 * @return 用户信息,如果不存在返回 empty Optional
 * @throws IllegalArgumentException 如果 userId 为 null
 */
public Optional<User> findById(String userId) { ... }

总结

要点 建议
命名 有意义、可读、一致
异常 不吞掉、不丢失、具体捕获
不可变 优先设计不可变类
防御性 校验参数、防御性复制、返回空集合
方法 短小、单一职责、参数少
注释 解释 Why,不解释 What
快速失败 尽早暴露问题

更多推荐