1. 程式人生 > >阿里巴巴Java開發手冊部分加註——異常日誌

阿里巴巴Java開發手冊部分加註——異常日誌

二、異常日誌

(一) 異常處理


1.【強制】Java 類庫中定義的可以通過預檢查方式規避的 RuntimeException 異常不應該通過

catch 的方式來處理,比如:NullPointerException,IndexOutOfBoundsException 等等。

說明:無法通過預檢查的異常除外,比如,在解析字串形式的數字時,不得不通過 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 方式。
注:可以簡化try catch finally的板式程式碼

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 方式的理由:

1)使用拋異常返回方式,呼叫方如果沒有捕獲到就會產生執行時錯誤。

2)如果不加棧資訊,只是 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/access 等;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。

正例:<logger name="com.taobao.dubbo.config" additivity="false">

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

正例:logger.error(各類引數或者物件 toString() + "_" + e.getMessage(), e);

7.【推薦】謹慎地記錄日誌。生產環境禁止輸出 debug 日誌;有選擇地輸出 info 日誌;如果使用 warn 來記錄剛上線時的業務行為資訊,一定要注意日誌輸出量的問題,避免把伺服器磁碟撐爆,並記得及時刪除這些觀察日誌。

說明:大量地輸出無效日誌,不利於系統性能提升,也不利於快速定位錯誤點。記錄日誌時請思考:這些日誌真的有人看嗎?看到這條日誌你能做什麼?能不能給問題排查帶來好處?

8.【推薦】可以使用 warn 日誌級別來記錄使用者輸入引數錯誤的情況,避免使用者投訴時,無所適從。如非必要,請不要在此場景打出 error 級別,避免頻繁報警。

說明:注意日誌輸出的級別,error 級別只記錄系統邏輯出錯、異常或者重要的錯誤資訊。

9.【推薦】儘量用英文來描述日誌錯誤資訊,如果日誌中的錯誤資訊用英文描述不清楚的話使用中文描述即可,否則容易產生歧義。國際化團隊或海外部署的伺服器由於字符集問題,【強制】使用全英文來註釋和描述日誌錯誤資訊。