當(dāng)前位置:首頁 > 公眾號(hào)精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]這篇文章醞釀了很久,一直想寫,卻一直覺得似乎要講的東西有點(diǎn)雜,又不是很容易講清楚,又怕爭(zhēng)議的地方很多,就一拖再拖。但是,每次看到不少遇到跟這個(gè)設(shè)計(jì)相關(guān)導(dǎo)致的問題,又忍不住跟人討論,但又很難一次說清楚,于是總后悔沒有及早把自己的觀點(diǎn)寫成文章。不管怎樣,觀點(diǎn)還是要表達(dá)的,無論對(duì)錯(cuò)。故...


這篇文章醞釀了很久,一直想寫,卻一直覺得似乎要講的東西有點(diǎn)雜,又不是很容易講清楚,又怕爭(zhēng)議的地方很多,就一拖再拖。但是,每次看到不少遇到跟這個(gè)設(shè)計(jì)相關(guān)導(dǎo)致的問題,又忍不住跟人討論,但又很難一次說清楚,于是總后悔沒有及早把自己的觀點(diǎn)寫成文章。不管怎樣,觀點(diǎn)還是要表達(dá)的,無論對(duì)錯(cuò)。



故障的推手——“Result"


先說結(jié)論:接口方法,尤其是對(duì)外HSF(開源版本即dubbo) api,接口異常建議不要使用Result,而應(yīng)該使用異常。阿里內(nèi)部的java編碼,已經(jīng)習(xí)慣性對(duì)外API一股腦兒使用“Result”設(shè)計(jì)——這是導(dǎo)致許多故障的重要原因!


???一個(gè)簡(jiǎn)化的例子


// 用戶查詢的HSF服務(wù)API,使用了Result做為返回結(jié)果public interface UserService { Result getUserById(Long userId);}
// 一段客戶端應(yīng)用facade的調(diào)用示例。讀寫緩存邏輯部分省略,僅做示意public User testGetUser(Long userId) { String userKey = "userId-" userId; // 先查緩存,如果命中則返回緩存中的user // cacheManager.get(123, userKey); // ...
try{ Result result = userService.getUserById(userId); if (result.isSuccess()) { cacheManager.put(123, userKey, result.getData()); return result.getData(); } // 否則緩存空對(duì)象,代表用戶不存在 cacheManager.put(123, userKey, NullCacheObject.getInstance(), 3600); return null; } catch (Exception e) { // TODO log throw new DemoException("getUserById error. userId=" userId, e); }}
上面的代碼很簡(jiǎn)單,客戶端應(yīng)用對(duì)User查詢服務(wù)做了個(gè)緩存。有些同學(xué)可能一眼就看出來,這里隱藏的bug:第10行的“result.isSuccess()”為false的實(shí)際含義是什么?是服務(wù)端系統(tǒng)異常嗎?還是用戶不存在?光看API是很難確定的。不得不去找服務(wù)提供方或文檔確認(rèn)其邏輯,根據(jù)錯(cuò)誤碼進(jìn)行區(qū)分。如果是服務(wù)端系統(tǒng)異常,那么第15行將導(dǎo)致線上bug,因?yàn)楹罄m(xù)1小時(shí)對(duì)該用戶的請(qǐng)求都認(rèn)為用戶不存在了。


???嚴(yán)謹(jǐn)點(diǎn)的寫法
如果要寫正確邏輯,那么代碼可能會(huì)變成這樣:


public User testGetUser(Long userId) { String userKey = "userId-" userId; // 先查緩存,如果命中則返回緩存中的user // cacheManager.get(123, userKey); // ...
try{ Result result = userService.getUserById(userId); if (result.isSuccess()) { cacheManager.put(123, userKey, result.getData()); return result.getData(); } if ("USER_NOT_FOUND".equals(result.getCode())) { // 否則緩存空對(duì)象,代表用戶不存在 cacheManager.put(123, userKey, NullCacheObject.getInstance(), 3600); } else { // 可能是SYSTEM_ERROR、DB_ERROR等一些系統(tǒng)性的異常,TODO log throw new DemoException("getUserById error. userId=" userId ", result=" result); } } catch (DemoException e) { throw e; } catch (Exception e) { // TODO log throw new DemoException("getUserById error. userId=" userId, e); } return null;}

很顯然,代碼變得復(fù)雜起來了,加上對(duì)外部調(diào)用的try catch異常處理,實(shí)際代碼變相當(dāng)復(fù)雜繁瑣。
???不使用Result的例子
public interface UserService { User getUserById(Long userId) throws DemoAppException;}
public User testGetUser(Long userId) { String userKey = "userId-" userId; // 先查緩存,如果命中則返回緩存中的user // cacheManager.get(123, userKey); // ...
try { User user = userService.getUserById(userId); if (user != null) { cacheManager.put(123, userKey, user); return user; } else { // 否則緩存空對(duì)象,代表用戶不存在 cacheManager.put(123, userKey, NullCacheObject.getInstance(), 3600); return null; } } catch (Exception e) { // TODO log throw new DemoException("getUserById error. userId=" userId, e); }}
這樣一看,代碼簡(jiǎn)潔清晰很多,也更符合對(duì)普通API的調(diào)用習(xí)慣。


???使用Result的幾個(gè)問題

  1. 調(diào)用成本高:雖然通過對(duì)依賴的API深入了解異常設(shè)計(jì),可以寫出嚴(yán)謹(jǐn)?shù)拇a以避免出現(xiàn)bug,但是簡(jiǎn)單的邏輯,代碼卻變得復(fù)雜。換言之,調(diào)用的成本變高。但是很可惜,我們忘記判斷而寫成“一個(gè)簡(jiǎn)化的例子”這樣是往往常事。

  2. 無意義錯(cuò)誤碼:SYSTEM_ERROR、DB_ERROR等系統(tǒng)異常的錯(cuò)誤碼,雖然放在Result中了,但是調(diào)用方除了日志和監(jiān)控作用外,業(yè)務(wù)邏輯永遠(yuǎn)不會(huì)關(guān)心,也永遠(yuǎn)處理不了。而些錯(cuò)誤碼的處理分支,實(shí)際與拋異常的處理邏輯一樣。既然如此,為何要將這些錯(cuò)誤碼放在返回值里?


關(guān)于阿里巴巴開發(fā)規(guī)約


我們看《阿里巴巴Java開發(fā)手冊(cè)》的“異常處理”小節(jié)第13條:


【推薦】對(duì)于公司外的http/api開放接口必須使用“錯(cuò)誤碼”;跨應(yīng)用間HSF調(diào)用優(yōu)先考慮使用Result方式,封裝isSuccess()方法、“錯(cuò)誤碼”、“錯(cuò)誤簡(jiǎn)短信息”;而應(yīng)用內(nèi)部推薦異常拋出。


這條推薦非常具有誤導(dǎo)性,在2016年孤盡對(duì)于這條規(guī)范進(jìn)行調(diào)研時(shí)的帖子:《【開發(fā)規(guī)約熱議投票02】HSF服務(wù)接口定義時(shí),是Result isSuccess方式返回,還是是拋異常的方式?》有部分同學(xué)不建議使用Result,但大部分同學(xué)推薦了Result的做法。


???為什么說這條規(guī)約具有誤導(dǎo)性?


因?yàn)檫@個(gè)問題本身沒有講清楚“對(duì)什么東西的處理”要用Result還是異常的方式,即這里沒有講清楚我們要解決的問題是什么。事實(shí)上我們常說的“失敗”,往往混淆了2種含義:


  1. 系統(tǒng)異常:比如網(wǎng)絡(luò)超時(shí)、DB異常、緩存超時(shí)等,調(diào)用方一般不太可能基于這些錯(cuò)誤類型做不同的業(yè)務(wù)邏輯,常用用于日志和監(jiān)控,方便定位排查。

  2. 業(yè)務(wù)狀態(tài):比如業(yè)務(wù)規(guī)則攔截導(dǎo)致的失敗,比如發(fā)權(quán)益時(shí)庫存不足、用戶限領(lǐng)等,為方便后文敘述和理解,暫時(shí)稱為“業(yè)務(wù)失敗”。這類“失敗”,從機(jī)器層面來看,嚴(yán)格來說不能算做是失敗,這只是一種正常的業(yè)務(wù)結(jié)果,這和“調(diào)用成功”這個(gè)業(yè)務(wù)結(jié)果對(duì)系統(tǒng)來說沒有任何區(qū)別,只是一個(gè)業(yè)務(wù)狀態(tài)而已。調(diào)用方往往可能關(guān)心對(duì)應(yīng)的錯(cuò)誤碼,以完成不同的業(yè)務(wù)邏輯。


有經(jīng)驗(yàn)的開發(fā),都會(huì)意識(shí)到這2種含義的區(qū)別,這對(duì)于幫助我們理解接口的異常設(shè)計(jì)非常重要!對(duì)這條開發(fā)規(guī)約而言,如果是第2種,并沒有什么大的問題,但如果是第1種,我則持相反的意見,因?yàn)檫@違背了java語言的基本設(shè)計(jì),不符合java編碼直覺,會(huì)潛移默化造成前面案例所示的理解和使用成本的問題。


談?wù)凧ava接口Result設(shè)計(jì)


???為什么針對(duì)HSF?


當(dāng)我們討論要用Result代替Exception時(shí),經(jīng)常會(huì)以這是HSF接口為由,因?yàn)樾阅荛_銷等等。我們常說HSF這種RPC框架,設(shè)計(jì)的目的就是為了看起來像本地調(diào)用。那么,這個(gè)“看起來像本地調(diào)用”到底指的是哪方面像呢?顯然,編碼時(shí)像,運(yùn)行時(shí)不像。所以我們寫調(diào)用HSF接口的代碼時(shí),感覺像在調(diào)用本地方法,那么我們的編碼直覺和習(xí)慣也都應(yīng)該是符合java的規(guī)范的。因此,至少有幾點(diǎn)理由,對(duì)于系統(tǒng)異常,我們的HSF接口更應(yīng)該使用Exception,而非Result的方式:


  1. 只有同樣遵循本地方法調(diào)用的設(shè)計(jì),來設(shè)計(jì)HSF的api,才能更好做到“像本地調(diào)用一樣”,更符合HSF設(shè)計(jì)的初衷。

  2. HSF接口是往往用于對(duì)外部團(tuán)隊(duì)提供服務(wù),更應(yīng)該遵循java語法的設(shè)計(jì),提供清晰的接口語義,降低調(diào)用方的使用成本,減少出bug的概率。

  3. Result并無統(tǒng)一規(guī)范,而Exception則是語言標(biāo)準(zhǔn),有利于中間件、框架代碼的監(jiān)控發(fā)現(xiàn)和異常重試等邏輯生效。


當(dāng)然,由于“運(yùn)行時(shí)不像”,對(duì)于HSF封裝帶來的抽象泄露,我們?cè)谑褂卯惓r(shí),需要關(guān)注幾點(diǎn)問題


  1. 異常要在接口顯式聲明,否則客戶端可能會(huì)反序列化失敗。

  2. 盡可能不帶原始堆棧,否則客戶端也可能反序列化失敗,或者堆棧過大導(dǎo)致性能問題??梢钥紤]異常中定義錯(cuò)誤碼以方便定位問題。


???結(jié)論


無論是HSF接口,還是內(nèi)部的API,都應(yīng)該遵循java語言的編碼直覺和習(xí)慣,業(yè)務(wù)結(jié)果(無論成功還是失?。┒紤?yīng)該通過返回值返回,而系統(tǒng)異常,則應(yīng)該使用拋出Exception的方式來實(shí)現(xiàn)。


關(guān)于Checked?Exception


講到這里,我們發(fā)現(xiàn),java的Checked Exception的設(shè)計(jì),作用上和反映業(yè)務(wù)失敗的Result很像。Result是強(qiáng)制調(diào)用方進(jìn)行判斷和識(shí)別,并根據(jù)不同的錯(cuò)誤碼進(jìn)行判斷和處理。而Checked Exception也是強(qiáng)制調(diào)用方進(jìn)行處理,并且可能要對(duì)不同的異常做不同的處理。但是,基于前面的結(jié)論,業(yè)務(wù)失敗應(yīng)該通過返回值來表達(dá),而不是異常;而異常是不應(yīng)該用于做業(yè)務(wù)邏輯判斷的,那么java的Checked Exception就變成奇怪的存在了。這里我明確我的觀點(diǎn),我們應(yīng)該盡可能不使用Checked?Exception。另外,《Thinking in Java》的作者 Bruce Eckel就曾經(jīng)公開表示,Java語言中的Checked Exception是一個(gè)錯(cuò)誤的決定,Java應(yīng)該移除它。C#之父Anders Hejlsberg也認(rèn)同這個(gè)觀點(diǎn),因此C#中是沒有Checked Exception的。


Reselt 的實(shí)質(zhì)是什么?


談?wù)凧ava接口Result設(shè)計(jì)


我們看看一個(gè)java方法的簽名(省略修飾符部分):


  1. 方法名:用于表達(dá)這個(gè)方法的功能

  2. 參數(shù):方法的輸入

  3. 返回值類型:方法的輸出

  4. 異常:方法中意外出現(xiàn)的錯(cuò)誤


所以,返回值和方法功能必須是配套的,返回值類型,就是這個(gè)方法的功能執(zhí)行結(jié)果的準(zhǔn)確表達(dá),即返回值必須正好就是當(dāng)前這個(gè)方法要做的事情的結(jié)果,必須滿足這個(gè)方法語義,而不應(yīng)該有超出這個(gè)語義外的東西存在。而異常,所說的“意外”,則是指超出這個(gè)方法語義之外的部分。這幾句話有點(diǎn)拗口,舉個(gè)例子來說,上面這個(gè)用戶接口,語義就是要通過用戶id查詢用戶,那么當(dāng)服務(wù)端發(fā)生DB超時(shí)錯(cuò)誤時(shí),對(duì)于“通過用戶id查詢用戶”這個(gè)語義來說,DB超時(shí)錯(cuò)誤”沒有任何意義,使用異常是恰好合適的,如果我們把這個(gè)錯(cuò)誤做為錯(cuò)誤碼放在返回值的Result里,那么就是增加了這個(gè)方法的使用成本。


???Result的由來


到底為什么會(huì)有“Result”這樣的東西誕生呢?如果設(shè)計(jì)的方法返回值是Result類型,那么它必須能準(zhǔn)確反應(yīng)這個(gè)方法調(diào)用的結(jié)果。實(shí)際上,以上面的例子為例,這個(gè)時(shí)候的Result就是User類本身,User.status相當(dāng)于Result.code。這聽起來可能有點(diǎn)和直覺不符,這是為什么?


public class UserRegisterResult { private String errorCode; private String errorMsg; private Long userId; // ...
public boolean isSuccess() { return errorCode == null; } // ...}
UserRegisterResult registerUser(User user) throws DemoAppException;

我們?cè)賮砜纯瓷厦孢@個(gè)“注冊(cè)用戶”的方法聲明,會(huì)發(fā)現(xiàn),這個(gè)方法定義一個(gè)Result顯得很合適。這是因?yàn)榍耙粋€(gè)例子,我們的方法是一個(gè)查詢方法,返回值剛好可以用領(lǐng)域?qū)ο箢愋捅旧恚@個(gè)“注冊(cè)用戶”的方法,顯然沒有現(xiàn)成合適的類型可以使用,所以就需要定義一個(gè)新的類型來表達(dá)方法的執(zhí)行結(jié)果??吹竭@里,我們會(huì)以為,對(duì)于“寫”與“讀”類型的方法有所差異,但實(shí)際上,對(duì)于java語言或者機(jī)器來說,并無二致,第二個(gè)方法UserRegisterResult的和第一個(gè)方法的User是同等地位。所以,最重要的還是一點(diǎn):需要有一個(gè)合適的類型,做為返回值,用于準(zhǔn)確表達(dá)方法執(zhí)行的功能結(jié)果。而偏“寫”類型,或者帶業(yè)務(wù)校驗(yàn)的讀接口,往往因?yàn)闆]有現(xiàn)成的類型可用,為了方便,常常會(huì)使用Result來代替。


???是否有必要統(tǒng)一Result?


講到這里,想想,當(dāng)我們這種“需要Result”的方法有多個(gè)時(shí),我們會(huì)說“我需要一個(gè)統(tǒng)一的Result類”時(shí),實(shí)際上說的什么呢?


  1. 我希望各種接口方法都統(tǒng)一同樣的Result,方便使用

  2. 我希望有個(gè)類復(fù)用errorCode、errorMsg以及相關(guān)的getter/setter等代碼


顯然,第1點(diǎn)理由經(jīng)不起推敲,為何“統(tǒng)一就方便使用”了?如果各種方法返回類型都一樣,那就違背了“返回值要和方法功能配套”的結(jié)論,也不符合高內(nèi)聚的設(shè)計(jì)原則。恰相反,返回值越是設(shè)計(jì)得專用,對(duì)調(diào)用方來說理解和使用成本越低。所以,我們實(shí)際想要的,僅僅是如何“偷懶”,也就是第2點(diǎn)理由。所以我們真正要做的是,只是在當(dāng)前領(lǐng)域范圍內(nèi),如何既滿足讓每個(gè)方法返回值專用以便使用,同時(shí)又可以偷懶復(fù)用部分代碼即可。因此,絕不必要求大家都統(tǒng)一使用同一個(gè)Result類型。

接口返回設(shè)計(jì)建議


根據(jù)前文的結(jié)論,我們知道,對(duì)于接口方法的返回值和異常處理,最重要的是需要遵循方法的語義進(jìn)行設(shè)計(jì)。以下是我梳理的一些設(shè)計(jì)上的原則和建議。


???對(duì)響應(yīng)合理分類


接口響應(yīng)按有業(yè)務(wù)結(jié)果和未知業(yè)務(wù)結(jié)果分類,業(yè)務(wù)結(jié)果不管是業(yè)務(wù)成功還是業(yè)務(wù)規(guī)則導(dǎo)致的失敗,都通過返回值返回;未知結(jié)果一般是系統(tǒng)性的異常導(dǎo)致,不要通過返回值錯(cuò)誤碼表達(dá),而是通過拋出異常來表達(dá)。這里最關(guān)鍵一點(diǎn),就是如何理解和區(qū)分某個(gè)“失敗”是屬于業(yè)務(wù)失敗,還是屬于系統(tǒng)異常。由于有時(shí)候這個(gè)區(qū)分并不是很容易,我們可以有一個(gè)比較簡(jiǎn)單的判斷標(biāo)準(zhǔn)來確定:


  1. 如果一個(gè)錯(cuò)誤,調(diào)用方只能通過人工介入的方式才能恢復(fù),比如修改代碼、改配置,或數(shù)據(jù)訂正等處理,則必然屬于異常

  2. 如果調(diào)用方無法使用代碼邏輯處理消化使得自動(dòng)恢復(fù),而是只能通過重試的方式,依賴下游的恢復(fù)才能恢復(fù),則屬于異常


???找到合適的場(chǎng)景


普通查詢接口,如無必要,不要使用Result包裝返回值??梢院?jiǎn)單分為3類做為參考:
  • 普通讀接口


查詢結(jié)果即是領(lǐng)域?qū)ο?,無其他業(yè)務(wù)規(guī)則導(dǎo)致的失?。航ㄗh直接用領(lǐng)域?qū)ο箢愋妥鰹榉祷刂?。如?/span>
User getUserById(Long userId) throws DemoAppException;
  • 寫接口


或者帶業(yè)務(wù)規(guī)則的讀接口:
  1. 理想情況是專門封裝一個(gè)返回值類,以降低調(diào)用方的使用成本。

  2. 可考慮將返回值類繼承Result,以復(fù)用errorCode和errorMsg等代碼,減輕開發(fā)工作量。但注意這不是必要的。

  3. 將本方法的錯(cuò)誤碼,直接定義到這個(gè)返回值類上(高內(nèi)聚原則)。

  4. 若有多個(gè)方法有共同的錯(cuò)誤碼,可以考慮通過將這部分錯(cuò)誤碼定義到一個(gè)Interface中,然后實(shí)現(xiàn)該接口。

// UserRegisterResult、UserUpdateResult可以繼承Result類,減少工作量,但調(diào)用方不需要感知Result類的存在UserRegisterResult registerUser(User user) throws DemoAppException;
UserUpdateResult updateUser(User user) throws DemoAppException;談?wù)凧ava接口Result設(shè)計(jì)
  • 帶業(yè)務(wù)規(guī)則的的領(lǐng)域?qū)ο笞x接口


完全遵循上面第2點(diǎn),會(huì)給方法提供者帶來一定的開發(fā)成本,權(quán)衡情況下可以考慮,套R(shí)esult包裝領(lǐng)域?qū)ο笞鰹榉祷刂?。注意,?duì)外不建議,可考慮用于內(nèi)部方法。如下接口,“沒有權(quán)限”是一個(gè)正常的業(yè)務(wù)失敗,調(diào)用方可能會(huì)判斷并做一定的業(yè)務(wù)邏輯處理:


// 查詢有效用戶,如果用戶存在但狀態(tài)非有效狀態(tài)則返回“用戶狀態(tài)錯(cuò)誤”的錯(cuò)誤碼,如果不存在則返回nullResult getEffectiveUserWithStatusCheck(Long userId) throws DemoAppException;

???內(nèi)外部區(qū)分


對(duì)外接口,尤其是HSF,由于變更成本高,更要遵循前面的原則;內(nèi)部方法,方法眾多,如果完全遵循需要編碼成本,這里需要做權(quán)衡,根據(jù)代碼規(guī)模和發(fā)展階段不斷重構(gòu)和調(diào)整即可。


???避免直接包裝原生類型


我們對(duì)外的接口,返回值要避免出現(xiàn)直接使用Result包裝一個(gè)原生類型。比如:


Result registerUser(User user) throws DemoAppException;

這樣設(shè)計(jì)導(dǎo)致的結(jié)果是,擴(kuò)展性很差。如果registerUser方法需要增加返回除了userId以外的其他字段時(shí),就面臨幾個(gè)選擇:


  1. 讓Result支持?jǐn)U展參數(shù),通過map來傳遞額外字段:可讀性和使用成本很高

  2. 開發(fā)一個(gè)新的registerUser方法:顯然,成本很高


???避免所有錯(cuò)誤碼定義在一個(gè)類中


有人建議,做一個(gè)全局的錯(cuò)誤碼定義,以做統(tǒng)一,方便排查和定位。但這樣做真的方便嗎?這樣做實(shí)際上有幾個(gè)問題:


  1. 完全違背了高內(nèi)聚、低耦合的設(shè)計(jì)原則。這個(gè)“統(tǒng)一的定義”將與各個(gè)域都有耦合,同時(shí)對(duì)于某單個(gè)接口而言,則不夠內(nèi)聚。

  2. 這個(gè)統(tǒng)一定義的錯(cuò)誤碼,一定會(huì)爆炸式增長(zhǎng),即便我們對(duì)其進(jìn)行分類(非常依賴人的經(jīng)驗(yàn)),遲早也會(huì)變得難以維護(hù)和理解。

  3. 不要將系統(tǒng)異常類的錯(cuò)誤碼和業(yè)務(wù)失敗錯(cuò)誤碼放在一起,這點(diǎn)其實(shí)和方法響應(yīng)分類設(shè)計(jì)是一回事。


我們?cè)谠O(shè)計(jì)拉菲2權(quán)益平臺(tái)的錯(cuò)誤碼時(shí),就犯了這樣的錯(cuò)誤。現(xiàn)在這個(gè)“統(tǒng)一的”錯(cuò)誤碼已經(jīng)超過400個(gè),揉合了管理域、投發(fā)放域、離線域等各種不同域的業(yè)務(wù)失敗、系統(tǒng)異常的錯(cuò)誤碼,不要說調(diào)用方,即便我們自己,也梳理不清楚了。而實(shí)際上,每個(gè)域、每個(gè)方法自己的業(yè)務(wù)失敗是非常有限的,它的增長(zhǎng)一定是隨著業(yè)務(wù)需求本身的變化而增長(zhǎng)的?,F(xiàn)在如果有個(gè)業(yè)務(wù)方來問我,拉菲2的發(fā)放接口,有哪些錯(cuò)誤碼(這問的實(shí)際是業(yè)務(wù)失敗,他也只關(guān)心業(yè)務(wù)失?。?guī)缀蹼y以回答。很可惜,這塊目前即便重構(gòu),難度也很大。


???異常處理機(jī)制


  • 異常錯(cuò)誤碼


前面我們講到,即便是拋異常的形式,我們也可以為我們的異常類設(shè)計(jì)錯(cuò)誤碼,異常錯(cuò)誤碼的增加會(huì)很快,往往也和當(dāng)前業(yè)務(wù)語義無關(guān),因此千萬不要和業(yè)務(wù)失敗的錯(cuò)誤碼定義在一起。異常內(nèi)的錯(cuò)誤碼主要用于日志、監(jiān)控等,核心原則就是,要方便定位問題。


  • 避免層層try?catch

到處充滿異常處理的代碼,會(huì)導(dǎo)致整個(gè)程序可讀性變差,寫起來也非常繁瑣,可以遵循一定的原則:


  1. 在原始發(fā)生錯(cuò)誤的地方try?catch,比如調(diào)用HSF接口的Facade層代碼,主要目的是為了記錄原始的錯(cuò)誤以及出入?yún)?,方便定位問題,一般會(huì)打日志,并轉(zhuǎn)換成本應(yīng)用的異常類上拋

  2. 在應(yīng)用的最頂層catch異常,打印統(tǒng)一日志,并根據(jù)“為什么針對(duì)HSF?”小節(jié)中的建議,處理成合適的異常后再拋出。對(duì)于HSF接口,可以直接實(shí)現(xiàn)HSF的“ServerFilter”來統(tǒng)一在框架層面處理。

  3. 中間層的代碼,不必再層層catch,比如domain層,可以讓代碼邏輯更加清晰。


  • 參數(shù)錯(cuò)誤


拋異常的場(chǎng)景,除了前面說的系統(tǒng)性異常外,參數(shù)錯(cuò)誤也推薦使用異常。原因如下:


  1. 參數(shù)正確一般是我們當(dāng)前上下文執(zhí)行的前提條件,我們一般可以使用assert來保證其正確。即我們的后續(xù)邏輯是認(rèn)為,當(dāng)前的參數(shù)是不可能錯(cuò)誤的,我們沒必要為此寫過多繁瑣的防御性代碼。

  2. 一旦發(fā)生參數(shù)錯(cuò)誤,則一定是調(diào)用方有代碼bug,或者配置bug,應(yīng)該通過拋出異常的方式,充分提前在開發(fā)或測(cè)試階段暴露。

  3. 參數(shù)錯(cuò)誤對(duì)調(diào)用方來說,是無法處理的,程序不可能自動(dòng)恢復(fù),一定是會(huì)需要人工介入才可能恢復(fù),調(diào)用方不可能會(huì)“判斷如果是xx參數(shù)錯(cuò)誤,我就做某個(gè)業(yè)務(wù)邏輯”這樣的代碼,因此通過返回值定義參數(shù)錯(cuò)誤碼沒有意義。


  • 系統(tǒng)異常和業(yè)務(wù)結(jié)果轉(zhuǎn)換


系統(tǒng)性異常并非一定是異常,因?yàn)橛行涌赡苡心芰μ幚砟承┊惓?,比如?duì)于弱依賴的接口,異常是可以吞掉,轉(zhuǎn)換成一個(gè)業(yè)務(wù)結(jié)果;相反,有些接口返回的一些業(yè)務(wù)失敗,但調(diào)用方認(rèn)為該業(yè)務(wù)失敗不可能出現(xiàn),出現(xiàn)也無法處理,那么這一層可以將其轉(zhuǎn)換成異常。


結(jié)尾


前面講了接口的響應(yīng),包括返回值Result和異常拋出的設(shè)計(jì),有很多結(jié)論是與現(xiàn)在公司內(nèi)部大家常見做法是不同的,這也是我為什么特別想要表達(dá)的,有可能正是日常我們的這些習(xí)以為常做法,才導(dǎo)致了團(tuán)隊(duì)間接口依賴調(diào)用的成本提高,也是導(dǎo)致故障的一個(gè)很重要原因。當(dāng)然,我相信,我的觀點(diǎn)也不一定都是對(duì)的,很多同學(xué)并不一定同意上面所有的結(jié)論,所以,歡迎大家在文章下面討論!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉