異常處理
- Java類庫中定義的可以通過預檢查方式規避的RuntimeException異常不應該通過catch方式來處理:
- NullPointerException
- IndexOutofBoundsException
- 無法通過預檢查的異常除外: 在解析字串形式數字時,不得不通過catch NumberFormatException來實現
if (obj != null) {}
- 異常不要用來做流程控制,條件控制:
- 異常設計的初衷是解決程式執行中的各種意外情況,且異常的處理效率比條件判斷方式要低很多
- 使用catch時要區分穩定程式碼和非穩定程式碼:
- 穩定程式碼: 無論如何不會出錯的程式碼
- 非穩定程式碼: 非穩定程式碼的catch儘可能區分異常型別,再做對應處理
- 對於大段程式碼進行try - catch,會使得程式無法根據不同的異常做出正確的應激反應,也不利於定位問題
- 在使用者註冊場景中,如果使用者輸入非法字元,或者使用者名稱稱已存在,或者使用者密碼過於簡單,在程式上作出分門別類的判斷,並提示給使用者
- 捕獲異常是為了處理,不要捕獲了什麼都不處理.如果不需要處理,應該將異常拋給呼叫者
- 最外層的業務使用者,必須處理異常,將其轉化為使用者可以理解的內容
- 如果有try塊放到了事務程式碼中 ,catch異常後,如果需要回滾事務,一定要注意手動回滾事務
- finally塊必須對資源物件,流物件進行關閉,有異常也要做try - catch
- JDK 7以後,可以使用try - with - resources 方式
- 不要在finally塊中使用return:
- finally塊中的return返回後方法結束執行,不會再執行try塊中的return語句
- 捕獲異常與丟擲異常必須完全匹配,或者是拋異常的父類
- 方法的返回值可以為null,不強制返回空集合或者空物件等,必須新增註釋充分說明什麼情況下會返回null值
- 即使呼叫方法返回空集合或者空物件,對於呼叫者來說,必須考慮到遠端呼叫失敗,序列化失敗,執行時異常等返回null的場景
- 一定要防止出現NPE異常,注意NPE產生的場景:
- 返回型別為基本資料型別,return包裝資料型別的物件時, 自動拆箱有可能產生NPE
- 資料庫的查詢結果可能為null
- 集合裡的元素即使isNotEmpty, 取出的資料元素也可能為null
- 遠端呼叫返回物件時,一律要進行空指標判斷,防止NPE
- 對於Session中獲取的資料,建議進行NPE檢查,避免空指標
- 級聯呼叫obj.getA().getB.getC(), 一連串的呼叫,容易產生NPE
- JDK 8使用Optional類來防止NPE問題
- 定義時區分unchecked和checked異常,避免直接丟擲new RuntimeException(), 不允許丟擲Exception或者Throwable, 應該使用有業務含義的自定義異常
- 推薦使用業務界已定義過的異常:
- DAOException
- ServiceException
- 對於公司外的http或者api開放介面必須使用 "錯誤碼"; 應用內部推薦異常丟擲; 跨應用間的RPC呼叫優先考慮使用Result方式,封裝isSuccess()方法,錯誤碼,錯誤簡簡訊息
- RPC方法使用Result方式的原因:
- 使用拋異常返回方式,呼叫方如果沒有捕獲到就會產生執行時錯誤
- 如果不加棧資訊,只是new自定義異常,加入自己理解的error message, 對於呼叫端解決問題的幫助不會太多.如果加了棧資訊,在頻繁呼叫出錯的情況下,資料序列化和傳輸的效能損耗也是問題
- 避免出現重複的程式碼,即DRY(Don't Repeat Yourself)原則:
- 重複的程式碼在以後的修改時,需要修改所有的副本,容易遺漏
- 抽取共性方法,或者抽象公共類,或者元件化
- 一個類中有多個public方法,都需要進行數行相同的引數校驗工作,這個時候就要進行抽取:
private boolean checkParam(DTO dto) {...}
日誌規約
- 應用中不可直接使用日誌系統(log4j,logback)中的API,應該使用日誌框架slf4j中的API, 使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式統一
- 日誌檔案至少儲存15天,因為有些異常具備以 "周" 為頻次發生的特點
- 應用中的擴充套件日誌(打點,臨時監控,訪問日誌等)命名方式:
- appName_logType_logName.log
- logType: 日誌型別,如 stats,monitor,access
- logName: 日誌描述
- 這樣通過檔名就可以知道日誌檔案屬於什麼應用,什麼型別,什麼目的,也方便歸類查詢
- mppserver應用中單獨監控時區轉換異常: mppserver_monitor_timeZoneConvert.log
- 對日誌進行分類,比如將錯誤日誌和業務日誌分開存放,便於開發人員檢視,也便於對日誌系統進行及時監控
- 對 trace,debug,info級別的日誌輸出,必須使用條件輸出形式或者使用佔位符方式
logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
- 如果日誌級別是warn: 上述日誌不會列印,但是或執行字串拼接操作
- 如果symbol是物件,會執行toString() 方法,浪費了系統資源,執行上述操作,最終日誌卻沒有列印
- 使用條件輸出形式:
if (logger.isDebugEnabled()) {
logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
}
logger.debug("Processing trade with id: {} and symbol: {}, id, symbol);
- 避免重複列印日誌,浪費磁碟空間,必須在log4j.xml中設定additivity=false
<logger name="com.oxford.dubbo.config" additivity="false">
- 異常資訊包括:
- 案發現場資訊
- 異常堆疊資訊
- 如果不處理,應該通過異常關鍵字throws向上丟擲
logger.error(各類引數或者物件toString() + "_" + e.getMessage(), e);
- 謹慎的記錄日誌:
- 生產環境禁止輸出debug日誌
- 有選擇地輸出info日誌
- 如果使用warn來記錄剛上線時的業務行為資訊,一定要注意日誌輸出量問題,避免伺服器內容過多,並及時刪除這些觀察日誌
- 大量地輸出無效日誌,不利於系統效能的提升,也不利於快速定位錯誤點
- 記錄日誌時需要思考:
- 這些日誌真的有人看嗎?
- 看到這條日誌能夠做什麼?
- 能不能給排查問題帶來好處?
- 可以使用warn日誌級別來記錄使用者輸入引數錯誤的情況
- 注意日誌的輸出級別:
- error級別只記錄系統邏輯出錯,異常或者重要的錯誤資訊
- 使用全英文來註釋和描述日誌錯誤資訊