軟件強(qiáng)健度等級劃分及異常處理的重構(gòu)

異常處理是我們?nèi)粘i_發(fā)中關(guān)注比較少的一塊筋讨,雖然很多時候并不起眼谆棺,但是如果處理不當(dāng)叹螟,很容易使精心設(shè)計(jì)的程序變得不堪一擊魄宏。通過學(xué)習(xí)軟件強(qiáng)健度等級劃分的概念及常用的異常處理方法秸侣,能讓我們可以根據(jù)用戶不同的需求實(shí)現(xiàn)不同程度的異常處理,使得系統(tǒng)結(jié)構(gòu)更清晰宠互,代碼更加簡潔味榛,軟件功能更加健壯。

寫在前面

這篇文章探討的主題是異常處理中的等級劃分及異常處理的重構(gòu)方法予跌,具體參考了陳建村所著的《笑談軟件工程:異常處理的設(shè)計(jì)與重構(gòu)》搏色,同時也借鑒了《clean code》對異常處理的部分內(nèi)容。我對書中體會較深的部分列舉出來匕得,算是讀書筆記與體會继榆。

為什么要談?wù)劗惓L幚?/h2>

作為一名軟件開發(fā)人員,從小到大你可能學(xué)過各式各樣的軟件設(shè)計(jì)與方法汁掠,從最基礎(chǔ)的程序語言略吨、數(shù)據(jù)結(jié)構(gòu)與算法,到面向?qū)ο蠓治雠c設(shè)計(jì)考阱、設(shè)計(jì)模式翠忠、軟件架構(gòu)以及各種敏捷開發(fā)實(shí)踐,包含自動化測試乞榨、測試驅(qū)動開發(fā)秽之、持續(xù)集成和敏捷設(shè)計(jì)原則等当娱。以上,所有的“大師”費(fèi)盡心力考榨,都在告訴你“如何設(shè)計(jì)軟件的光明面”跨细。
但是,世界是對立的河质,有光明就有黑暗冀惭,黑暗面就是“異常行為”(abnormal behavior),軟件設(shè)計(jì)忽略任何一方掀鹅,都可能讓原本精心規(guī)劃的設(shè)計(jì)變得不堪一擊散休。

強(qiáng)健度等級與異常處理策略

這里定義了軟件的強(qiáng)健度等級,總共分為四個等級乐尊。

等級0:未定義

未定義(undefined)表示當(dāng)某個服務(wù)發(fā)生錯誤的時候戚丸,可能會讓調(diào)用者指導(dǎo)錯誤發(fā)生,也有可能會假裝沒事扔嵌。也就是說限府,使用該服務(wù)的人無法確切知道它是否成功達(dá)成任務(wù)。當(dāng)錯誤發(fā)生的時候对人,服務(wù)處于不明或是錯誤狀態(tài)谣殊。異常發(fā)生時系統(tǒng)可能會終止,也可能繼續(xù)執(zhí)行牺弄。

等級1:錯誤報(bào)告

錯誤報(bào)告(error-reorting)代表當(dāng)某個服務(wù)發(fā)生錯誤的時候姻几,一定要讓調(diào)用者知道,絕對不能假裝沒事势告。要達(dá)到錯誤報(bào)告強(qiáng)健度等級很簡單蛇捌,就是把所有的異常都往外丟,然后在主程序(整個系統(tǒng)最外層的那個程序)捕捉所有的異常并報(bào)告給使用者或開發(fā)人員知道咱台。錯誤報(bào)告又稱為“早死早投胎”络拌。

等級2:狀態(tài)恢復(fù)

狀態(tài)恢復(fù)除了要求等級1錯誤報(bào)告以外,還要求當(dāng)錯誤發(fā)生之后回溺,服務(wù)必須保證系統(tǒng)仍然處于正確狀態(tài)春贸。由于整個系統(tǒng)的狀態(tài)還是正確的,因此遗遵,當(dāng)異常發(fā)生之后萍恕,系統(tǒng)仍可以執(zhí)行。
要達(dá)到這個等級车要,必須要多做兩件事情允粤。首先是錯誤處理(error handling),讓系統(tǒng)恢復(fù)到一個正確的狀態(tài)。假設(shè)有一個服務(wù)修改了數(shù)據(jù)庫內(nèi)容类垫,當(dāng)異常發(fā)生時就要執(zhí)行回滾(roolback)動作司光。其次是釋放資源(cleanup)。例如把要來的內(nèi)存悉患、文件残家、數(shù)據(jù)庫聯(lián)機(jī)等資源釋放。狀態(tài)恢復(fù)又稱為弱容錯(weakly tolerant)购撼。

等級3:行為恢復(fù)

這個等級核心就是服務(wù)的使命必達(dá)跪削。因此,當(dāng)某個服務(wù)執(zhí)行失敗的時候迂求,便要想辦法排除困難,總之就是要達(dá)成原本被賦予的任務(wù)晃跺。當(dāng)發(fā)生錯誤的時候揩局,除了達(dá)成等級2的狀態(tài)恢復(fù)之外,還需要“想其他方法來達(dá)成原本的任務(wù)”掀虎,如重試設(shè)計(jì)多樣性凌盯、功能多樣性數(shù)據(jù)多樣性烹玉、時序多樣性等設(shè)計(jì)技巧驰怎,嘗試?yán)^續(xù)提供服務(wù)。

強(qiáng)健度等級的推廣

  • 首先二打,在團(tuán)隊(duì)中對團(tuán)隊(duì)成員倡導(dǎo)強(qiáng)健度等級觀念县忌。沒錯,只需要花一兩個小時继效,因?yàn)橛^念本身很簡單症杏,不需要長時間訓(xùn)練便可了解。
  • 在沒有特殊情況下瑞信,默認(rèn)所有函數(shù)一定達(dá)到強(qiáng)健度等級1厉颤。乍看起來,等級1好像“很不負(fù)責(zé)任”把所有異常都往外丟凡简,但是如此一來逼友,反而可在開發(fā)階段及時發(fā)現(xiàn)問題并加以修復(fù)。將問題暴露之后秤涩,便有足夠的情境可以決定該異常的處理方式帜乞,是否將等級1升級到更高的等級。
  • 對于特定函數(shù)而言(如數(shù)據(jù)庫處理)溉仑,因?yàn)閿?shù)據(jù)庫錯誤會給使用者造成很大的困擾挖函,加上數(shù)據(jù)庫本身有事務(wù)功能,因此默認(rèn)應(yīng)該達(dá)到強(qiáng)健度2等級。
  • 除非客戶特別要求或是不達(dá)到等級3會使系統(tǒng)變得很難用(例如網(wǎng)絡(luò)數(shù)據(jù)傳遞怨喘,如果不能自動保證數(shù)據(jù)可以完整無誤地傳遞到遠(yuǎn)程津畸,系統(tǒng)將變得很難用),否則必怜,不會特別要求函數(shù)做到行為恢復(fù)肉拓。

異常處理壞味道與重構(gòu)方案

  1. 用異常代替錯誤碼
// 壞味道
public synchronized int withdraw(int amount){
  if(amount > this.balance){
    return -1;
  } else {
    this.balance = this.balance - amount;
    return this.balance;
  }
}
// 重構(gòu)后代碼
public synchronized int withdraw(int amount) throws NotEnoughMoneyException {
  if(amount > this.balance){
    throw new NotEnoughMoneyException();
  }
  this.balance = this.balance - amount;
  return this.balance;
}

動機(jī):以傳回值來代表錯誤狀況,會讓軟件組件連強(qiáng)健度等級1(錯誤報(bào)告)的標(biāo)準(zhǔn)都達(dá)不到梳庆,因?yàn)檎{(diào)用者通常傾向忽略傳回值得檢查暖途,所以也忽略了錯誤狀況。因此膏执,若采用異常來取代錯誤碼驻售,可以使軟件達(dá)到強(qiáng)健度1。

  1. 以未查異常代替忽略已查異常/空的處理程序
// 壞味道
catch(IOException e){
  /* ignoring the exception */
  e.printStackTrace();
}
// 重構(gòu)后代碼
catch(IOException e){
  throw new UnhandledException(e);
}

動機(jī):常用的IDE如eclipse對于Checked異常的處理就是選擇忽略:

catch(IOException e) {
  /* TODO */
  e.printStackTrace();
};

雖然寫了TODO注釋來提醒自己要“抽空”回來再續(xù)這段孽緣更米,但是注釋本身沒有什么約束力欺栗,絕大多數(shù)的時候會選擇遺忘,連強(qiáng)健度1的要求都達(dá)不到征峦。因此迟几,與其捕捉這一類已查異常并忽略它,不如在捕獲后轉(zhuǎn)拋一個未查異常栏笆。這時候类腮,你就可以專心處理正常邏輯,而暫時不被這一類已查異常干擾蛉加。

  1. 使用最外層try語句避免意外終止
// 壞味道
static public void main(String[] args){
  /*
  * 做一大堆事情的主程序
  */
}
// 重構(gòu)后代碼
try{
  /*
  * 做一大堆事情的主程序
  */
} catch(Throwable e){
  logger.log(e);
  dialog.show(Dialog.CRITICAL, e.getMessage());
}

動機(jī):按照強(qiáng)健度1要求蚜枢,所有異常都往外丟,未被捕捉的異常最終都會傳遞到主程序(或線程)上七婴。要是主程序也沒有捕捉這些異常祟偷,整個應(yīng)用程序就會被迫終止,這就是大家非常熟悉的老朋友“程序當(dāng)?shù)簟薄?br> 因此打厘,為了避免應(yīng)用程序不預(yù)期地終止修肠,將最外層程序代碼以一個try語句包住,捕捉所有異常類户盯,在頁面上顯示清楚且容易理解的錯誤信息嵌施,并視需要記錄詳細(xì)出錯信息到日志文件中,最后結(jié)束應(yīng)用程序的執(zhí)行莽鸭,讓它“死得好看一點(diǎn)”吗伤。

  1. 以函數(shù)取代嵌套的try語句
// 壞味道
finally{
  try{
    if(in != null) in.close();
  } catch(IOException e){
    // log the exception
  }
}
// 重構(gòu)后代碼
finally {
  cleanup(in);
}

動機(jī):嵌套的循環(huán)使得代碼結(jié)構(gòu)復(fù)雜,難于理解與維護(hù)硫眨。

  1. 引入Checkpoint類
// 原代碼
public void moveFiles(String srcFolder, String destFolder) throws IOException {
  try {
    // 復(fù)制srcFolder 所有文件到 destFolder
    // 可能發(fā)生 IOException
  } finally {
    // 釋放資源
  }
}
// 重構(gòu)后代碼
public void moveFiles(String srcFolder, String destFolder) throws IOException {
  FolderCheckpoint fscp = null;
  try {
    fscp = new FolderCheckpoint();
    fscp.establish(srcFolder);
    // 復(fù)制 srcFolder 所有檔案到 destFolder足淆,
    // 可能會發(fā)生 IOException
  } catch(Exception e) {
    fscp.restore();
    throw e;
  } finally {
    fscp.drop();
    // 釋放資源
  }
}

動機(jī):為了達(dá)到強(qiáng)健度2的要求,在程序發(fā)生異常的時候能使系統(tǒng)狀態(tài)恢復(fù)到正常的狀態(tài)并持續(xù)運(yùn)作下去,可使用checkpoint方法巧号。checkpoint就是“快照”(snapshot)的觀念族奢,一般有三個基本函數(shù),分別是產(chǎn)生檢查點(diǎn)establish丹鸿,恢復(fù)數(shù)據(jù)的restore以及丟棄檢查點(diǎn)的drop越走。

  1. 引入多才多藝的try塊
// 壞味道
public User readUser(String name) throws ReadUserException {
  try {
  } catch(Exception e){
    try {
      return readFromLDAP(name);  
    } catch(IOException ex) {
      throw new ReadUserException(ex);  
    }
  }
}
// 重構(gòu)后代碼
public User readUser(String name) throws ReadUserException {
  final int maxAttempt = 3;
  int attempt = 1;
  while(true) {
    try {
      if(attempt <= 2) return readFromDatabase(name);
      else return readFromLDAP(name);  
    }  catch(Exception e) {
      if(++attempt > maxAttempt)
        throw new ReadUserExcetption(e);
    }
  }
}

動機(jī):為了達(dá)到強(qiáng)健度3的要求,重試也是一種很常見也很有用的異常處理策略靠欢。但很多人將catch塊當(dāng)成try塊的“備胎”廊敌,等同于在catch塊中執(zhí)行重試。這種做法的主要缺點(diǎn)在于“只能重試一次”门怪,如果要多次重試骡澈,勢必會寫出具備嵌套try語句(Nested Try Statement)的程序,者又是另外一個異常處理壞味道掷空。
因此秧廉,重新思考try塊與catch塊的責(zé)任分工,讓try塊負(fù)責(zé)實(shí)現(xiàn)程序正常邏輯(包含主要方案與替代方案)拣帽,將“備胎(替代方案)”從catch塊移到try塊,讓catch塊負(fù)責(zé)控制重試終止條件與錯誤報(bào)告嚼锄。

總結(jié)

市面上關(guān)于異常處理的書籍不多减拭,《異常處理的設(shè)計(jì)與重構(gòu)》算是鳳毛麟角中的一本。除了上面講到的強(qiáng)健度等級劃分和異常處理重構(gòu)区丑,書中還講了異常處理的基本觀念及java的異常處理機(jī)制等內(nèi)容拧粪。這里我對書中最實(shí)用的章節(jié)進(jìn)行了整理與歸納,同學(xué)可根據(jù)興趣閱讀其他章節(jié)沧侥,會對上述異常重構(gòu)的方法有更加深刻的認(rèn)識可霎。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市宴杀,隨后出現(xiàn)的幾起案子癣朗,更是在濱河造成了極大的恐慌,老刑警劉巖旺罢,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旷余,死亡現(xiàn)場離奇詭異,居然都是意外死亡扁达,警方通過查閱死者的電腦和手機(jī)正卧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跪解,“玉大人炉旷,你說我怎么就攤上這事。” “怎么了窘行?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵饥追,是天一觀的道長。 經(jīng)常有香客問我抽高,道長判耕,這世上最難降的妖魔是什么撕予? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任唱遭,我火速辦了婚禮鲤妥,結(jié)果婚禮上放椰,老公的妹妹穿的比我還像新娘嵌削。我一直安慰自己种远,他們只是感情好盆耽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布轰绵。 她就那樣靜靜地躺著莹桅,像睡著了一般昌执。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诈泼,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天懂拾,我揣著相機(jī)與錄音,去河邊找鬼铐达。 笑死岖赋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瓮孙。 我是一名探鬼主播唐断,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼杭抠!你這毒婦竟也來了脸甘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤偏灿,失蹤者是張志新(化名)和其女友劉穎丹诀,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體菩混,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡忿墅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了沮峡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疚脐。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖邢疙,靈堂內(nèi)的尸體忽然破棺而出棍弄,到底是詐尸還是另有隱情望薄,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布呼畸,位于F島的核電站痕支,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蛮原。R本人自食惡果不足惜卧须,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望儒陨。 院中可真熱鬧花嘶,春花似錦、人聲如沸蹦漠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽笛园。三九已至隘击,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間研铆,已是汗流浹背埋同。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留棵红,地道東北人莺禁。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像窄赋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子楼熄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理忆绰,服務(wù)發(fā)現(xiàn),斷路器可岂,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 首先需要區(qū)分的是SEH與C++異常處理的異同错敢,它們相同的地方在于都是基于編譯程序支持的文法特性,編譯器在遇到異常處...
    縱橫而樂閱讀 3,153評論 0 2
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,074評論 25 707
  • 北京時間 6 月 6 日電缕粹,據(jù)國外媒體報(bào)道稚茅,美國宇航局"太空發(fā)射系統(tǒng)(SLS)"是世界上最強(qiáng)大的運(yùn)載火箭,但是該火...
    NASA菌閱讀 708評論 1 2
  • 測試基本概念 測試用例設(shè)計(jì)白皮書--測試用例基本概念測試用例設(shè)計(jì)白皮書--等價類劃分方法測試用例設(shè)計(jì)白皮書--邊界...
    WendySays閱讀 926評論 0 13