在日常开发中实现异常处理和空值处理的最佳实践
在日常开发中实现异常处理和空值处理的最佳实践
在日常开发中,良好的异常处理和空值处理是编写健壮、可维护代码的关键。以下是一些实用的最佳实践和具体实现方法:
1. 空值处理策略
1.1 使用 Optional 类
// 不推荐:直接返回可能为null的值
public String findUserName(Long userId) {User user = userRepository.findById(userId);return user != null ? user.getName() : null;
}// 推荐:使用Optional明确表示可能为空
public Optional<String> findUserName(Long userId) {return userRepository.findById(userId).map(User::getName);
}// 调用方处理
Optional<String> userName = findUserName(123L);
String name = userName.orElse("Unknown User");
// 或者
String name = userName.orElseThrow(() -> new UserNotFoundException("User not found: " + 123L));
1.2 使用 Objects 工具类
// 参数验证
public void processUser(User user) {Objects.requireNonNull(user, "User cannot be null");Objects.requireNonNull(user.getName(), "User name cannot be null");// 处理逻辑
}// 默认值处理
public String getUserDisplayName(User user) {String name = Objects.requireNonNullElse(user.getDisplayName(), "Anonymous");return name.trim();
}
1.3 集合空值处理
// 返回空集合而不是null
public List<String> getUserRoles(Long userId) {List<String> roles = userRoleRepository.findByUserId(userId);return roles != null ? roles : Collections.emptyList();
}// 使用Collections工具类
public List<String> getUserPermissions(Long userId) {return Optional.ofNullable(permissionRepository.findByUserId(userId)).orElse(Collections.emptyList());
}
1.4 字符串空值处理
// 使用StringUtils(Apache Commons Lang或Spring)
public String processInput(String input) {if (StringUtils.isEmpty(input)) {return "default";}return input.trim();
}// 或者使用Java 11+的isBlank
public String sanitizeInput(String input) {if (input == null || input.isBlank()) {return "";}return input.trim();
}
2. 异常处理策略
2.1 定义清晰的异常层次结构
// 基础业务异常
public abstract class BusinessException extends RuntimeException {private final String errorCode;public BusinessException(String errorCode, String message) {super(message);this.errorCode = errorCode;}public String getErrorCode() {return errorCode;}
}// 具体业务异常
public class UserNotFoundException extends BusinessException {public UserNotFoundException(Long userId) {super("USER_NOT_FOUND", "User not found with ID: " + userId);}
}public class InsufficientPermissionsException extends BusinessException {public InsufficientPermissionsException(String operation) {super("INSUFFICIENT_PERMISSIONS", "Insufficient permissions for operation: " + operation);}
}
2.2 使用全局异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {// 处理业务异常@ExceptionHandler(BusinessException.class)public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {log.warn("Business exception: {}", ex.getMessage());ErrorResponse error = new ErrorResponse(ex.getErrorCode(),ex.getMessage(),HttpStatus.BAD_REQUEST.value());return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);}// 处理数据访问异常@ExceptionHandler(DataAccessException.class)public ResponseEntity<ErrorResponse> handleDataAccessException(DataAccessException ex) {log.error("Data access error", ex);ErrorResponse error = new ErrorResponse("DATA_ACCESS_ERROR","An error occurred while accessing data",HttpStatus.INTERNAL_SERVER_ERROR.value());return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);}// 处理参数验证异常@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {Map<String, String> errors = ex.getBindingResult().getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField,fieldError -> fieldError.getDefaultMessage() != null ? fieldError.getDefaultMessage() : "Validation failed"));ErrorResponse error = new ErrorResponse("VALIDATION_FAILED","Input validation failed",HttpStatus.BAD_REQUEST.value(),errors);return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);}
}
2.3 异常处理工具类
@Slf4j
public final class ExceptionHandlingUtils {private ExceptionHandlingUtils() {// 工具类,防止实例化}// 安全执行操作,返回Optionalpublic static <T> Optional<T> executeSafely(Supplier<T> operation, String operationName) {try {return Optional.ofNullable(operation.get());} catch (Exception e) {log.warn("Operation {} failed: {}", operationName, e.getMessage());return Optional.empty();}}// 安全执行操作,提供默认值public static <T> T executeSafely(Supplier<T> operation, T defaultValue, String operationName) {try {T result = operation.get();return result != null ? result : defaultValue;} catch (Exception e) {log.warn("Operation {} failed: {}", operationName, e.getMessage());return defaultValue;}}// 安全执行操作,抛出特定异常public static <T, E extends RuntimeException> T executeOrThrow(Supplier<T> operation, Supplier<E> exceptionSupplier,String operationName) {try {T result = operation.get();if (result == null) {throw exceptionSupplier.get();}return result;} catch (Exception e) {log.error("Operation {} failed", operationName, e);throw exceptionSupplier.get();}}
}// 使用示例
public User findUserSafe(Long userId) {return ExceptionHandlingUtils.executeSafely(() -> userRepository.findById(userId),null,"findUserById");
}
2.4 资源管理和异常处理
// 使用try-with-resources确保资源释放
public String readFileContent(String filePath) {try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {StringBuilder content = new StringBuilder();String line;while ((line = reader.readLine()) != null) {content.append(line).append("\n");}return content.toString();} catch (IOException e) {throw new FileReadException("Failed to read file: " + filePath, e);}
}// 数据库事务中的异常处理
@Transactional
public void updateUserProfile(Long userId, UserProfile profile) {try {User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException(userId));user.setProfile(profile);userRepository.save(user);// 发送通知notificationService.sendProfileUpdateNotification(userId);} catch (NotificationException e) {// 记录日志但继续事务log.warn("Failed to send notification for user: {}", userId, e);// 事务不会回滚,因为NotificationException不是RuntimeException}
}
3. 日常开发中的实践建议
3.1 代码审查清单
在代码审查时,关注以下异常和空值处理方面:
- 方法是否返回null而不是空集合?
- 是否检查了参数的有效性?
- 是否适当处理了可能为空的返回值?
- 异常消息是否包含足够的上下文信息?
- 是否捕获了过于宽泛的Exception?
- 资源是否被正确关闭?
- 日志记录是否适当?
3.2 单元测试中的异常测试
@Test
void shouldThrowExceptionWhenUserNotFound() {// 准备Long nonExistentUserId = 999L;// 执行和断言assertThrows(UserNotFoundException.class, () -> {userService.getUserDetails(nonExistentUserId);});
}@Test
void shouldReturnEmptyOptionalWhenUserNotFound() {// 准备Long nonExistentUserId = 999L;// 执行Optional<User> result = userService.findUser(nonExistentUserId);// 断言assertFalse(result.isPresent());
}@Test
void shouldHandleNullInputGracefully() {// 执行String result = stringProcessor.process(null);// 断言assertEquals("default", result);
}
3.3 使用断言进行防御性编程
public class ValidationUtils {public static void notNull(Object obj, String message) {if (obj == null) {throw new IllegalArgumentException(message);}}public static void notBlank(String str, String message) {if (str == null || str.trim().isEmpty()) {throw new IllegalArgumentException(message);}}public static void isTrue(boolean condition, String message) {if (!condition) {throw new IllegalArgumentException(message);}}
}// 在业务代码中使用
public void createUser(String username, String email) {ValidationUtils.notBlank(username, "Username cannot be blank");ValidationUtils.notBlank(email, "Email cannot be blank");ValidationUtils.isTrue(email.contains("@"), "Invalid email format");// 创建用户的逻辑
}
4. 总结
在日常开发中实现良好的异常和空值处理需要:
- 明确策略:确定何时使用Optional、何时抛出异常、何时返回默认值
- 一致性:在整个项目中保持一致的异常和空值处理模式
- 上下文信息:在异常和日志中包含足够的上下文信息以便调试
- 防御性编程:验证输入参数,使用断言防止无效状态
- 资源管理:确保资源正确释放,使用try-with-resources
- 测试覆盖:编写测试验证异常情况和边界条件
通过遵循这些最佳实践,您可以编写出更加健壮、可维护和可靠的代码。