論一個好的工程師應該如何作好異常處理和日誌記錄

這是我參與更文挑戰的第26天,活動詳情查看:更文挑戰java

異常處理

  • 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問題
  • 定義時區分uncheckedchecked異常,避免直接拋出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級別只記錄系統邏輯出錯,異常或者重要的錯誤信息
  • 使用全英文來註釋和描述日誌錯誤信息
相關文章
相關標籤/搜索