掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術咨詢/運營咨詢/技術建議/互聯(lián)網(wǎng)交流
聽起來還是有點抽象,能否給點代碼? 剛好有一個 jdoctor 的項目,作者來自Oracle Labs[1] 樣例代碼如下:

創(chuàng)新互聯(lián)公司是一家成都網(wǎng)站設計、成都網(wǎng)站制作,提供網(wǎng)頁設計,網(wǎng)站設計,網(wǎng)站制作,建網(wǎng)站,專業(yè)公司,網(wǎng)站開發(fā)公司,于2013年成立是互聯(lián)行業(yè)建設者,服務者。以提升客戶品牌價值為核心業(yè)務,全程參與項目的網(wǎng)站策劃設計制作,前端開發(fā),后臺程序制作以及后期項目運營并提出專業(yè)建議和思路。
ProblemBuilder.newBuilder(TestProblemId.ERROR1, StandardSeverity.ERROR, "Hawaiian pizza")
.withLongDescription("Pineapple on pizza would put your relationship with folks you respect at risk.")
.withShortDescription("pineapple on pizza isn't allowed")
.because("the Italian cuisine should be respected")
.documentedAt("https://www.bbc.co.uk/bitesize/articles/z2vftrd")
.addSolution(s -> s.withShortDescription("eat pineapple for desert"))
.addSolution(s -> s.withShortDescription("stop adding pineapple to pizza"));
這里的Problem理解為Error沒有問題,核心主要包括以下幾個字段:
有了這些具體的字段后,我們理解起來就方便多啦。
各種錯誤處理上都建議使用錯誤碼,錯誤碼有非常多的優(yōu)勢:唯一性、搜索/統(tǒng)計更方便等,所以我們還是要討論一下錯誤碼的設計。網(wǎng)上也有不少錯誤碼的設計規(guī)范,當然這篇文章也少不了重復造輪子,該設計提供給大家參考,大家自行判斷啊,當然也非常歡迎留言指正。
一個錯誤碼通常包含三個部分:
有了上述的規(guī)范后,讓我們看一下典型的錯誤編碼長什么樣子:
我們采用應用名縮寫, 組件名或者編碼, 狀態(tài)值,然后以中劃線連接起來。中劃線比較方便閱讀,下劃線有時候在顯示的時候理解為空格。同時有了標準的HTTP Status Code支持,不用參考文檔,你都能猜一個八九不離十。 錯誤碼設計千萬不要太復雜,試圖將所有的信息都添加進去,當然信息非常全,但是也增加了開發(fā)者理解和使用成本,這個可能要做一個取舍,當然我也不是說目前這種一鍵三連(打賞、點贊加轉發(fā))的結構就最合理,你也可以自行調整。有沒有做心里研究的同學來說一下,這種三部分組成的方式,是不是最符合人們的認知習慣?如果超過三部分,如4和5,人們能記住和使用的概率是不是就下降的非常多?
還記得前面說的error的context嗎?這里error code其實就是啟動context的作用,如 UIC-LOGIN-404,錯誤發(fā)生在哪里?錯誤碼幫你定位啦。當時代碼想干什么?錯誤碼也說明啦。雖然說錯誤碼不能完全代表錯誤的上下文,但是其承載的信息已經(jīng)足夠我們幫我們了解當時的上下文啦,所以這里error code就是起著context的作用。目前看來至少error code要比ProblemBuilder.newBuilder(TestProblemId.ERROR1, StandardSeverity.ERROR, "Hawaiian pizza") 中的Hawaiian pizza 作為context更具有說服力,也規(guī)范一些。
錯誤碼設計完畢后,我們還不能用錯誤碼+簡短消息方式輸出錯誤,不然就出現(xiàn)類似 ORA-00942: table or view does not exist這種情況,你一定會吐槽:"你為何不告訴哪個表或者view?"。所以我們還需要設計一個message格式,能夠將錯誤的context, description, reason, document link, solutions全部包含進來,這樣對開發(fā)者會比較友好。這里我擬定了一個Message的規(guī)范,當然大家可以發(fā)表自己的意見啊,如下:
long description(short desc): because/reason --- document link -- solutions
解釋一下:
看一個具體的消息格式例子:
APP-100-400=Failed to log in system with email and password(Email login failed): can not find account with email {} --- please refer https://example.com/login/byemail --- Solutions: 1. check your email 2. check your password
上述的APP-100-400的錯誤碼對應的描述基本覆蓋到jdoctor中需要的信息,可以說對一個錯誤的描述應該非常全啦,而且有一定的格式,也方便后續(xù)的日志分析。
有了錯誤碼和message的規(guī)范,接下來我們應該如何保存這些信息呢?如果是Java,是不是要創(chuàng)建對應的ErrorEnum,然后是一些POJO?這里個人建議使用properties文件來保存錯誤碼和message的信息。文件名可以直接為ErrorMessages.properties,當然是在某一package下,文件樣例如下:
### error messages for your App
APP-100-400=Failed to log in system with email and password(Email login failed): can not find account with email {0} --- please refer https://example.com/login/byemail --- Solutions: 1. check your email 2. check your password
APP-100-401=Failed to log in system with phone and pass(Phone login failed): can not find account with phone {0} --- please refer https://example.com/login/byphone --- Solutions: 1. check your phone 2. check your pass code in SMS
為何要選擇properties文件來保存error code和message信息,主要有以下幾個原因:
最后最關鍵的是IDE支持非常友好 , 以Java開發(fā)者使用的IntelliJ IDEA來說,對Properties文件的支持可以說是到了極致,如下:
快速查看:鼠標移上去就可以,按下CMD鼠標移上去也可以, Alt+Space也可以,當然點擊直接定位就更不用說啦。
直接修改message的值:
總之IntellIJ IDEA對properties文件的支持到了極致,我們也沒有理由不考慮開發(fā)者體驗的問題,到處跳來跳去地找錯誤碼,這種傷害程序員開發(fā)體驗的事情不能做。 當然JetBrains的其他IDE,WebStorm等都有對proproperties文件編輯支持。
看起來功能挺酷炫的,是不是這種方式錯誤管理要介入一個開發(fā)包啊?不需要,你只需要10行代碼就搞定,如下:
import org.slf4j.helpers.MessageFormatter;
public class AppErrorMessages {
private static final String BUNDLE_FQN = "app.ErrorMessages";
private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_FQN, new Locale("en", "US"));
public static String message(@PropertyKey(resourceBundle = BUNDLE_FQN) String key, Object... params) {
if (RESOURCE_BUNDLE.containsKey(key)) {
String value = RESOURCE_BUNDLE.getString(key);
final FormattingTuple tuple = MessageFormatter.arrayFormat(value, params);
return key + " - " + tuple.getMessage();
} else {
return MessageFormatter.arrayFormat(key, params).getMessage();
}
}
}
這樣在任何地方如果你要打印錯誤消息的時候,這樣log.info(AppErrorMessages.message("APP-100-400","xxx"));就可以。如果你還有想法和log進行一下Wrapper,如 log.info("APP-100-400","xxx"); ,也沒有問題,樣例代碼如下:
public class ErrorCodeLogger implements Logger {
private Logger delegate;
private static final String BUNDLE_FQN = "app.ErrorMessages";
private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_FQN, new Locale("en", "US"));
public ErrorCodeLogger(Logger delegate) {
this.delegate = delegate;
}
@Override
public void trace(@PropertyKey(resourceBundle = BUNDLE_FQN) String msg) {
delegate.trace(RESOURCE_BUNDLE.getString(msg));
}
}
接下來你就可以在log中直接整合error code,非常便捷。上述代碼我已經(jīng)寫好,你參考文章末尾的項目地址即可。
最終的日志輸出如下:
提醒:這里我們使用了slf4j的MessageFormatter,主要是方便后續(xù)的Slf4j的整合,而且slf4j的MessageFormatter比Java的MessageFormat容錯和性能上更好一些。
大多數(shù)開發(fā)者對HTTP Status Code都比較熟悉,所以看到這些code就大致明白什么意思,當然對應用開發(fā)者也有嚴格的要求,你千萬別將404解釋為內部錯誤,如數(shù)據(jù)庫連接失敗這樣的,逆正常思維的事情不要做。HTTP status code歸類如下,當然你也可以參考一下 HTTP Status Codes Cheat Sheet[2]。
但是Error Status Code不局限在HTTP Status Code,你也可以參考SMTP, POP3等Status Code,此外你也自行可以選擇諸如007,777這樣的編碼,只要能解釋的合理就可以啦。
在日常的生活中,我們會使用一些特殊意義的數(shù)字或者和數(shù)字諧音,以下是一些友情提醒:
這種有特殊意義的數(shù)字或者數(shù)字諧音,如520,886,999,95等,如果能使用的恰當非常方便理解或更友好,如透傳給用戶UIC-REG-200(注冊成功),如果調整為UIC-REG-520可能更溫馨一些??偟膩碚f使用這些數(shù)字要注意場景,當然比較保險的做法就是參考HTTP,SMTP等設計的status code。
就Java和IntelliJ IDEA的支持來看,目前的配合還是比較好的,如i18n,維護成本等,而且這些ErrorMessages.properties也可以提交到中心倉庫進行Error Code集中管理,如果是Java Enum+POJO對i18n和集中管理都比較麻煩,而且代碼量也比較大,你從上述的jdoctor的problem builder的就可以看出。當然在不同的語言中也未必是絕對的,如在Rust中,由于enum的特性比較豐富,所以在Rust下使用enum來實現(xiàn)error code可能是比較好的選擇。
#[derive(Debug)]
enum ErrorMessages {
AppLogin404 {
email: String,
},
AppLogin405(String),
}
impl fmt::Display for ErrorMessages {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// extract enum name parameter
// output message from java-properties
write!(f, "{:?}", self)
}
}
不少錯誤碼設計中會添加錯誤級別,如 RS-001-404-9 這樣,最后一位表示錯誤的嚴重級別。這樣做沒有問題,但是也要考慮現(xiàn)實因素,如下:
如果將錯誤的基本固化到error code中,這個后續(xù)你就沒法調整啦,你如果調整了錯誤級別,那就是可能就是另外一個錯誤碼,給統(tǒng)計和理解都會造成問題。我個人是建議錯誤碼中不要包括嚴重級別這些信息,而是通過外圍的文檔和描述進行說明,當然你也可以通過諸如 log.info , log.error來確定錯誤的級別。
由于IntelliJ IDEA并不支持動態(tài)的properties文件名稱,如果你用動態(tài)的properties文件名稱,就不能進行代碼提示,查找等功能也都不能使用,所以必須是這種 @PropertyKey(resourceBundle = BUNDLE_FQN) 靜態(tài)的properties文件名方式。就一個Java類,你就受累Copy一下這個Java類,畢竟是一次性的工作,當然你想個性化調整代碼也更方便,如和Log4j 2.x或自定也的logging框架整合也簡單些。 日志是項目最基本的需求,所以你創(chuàng)建的項目的時候,就把Error Code對應的代碼添加到項目模板中,這樣項目創(chuàng)建后就自動包含logging和error code的功能。
原文和Reddit上相關的討論也進行了一些整理和說明:
采用error code + 基于properties文件存儲error message,這個設計其實就是一個綜合的取舍。如果IDEA不能很好地支持properties文件,你看到一個Error Code,不能直接定位到錯誤的消息,相反還需要跳轉來跳轉去找對應的消息,那么Enum + POJO可能就是好的選擇。此外error code的設計也非常偏向http status code方案,這個也是主要基于大家對HTTP都非常熟悉,基本上就能猜出大概的意思,相反隨機編碼的數(shù)字就沒有這方法的優(yōu)勢,要去error code中心再去查找一下,無形中也是浪費開發(fā)人員的時間。
最后項目的Demo地址:http://gitlab.alibaba-inc.com/leijuan/java-error-messages-wizard
[1]https://github.com/melix/jdoctor
[2]https://cheatography.com/kstep/cheat-sheets/http-status-codes/
[3]https://www.morling.dev/blog/whats-in-a-good-error-message/

我們在微信上24小時期待你的聲音
解答本文疑問/技術咨詢/運營咨詢/技術建議/互聯(lián)網(wǎng)交流