本文從工作中的具體實踐出發,介紹自己對錯誤碼的一些設計思考。下面將從問題背景、需求分析、設計實踐這三個方面來分別闡述。
問題背景
拋開具體的業務處理邏輯,這個問題可以抽象為兩種模式:
報文頭和報文體一致模式
這種模式,是由前端往后台發送請求得到響應,由前端負責封包解包。這里的報頭和報體具有相同的數據組織格式和邏輯層級,其邏輯一致性由業務服務來保證,具體體現如下:
- 如果報頭中的響應碼出錯,那報頭的響應信息包含錯誤描述信息,報文體為空
- 如果報頭中的響應碼正確,那報頭的響應信息為空,報文體是正確的業務數據,且數據格式與報頭一致。
報文頭和報文體不一致的模式
這種模式,常見於在現有服務中新增一個中轉服務,專門用來對接第三方服務。由前端負責對接數據的拆包和解包,中轉后台服務只處理網絡層的收發邏輯,不關心具體數據內容。
由於第三方服務和中轉服務都存在錯誤可能,因此,前端如何解析響應就需要好好設計。
需求分析
從使用者的角度來看,期望能夠從請求響應獲取以下三個接口:
- 該響應是成功還是失敗?
- 如果失敗,失敗的錯誤碼是多少?
- 如果失敗,失敗的錯誤描述信息是什么?
使用者使用接口1來進行業務分支判斷。一般來說,成功則顯示響應數據,失敗則通過接口2和接口3來提示錯誤信息。其中,錯誤碼是便於開發定位原因,錯誤信息是便於最終用戶知曉錯誤情況。
設計實踐
這里以接入第三方服務返回json格式的響應數據來舉例:一般格式如下:
{ // 響應成功
code: 0,
msg: "",
data: [XXX]
}
{ // 響應失敗
code : 1000,
msg: "XXX"
data:{}
}
通過前面的分析以及具體實踐,在與同事討論中,對於接口2存在的必要性上有分歧。為什么呢?因為在現有系統中,報文頭和報文體一致模式和不一致模式都存在,且都存在錯誤的情況,在提示用戶時,在是否需要顯示錯誤碼這一點上,產品和開發意見有差異。
- 對於報文和報體數據一致的場景,
接口2的數據來源唯一,且可以保證准確一致。 - 對於不一致的場景,
接口2的數據來源有多種,具體有以下三種情況:- 中轉層的錯誤碼和錯誤信息
- 第三方服務返回正常,但業務錯誤
- 第三方服務返回異常,這里可能有:格式錯誤、無期望的字段等
對於后者來說,錯誤信息來源也存在上述問題。按照單一職責的原則來設計,會是這樣:
struct ErrorInfo
{
int nErrCode;
string nErrMsg;
}
struct ErrorInfo m_ProxyLayer; // 中轉層錯誤信息
struct ErrorInfo m_ThirdLayer; // 第三方服務層的錯誤信息
bool bParseFlag; // 解析json是否成功
bool bExpectField; // 期望字段是否存在
由於在不同層級的錯誤不會同時發生,且錯誤碼有優先級別,照此設計可使得職責清晰,缺點在於有多種設置錯誤方式,且在獲取錯誤碼和錯誤信息時,要依據錯誤碼的優先級來處理。
個人看法,在該場景下,有兩種妥協方案,在違反單一職責原則的前提下,提供更好的封裝和使用。
方案一:在前端響應時,使用如下數據接口來保存錯誤信息:
struct ErrorInfo
{
int nErrCode;
string nErrMsg;
void setErrorInfo(int nErrorCode, const string& strErrMsg)
{
this->nErrCode = nErrorCode;
this->strErrMsg = strErrMsg;
}
bool IsRspRight(){
return 0 != nErrCode;
}
int GetErrCode(){return nErrCode;}
const string& GetErrMsg(){return nErrMsg;}const
}
使用單一錯誤碼以及錯誤信息來保存不同層級的錯誤信息,與上述職責分離的方案相比,設置和獲取錯誤使用統一接口,簡單直接。因為不同層級的錯誤不會同時發生,即使出現相同的錯誤碼,也只會返回優先級最高的錯誤碼以及錯誤信息。該方案的缺點在於復用錯誤碼以及錯誤信息,上層不能依據錯誤信息做后續的業務判斷,因為它的含義已經不再單一。
方案二:在中轉層處理,針對第三方服務的指令,提供指令適配器,將其轉換為前端可統一解包的格式。好處在前端可按照之前封包解包邏輯進行處理,無需關系后端對接類型,按照中轉層提供的接口傳參即可。缺點在於中轉層承擔對接第三方服務的全部工作以及增加指令適配工作,一旦出現異常,所有依賴該中轉層的指令都會失敗
小結
針對以上場景,提出了職責分離和職責復用兩種處理方式,在此拋磚引玉,大家有什么其他想法,可以在評論區一起討論。
