由于天天写bug,代码里各个地方充斥着对异常的处理,有时候思路被卡住:这个地方是该抛异常呢,还是我就地解决?是抛出受检异常(Exception)还是抛出非受检异常(RuntimeException)呢?这个异常带不带状态码啊?常常在想,有没有什么好的定律可以指导我优雅地处理Java异常。
目前项目中常见的抛异常的做法是
public interface IExceptionCode {
String name();
String getMessage();
}
public enum AccountExceptionEnum implements IExceptionCode {
// 枚举名就是状态码
ACCOUNT0001("验证码错误");
private String message;
AccountExceptionEnum(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
}
public class ServiceException extends Exception {
private String code;
public ServiceException(String message) {
super(message);
}
public ServiceException(String code, String message) {
this(message);
this.setCode(code);
}
public ServiceException(String code, String message, Throwable cause) {
super(message, cause);
this.setCode(code);
}
public ServiceException(String code, Throwable cause) {
super(cause);
this.setCode(code);
}
public void setCode(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}
// 定义特定异常
public class AccountServiceException extends ServiceException {
public MarsServiceException(String message) {
super(message);
}
public MarsServiceException(IExceptionCode message) {
super(message.getMessage());
super.setCode(message.name());
}
}
抛出带状态码的特定受检异常:
throw new AccountServiceException(AccountExceptionEnum.ACCOUNT0001);
这些特定ServiceException类型的异常都抛到了前端,异常序列化后的字符串:
{
"code": "ACCOUNT0001",
"message": "验证码错误",
"data": null}
每次要抛出一个业务异常的时候,都要写一个code和message,当业务流程校验比较复杂的时候,就要在AccountExceptionEnum里定义很多的code、message。感觉不爽的是,这大量的code码没有产生业务意义,觉得很冗余。还有项目中异常的使用也比较乱。
那来给寄几定义几个使用异常的不定法则吧,从此寄几再也不用担心我使用异常了。
一、业务性api、open api、soa接口层抛出特定的受检(Exception)异常,不抛非受检异常(RuntimeException)
api接口和soa接口声明受检异常,抛出特定受检异常(比如上面定义的AccountServiceException),这样controller层对异常的序列化也比较方便,清晰明了,就告诉你我会抛出这种类型的异常,你小心点。
二、状态码有业务意义,才抛出带状态码的异常
1. 开放平台的open api接口
开放平台的open api接口产生的异常都要带上状态码,以便第三方平台调用的识别。
2、一般业务系统api接口
将项目中的异常分成两类,有状态码和没有状态码。将状态码抛出去一定得是有业务意义的,不然就直接将错误message抛给前端就好了。秉承的原则就是不要写一点多余的代码。
3、soa接口
服务之间的调用,原则和2中所说一样。
4、业务方法
只抛带错误message的异常。
三、以fail fast为原则,需要fail fast情景抛出非受检异常(RuntimeException);其他情况抛出受检异常(Exception)
使用guava的Preconditions(checkNotNull、checkArgument……)或jsr 303(@NotNull、@NotBlank、@NotEmpty……)这种类似校验都抛出非受检异常(RuntimeException)。这些校验符合fail fast原则,你要不满足这些条件我就马上让你失败,啥也捞不到,并且也不告诉你要抛出什么类型的异常。剩下的就是业务逻辑的校验,抛出受检异常。
在目前的工作过程中,我总结出这三个不定法则,觉得这样做会少些麻烦、方便处理、少写代码,有了规则,让项目的后续开发以及维护都变得比较便利了。
如有不妥,欢迎拍砖。