目錄
一、錯誤碼
二、異常處理
三、日志規約
一、錯誤碼
強制:
1、錯誤碼的制訂原則:快速溯源、溝通標準化。
1)錯誤碼必須能夠快速知曉錯誤來源,可快速判斷是誰的問題。
2)錯誤碼必須能夠清晰地比對(代碼中容易equals)?。
3)錯誤碼有利于團隊快速對錯誤原因達成一致。
2、錯誤碼不體現版本號和錯誤等級信息。
說明:錯誤碼以不斷追加的方式進行兼容。錯誤等級由日志和錯誤碼本身的釋義決定。
3、當全部正常,但不得不填充錯誤碼時,返回五個零(00000)?。
4、錯誤碼為字符串類型,共5位,分為錯誤產生來源、四位數字編號兩部分。
說明:錯誤產生來源分為A、B、C三種,
????????A表示錯誤來源于用戶,例如參數錯誤、用戶安裝版本過低、用戶支付超時等;
????????B表示錯誤來源于當前系統,例如業務邏輯出錯、程序健壯性差等;
????????C表示錯誤來源于第三方服務,例如CDN服務出錯、消息投遞超時等;
四位數字編號從0001到9999,大類之間的步長間距預留100。
5、編號不與公司業務架構和組織架構掛鉤,以先到先得為原則在統一平臺上辦理,一旦審批生效,編號即被永久固定。
6、錯誤碼使用者避免隨意定義新的錯誤碼。
說明:在代碼中使用錯誤碼時,盡可能在原有錯誤碼附表中找到語義相同或者相近的錯誤碼。
7、錯誤碼不能直接輸出給用戶作為提示信息使用。
說明:堆棧、錯誤碼(errorCode)?、錯誤信息(errorMessage)、提示信息(userTip)是一個有效關聯并互相轉義的和諧整體,但請勿越俎代庖。
推薦:
1、errorCode之外的業務獨特信息由errorMessage承載,不要讓errorCode本身涵蓋過多的具體業務屬性。
2、在獲取第三方服務錯誤時,向上拋出允許本系統轉義,由C轉為B,并且在錯誤信息上帶上原有的errorCode。
參考:
1、錯誤碼分為一級宏觀錯誤碼、二級宏觀錯誤碼、三級宏觀錯誤碼。
說明:在無法確定的錯誤場景中,可以直接使用一級宏觀錯誤碼,分別是:A0001(用戶端錯誤)?、B0001(系統執行出錯)?、C0001(調用第三方服務出錯)?。
正例:調用第三方服務出錯是一級,中間件出錯是二級,消息服務出錯是三級。
2、錯誤碼的后三位數字與HTTP狀態碼沒有任何關系。
3、錯誤碼盡量有利于具有不同文化背景的開發者進行交流與代碼協作。
說明:英文單詞形式的錯誤碼不利于非英語母語國家(如阿拉伯語國家、希伯來語國家、俄羅斯語國家等)的開發者之間互相協作。
4、錯誤碼即人性,感性認知+口口相傳,使用純數字編排錯誤碼不利于感性記憶和分類。
二、異常處理
強制:
1、Java類庫中定義的可以通過預檢查方式規避的RuntimeException不應該通過catch的方式處理,如:NullPointerException、IndexOutOfBoundsException等。
說明:無法通過預檢查的異常不在此列,比如當解析字符串形式的數字時,可能存在數字格式錯誤,通過catch NumberFormatException實現。
2、異常被捕獲后不要用來做流程控制和條件控制。
說明:異常設計的初衷是解決程序運行中的各種意外,且異常的處理效率比條件判斷方式要低很多。
3、catch時請分清穩定代碼和非穩定代碼。穩定代碼一般指本機運行且執行結果確定性高的代碼。對于非穩定代碼的catch,盡可能在進行異常類型的區分后,再做對應的異常處理。
說明:對大段代碼進行try-catch,將使程序無法根據不同的異常做出正確的“應激”反應,也不利于定位問題,這是一種不負責任的表現。
正例:在用戶注冊的場景中,如果用戶輸入非法字符,或用戶名稱已存在,或用戶輸入的密碼過于簡單,那么在程序上會作出分門別類的判斷,并提示用戶。
4、捕獲異常是為了處理異常,不要捕獲了卻什么都不處理而拋棄之,如果不想處理它,請將該異常拋給它的調用者。最外層的業務使用者必須處理異常,將其轉化為用戶可以理解的內容。
5、在事務場景中,拋出異常被catch后,如果需要回滾,那么一定要注意手動回滾事務。
6、finally塊必須對資源對象、流對象進行關閉操作,如果有異常就要做try-catch操作。
說明:對于JDK 7及以上版本,可以使用try-with-resources方式。
7、不要在finally塊中使用return。
說明:try塊中的return語句執行成功后,并不馬上返回,而是繼續執行finally塊中的語句,如果此處存在return語句,則在此直接返回,無情地丟棄try塊中的返回點。
8、捕獲異常與拋異常必須完全匹配,或者捕獲異常是拋異常的父類。
9、在調用RPC、二方包或動態生成類的相關方法時,捕獲異常必須使用Throwable類攔截。
說明:通過反射機制調用方法,如果找不到方法,則拋出NoSuchMethod Exception。在什么情況下會拋出NoSuchMethodError呢?二方包在類沖突時,仲裁機制可能導致引入非預期的版本使類的方法簽名不匹配,或者在字節碼修改框架(比如:ASM)動態創建或修改類時,修改了相應的方法簽名。對于這些情況,即使在代碼編譯期是正確的,在代碼運行期也會拋出NoSuchMethodError。
推薦:
1、方法的返回值可以為null,不強制返回空集合或者空對象等,必須添加注釋充分說明在什么情況下會返回null值。此時數據庫id不支持存入負數而拋出異常。
說明:防止產生NPE是調用者的責任。即使被調用方法返回空集合或者空對象,對調用者來說,也并非高枕無憂,必須考慮到遠程調用失敗、序列化失敗、運行時異常等場景返回null值的情況。
2、防止產生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。
????????正例:使用JDK 8的Optional類防止產生NPE。
3、定義時區分unchecked / checked異常,避免直接拋出new RuntimeException(),更不允許拋出Exception或者Throwable,應使用有業務含義的自定義異常。推薦業界已定義過的自定義異常,如:DAOException /ServiceException等。
參考:
1、對于公司外的HTTP/API開放接口,必須使用“errorCode”?;應用內部推薦異常拋出;跨應用間RPC調用優先考慮使用Result方式,封裝isSuccess()方法、?“errorCode”和“errorMessage”?。
說明:關于RPC方法返回方式使用Result方式的理由。
1)使用拋異常返回方式,調用方如果沒有捕獲到,就會產生運行時錯誤。
2)如果不加棧信息,只是new自定義異常,加入自己理解的errorMessage,對于調用端解決問題的幫助不會太多。如果加了棧信息,在頻繁調用出錯的情況下,數據序列化和傳輸的性能損耗也是問題。
2、避免出現重復的代碼(Don't Repeat Yourself)?,即DRY原則。
說明:隨意復制和粘貼代碼,必然導致代碼的重復,當以后需要修改時,需要修改所有的副本,容易遺漏。必要時抽取共性方法或公共類,甚至將代碼組件化。
三、日志規約
強制:
1、應用中不可直接使用日志系統(Log4j、Logback)中的API,而應依賴使用日志框架(SLF4J、JCL--Jakarta Commons Logging)中的API,使用門面模式的日志框架,有利于維護日志并保證各個類的日志處理方式統一。
2、所有日志文件至少保存15天,因為有些異常具備以“周”為頻次發生的特點。對于當天日志,以“應用名.log”保存在“/home/admin/應用名/logs/”目錄下,過往日志格式: {logname}.log.{保存日期},日期格式:yyyy-MM-dd。
3、根據國家法律,網絡運行狀態、網絡安全事件、個人敏感信息操作等相關記錄,留存日志的時間不少于六個月,并且進行網絡多機備份。
4、應用中的擴展日志(如打點、臨時監控、訪問日志等)命名方式:appName_logType_logName.log。logType為日志類型,如stats/monitor/access等;logName為日志描述。這種命名的好處是通過文件名就可以知道日志文件屬于哪個應用,哪種類型,有什么目的,這也有利于歸類查找。
說明:推薦對日志進行分類,如將錯誤日志和業務日志分開存放,既便于開發人員查看,也便于通過日志及時監控系統。
5、當輸出日志時,字符串變量之間的拼接使用占位符的方式。
說明:因為String字符串的拼接會使用StringBuilder的append()方式,所以有一定的性能損耗。使用占位符僅是替換動作,可以有效提升性能。
6、對于trace/debug/info級別的日志輸出,必須進行日志級別的開關判斷。
說明:在debug(參數)的方法體內,雖然當第一行代碼is Disabled(Level.DEBUG_INT)為真時(Slf4j的常見實現Log4j和Logback)會直接return,但是參數可能會進行字符串拼接運算。此外,如果debug(getName())這種參數內有getName()方法調用,則會浪費方法調用的開銷。
7、避免重復打印日志,否則會浪費磁盤空間,務必在日志配置文件中設置additivity=false。
8、在生產環境中禁止直接使用System.out或System. err輸出日志,或使用e.printStackTrace()打印異常堆棧。
說明:只有每次Jboss重啟時,標準日志輸出文件與標準錯誤輸出文件才滾動,如果大量輸出送往這兩個文件,則容易造成文件大小超過操作系統大小限制。
9、異常信息應該包括兩類:案發現場信息和異常堆棧信息。如果不處理,那么通過關鍵字throws往上拋出。
10、打印日志時,禁止直接用JSON工具將對象轉換成String。
說明:如果對象里某些get方法被覆寫,存在拋出異常的情況,則可能會因為打印日志而影響正常的業務流程的執行。
正例:打印日志時,僅打印業務相關屬性值或者調用其對象的toString()方法。
推薦:
1、謹慎地記錄日志。在生產環境中禁止輸出debug日志;有選擇地輸出info日志;如果使用warn記錄剛上線時的業務行為信息,則一定要注意日志輸出量的問題,避免把服務器磁盤撐爆,并及時刪除這些觀察日志。
說明:大量地輸出無效日志,既不利于提升系統性能,也不利于快速定位錯誤點。記錄日志時請思考:這些日志真的有人看嗎?看到這條日志你能做什么?能不能給問題排查帶來好處?
2、可以使用warn日志級別記錄用戶輸入參數錯誤的情況,避免當用戶投訴時無所適從。
說明:如非必要,請不要在此場景中打出error級別,避免頻繁報警。注意日志輸出的級別,error級別只記錄系統邏輯出錯、異常等重要的錯誤信息。
3、盡量用英文描述日志錯誤信息。如果日志中的錯誤信息用英文描述不清楚,可以使用中文描述,否則容易產生歧義。
說明:國際化團隊或海外部署的服務器,由于字符集問題,【強制】使用全英文注釋和描述日志錯誤信息。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ---內容來源《阿里巴巴開發手冊》