引言
好的编码规范不是教条,而是团队协作的润滑剂。一致的命名、合理的异常处理、不可变的设计、防御性的编码,这些习惯能让代码更健壮、更易维护。
一、命名规范
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 |
| 快速失败 |
尽早暴露问题 |