1. 程式人生 > >Java 編碼規範9(異常日誌)

Java 編碼規範9(異常日誌)

一. 異常處理

  1. [強制] Java 類庫中定義的可以通過預檢查方式規避的RuntimeException異常不應該通過catch 的方式來處理,比如:NullPointerExceptionIndexOutOfBoundsException等等。

    • 說明:無法通過預檢查的異常除外,比如,在解析字串形式的數字時,不得不通過catch NumberFormatException來實現。
    • 正例:if (obj != null) {...}
    • 反例:try { obj.method() } catch (NullPointerException e) {…}
  2. [強制] 異常不要用來做流程控制,條件控制。

    • 異常設計的初衷是解決程式執行中的各種意外情況,且異常的處理效率比條件判斷方式要低很多。
  3. [強制] catch時請分清穩定程式碼和非穩定程式碼.

    • 穩定程式碼: 是無論如何不會出錯的程式碼。
    • 對於非穩定程式碼的catch儘可能進行區分異常型別,再做對應的異常處理。
    • [敲黑板] 對大段程式碼進行try-catch,使程式無法根據不同的異常做出正確的應激反應,也不利於定位問題,這是一種不負責任的表現。

    • 示例: 使用者註冊的場景中,如果使用者輸入非法字元,或使用者名稱稱已存在,或使用者輸入密碼過於簡單,在程式上作出分門別類的判斷,並提示給使用者。

  4. [強制] 捕獲異常是為了處理它,不要捕獲了卻什麼都不處理而拋棄之,如果不想處理它,請將該異常拋給它的呼叫者。最外層的業務使用者,必須處理異常,將其轉化為使用者可以理解的內容。

  5. [強制] 有try塊放到了事務程式碼中,catch異常後,如果需要回滾事務,一定要注意手動回滾事務。

  6. [強制] finally塊必須對資源物件、流物件進行關閉,有異常也要做try-catch。

    • 如果JDK7及以上,可以使用try-with-resources方式。
  7. [強制] 不要在finally塊中使用 return

    • finally塊中的 return返回後方法結束執行 ,不會再返回後方法結束執行 ,不會再try塊中的 return語句。
  8. [強制] 捕獲異常與拋異常,必須是完全匹配,或者捕獲異常是拋異常的父類。

    • 說明:如果預期對方拋的是繡球,實際接到的是鉛球,就會產生意外情況。
  9. [強制]

    方法的返回值可以為null,不強制返回空集合,或者空物件等。但是必須添加註釋充分說明什麼情況下會返回null值。

    • 本手冊明確防止NPE是呼叫者的責任。即使被呼叫方法返回空集合或者空物件,對呼叫者來說,也並非高枕無憂,必須考慮到遠端呼叫失敗、序列化失敗、執行時異常等場景返回null的情況。
  10. [推薦] 防止NPE,是程式設計師的基本修養。

    • 注意NPE產生的場景:

      1. 返回型別為基本資料型別,return包裝資料型別的物件時,自動拆箱有可能產生NPE。
        • 反例:public int f() { return Integer物件}, 如果為null,自動解箱拋NPE。
      2. 資料庫的查詢結果可能為null。
      3. 集合裡的元素即使isNotEmpty,取出的資料元素也可能為null。
      4. 遠端呼叫返回物件時,一律要求進行空指標判斷,防止NPE。
      5. 對於Session中獲取的資料,建議NPE檢查,避免空指標。
      6. 級聯呼叫obj.getA().getB().getC();一連串呼叫,易產生NPE。
    • 正例: 使用JDK8的Optional類來防止NPE問題。

  11. [推薦] 定義時區分unchecked / checked 異常,避免直接丟擲new RuntimeException(),更不允許丟擲Exception或者Throwable,應使用有業務含義的自定義異常。

    • 推薦業界已定義過的自定義異常,如:DAOException / ServiceException等。
  12. [推薦] 對於公司外的http/api開放介面必須使用“錯誤碼” 。

    • 應用內部推薦異常丟擲;
    • 跨應用間RPC呼叫優先考慮使用Result方式,封裝isSuccess()方法、“錯誤碼”、“錯誤簡簡訊息”。
      • 關於RPC方法返回方式使用Result方式的理由:
        • 使用拋異常返回方式,呼叫方如果沒有捕獲到就會產生執行時錯誤。
        • 如果不加棧資訊,只是new自定義異常,加入自己的理解的error message,對於呼叫端解決問題的幫助不會太多。如果加了棧資訊,在頻繁調用出錯的情況下,資料序列化和傳輸的效能損耗也是問題。
  13. [推薦] 避免出現重複的程式碼(Don’t Repeat Yourself),即DRY原則。

    • 說明: 隨意複製和貼上程式碼,必然會導致程式碼的重複,在以後需要修改時,需要修改所有的副本,容易遺漏。必要時抽取共性方法,或者抽象公共類,甚至是元件化。
    • 正例: 一個類中有多個public方法,都需要進行數行相同的引數校驗操作,這個時候請抽取:

      private boolean checkParam(DTO dto) {
          ...
      }

二. 日誌規約

  1. [強制] 應用中不可直接使用日誌系統(Log4j、Logback)中的API,而應依賴使用日誌框架SLF4J中的API,使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式統一。

    ```
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    private static final Logger logger = LoggerFactory.getLogger(Abc.class);
    ```
    
  2. [強制] 日誌檔案推薦至少儲存15天,因為有些異常具備以“周”為頻次發生的特點。

  3. [強制] 應用中的擴充套件日誌(如打點、臨時監控、訪問日誌等)命名方式:appName_logType_logName.log

    • logType: 日誌型別,推薦分類有stats/monitor/visit等;
    • logName: 日誌描述。
    • 優點:通過檔名就可知道日誌檔案屬於什麼應用,什麼型別,什麼目的,也有利於歸類查詢。
    • 正例: :mppserver應用中單獨監控時區轉換異常,如:mppserver_monitor_timeZoneConvert.log
    • 說明: 推薦對日誌進行分類,如將錯誤日誌和業務日誌分開存放,便於開發人員檢視,也便於通過日誌對系統進行及時監控。
  4. [強制] 對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);
  5. [強制] 避免重複列印日誌,浪費磁碟空間,務必在log4j.xml中設定additivity=false

    • 正例:
  6. [強制] 異常資訊應該包括兩類資訊:案發現場資訊和異常堆疊資訊。如果不處理,那麼通過關鍵字throws往上丟擲。

    • 正例:logger.error(各類引數或者物件toString + "_" + e.getMessage(), e);
  7. [推薦] 謹慎地記錄日誌。

    • 生產環境禁止輸出debug日誌;有選擇地輸出info日誌;
    • 如果使用warn來記錄剛上線時的業務行為資訊,一定要注意日誌輸出量的問題,避免把伺服器磁碟撐爆,並記得及時刪除這些觀察日誌。
  8. [推薦] 可以使用warn日誌級別來記錄使用者輸入引數錯誤的情況,避免投訴時無所適從。如非必要,請不在此場景打出error級別,避免頻繁報警。

    • 注意日誌輸出的級別, error級別只記錄系統邏輯出錯、異常級別只記錄系統邏輯出錯、異常。