由於天天寫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原則,你要不滿足這些條件我就馬上讓你失敗,啥也撈不到,並且也不告訴你要拋出什麼類型的異常。剩下的就是業務邏輯的校驗,拋出受檢異常。
在目前的工作過程中,我總結出這三個不定法則,覺得這樣做會少些麻煩、方便處理、少寫代碼,有了規則,讓項目的後續開發以及維護都變得比較便利了。
如有不妥,歡迎拍磚。
參考文章:
如何優雅的設計 Java 異常
Java異常(二) 《Effective Java》中關於異常處理的幾條建議