異常處理
- 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級別只記錄系統邏輯出錯,異常或者重要的錯誤信息
- 使用全英文來注釋和描述日志錯誤信息