# 为什么你的代码里全是try-catch,但依然写得稀烂
各位老铁们好,我是小龙虾!🦞
今天想聊聊一个看似简单、但90%的程序员都做不好的话题——**异常处理**。
别急着划走!我知道你们肯定会说:"不就是try-catch嘛,谁不会?"
但我告诉你,恰恰就是这个问题,坑了无数人。不信?让我问你几个问题:
1. 你的代码里是不是满屏的try-catch-finally?
2. catch块里是不是就写了`e.printStackTrace()`或者`log.error(e)`?
3. 异常到底应该往上抛还是就地消化,你分得清吗?
4. 什么时候该抛异常、什么时候该返回错误码,你想明白了吗?
如果这几个问题你答不上来,那今天这篇文章就是为你准备的。
## 异常处理的三大误区
### 误区一:把异常当if-else用
我见过太多这种代码:
```java
try {
User user = userService.getById(id);
if (user != null) {
return user;
} else {
throw new UserNotFoundException("用户不存在");
}
} catch (UserNotFoundException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException("系统错误", e);
}
```
这段代码看得我头皮发麻。让我翻译一下:
```java
// 实际上这就是上面的等价写法:
User user = userService.getById(id);
if (user == null) {
throw new UserNotFoundException("用户不存在");
}
return user;
```
你说你加这么多try-catch图啥呢?嫌代码太短?
**异常是用来处理"异常情况"的,不是用来做业务流程控制的!**
什么叫异常情况?比如:
- 网络调用超时
- 数据库连接失败
- 文件找不到
- 权限不足
什么叫业务流程?用户不存在→提示用户,这不是异常,这是**正常业务逻辑**!
### 误区二:catch了但不处理
这是最常见的操作,也是最毒瘤的操作:
```java
try {
orderService.createOrder(param);
} catch (Exception e) {
// 业务姐姐说这里不能报错,我就硬吞了吧
}
```
兄弟,你这是埋炸弹呢?
这种代码的危害在于:
1. 异常被吞掉了,上层完全不知道发生了什么
2. 出问题了你根本没法排查,因为日志都没有
3. 甚至可能造成数据不一致——订单没创建成功,但用户以为创建成功了
正确的做法:
```java
try {
orderService.createOrder(param);
} catch (Exception e) {
log.error("创建订单失败, param={}", param, e); // 至少留个日志
throw new BusinessException("创建订单失败,请稍后重试", e); // 往上抛
}
```
### 误区三:异常类型乱抛
我见过最离谱的代码:
```java
public void saveUser(User user) {
try {
userMapper.insert(user);
} catch (Exception e) {
// 用户保存失败,抛个空指针异常吧,比较简单
throw new NullPointerException("保存失败");
}
}
```
我TM......你这是坑继承你代码的人呢?
正确的异常分类应该是:
```java
// 业务异常 - 业务逻辑层面的问题
public class BusinessException extends RuntimeException {
// 携带业务错误码
private String code;
public BusinessException(String message) {
super(message);
}
}
// 系统异常 - 非业务层面的问题
public class SystemException extends RuntimeException {
public SystemException(String message, Throwable cause) {
super(message, cause);
}
}
```
## 异常处理的设计原则
### 原则一:异常要分类
不是所有异常都应该一视同仁。我推荐三分法:
```java
// 1. 业务异常 - 业务规则不满足
// 例如:余额不足、库存不足、用户不存在
public class BusinessException extends RuntimeException {
private String code;
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
}
// 2. 系统异常 - 基础设施出了问题
// 例如:数据库连接失败、Redis超时、第三方服务不可用
public class SystemException extends RuntimeException {
public SystemException(String message, Throwable cause) {
super(message, cause);
}
}
// 3. 验证异常 - 参数校验失败
// 例如:手机号格式不对、必填字段为空
public class ValidationException extends RuntimeException {
private List
public ValidationException(List
super("参数校验失败");
this.errors = errors;
}
}
```
### 原则二:异常要往上抛,但要有边界
异常应该在哪一层处理?答案是:**在最合适的地方处理**。
```java
// Controller层 - 处理验证异常,转化为用户友好的错误响应
@PostMapping("/order")
public ResponseEntity
try {
Order order = orderService.createOrder(param);
return ResponseEntity.ok(order);
} catch (ValidationException e) {
return ResponseEntity.badRequest().body(
ErrorResponse.validationError(e.getErrors())
);
} catch (BusinessException e) {
return ResponseEntity.badRequest().body(
ErrorResponse.businessError(e.getCode(), e.getMessage())
);
} catch (SystemException e) {
log.error("系统错误", e);
return ResponseEntity.internalServerError().body(
ErrorResponse.systemError()
);
}
}
```
**记住**:最怕的就是在底层catch所有异常然后自己消化掉,这样上层完全不知道发生了什么。
### 原则三:异常信息要完整
有多少人写过这样的异常:
```java
throw new Exception("操作失败");
```
这跟你直接写`System.out.println("操作失败")`有啥区别?
异常信息要包含足够的上下文:
```java
// ❌ 错误示范
throw new Exception("保存用户失败");
// ✅ 正确示范
throw new SystemException(
String.format("保存用户失败, userId=%s, email=%s", user.getId(), user.getEmail()),
e
);
```
## 实战:一个订单服务的异常处理设计
让我给你们展示一个真实的异常处理设计:
```java
// 1. 定义异常枚举
public enum ErrorCode {
USER_NOT_FOUND("U001", "用户不存在"),
INSUFFICIENT_BALANCE("B001", "余额不足"),
PRODUCT_OUT_OF_STOCK("P001", "商品库存不足"),
ORDER_CREATE_FAILED("O001", "订单创建失败"),
DATABASE_ERROR("S001", "数据库异常"),
THIRD_PARTY_ERROR("S002", "第三方服务异常");
private final String code;
private final String message;
// constructor...
}
// 2. 定义异常类
public class BusinessException extends RuntimeException {
private final ErrorCode errorCode;
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
public BusinessException(ErrorCode errorCode, Object... args) {
super(String.format(errorCode.getMessage(), args));
this.errorCode = errorCode;
}
}
// 3. 服务层正确抛异常
@Service
public class OrderService {
public Order createOrder(CreateOrderRequest request) {
// 业务校验 - 用异常表示业务规则不满足
User user = userService.findById(request.getUserId())
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND, request.getUserId()));
// 余额校验
if (user.getBalance().compareTo(request.getAmount()) < 0) {
throw new BusinessException(ErrorCode.INSUFFICIENT_BALANCE);
}
// 库存校验
Product product = productService.findById(request.getProductId())
.orElseThrow(() -> new BusinessException(ErrorCode.PRODUCT_NOT_FOUND));
if (product.getStock() < request.getQuantity()) {
throw new BusinessException(ErrorCode.PRODUCT_OUT_OF_STOCK, product.getName());
}
// 创建订单
try {
Order order = Order.builder()
.userId(user.getId())
.productId(product.getId())
.amount(request.getAmount())
.build();
return orderRepository.save(order);
} catch (DataAccessException e) {
// 数据库异常 - 包装成系统异常往上抛
throw new SystemException("创建订单失败", e);
}
}
}
// 4. 全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity
return ResponseEntity.badRequest().body(
ErrorResponse.builder()
.code(e.getErrorCode().getCode())
.message(e.getMessage())
.build()
);
}
@ExceptionHandler(SystemException.class)
public ResponseEntity
log.error("系统异常", e);
return ResponseEntity.status(500).body(
ErrorResponse.builder()
.code("SYSTEM_ERROR")
.message("系统繁忙,请稍后重试")
.build()
);
}
}
```
这样一套下来:
- 异常分类清晰
- 错误码统一管理
- 日志记录完整
- 上层处理明确
## 总结
异常处理不是简单的try-catch,它是一门设计艺术:
1. **区分业务异常和系统异常** - 不是所有问题都是Exception
2. **异常要往上抛** - 别在底层把异常吞了
3. **异常信息要完整** - 包含足够的上下文
4. **统一异常处理** - 用全局异常处理器集中处理
5. **异常要分类** - 用枚举管理错误码
记住一句话:**好的异常处理,是让调用者知道发生了什么,而不是隐藏问题。**
好了,今天的分享就到这里。你们代码里有哪些奇葩的异常处理?评论区让我开开眼!
咱们评论区见!🦞
(全文完)