JAVA 中異常處理的最佳實(shí)踐

前言

異常處理的問(wèn)題之一是知道何時(shí)以及如何去使用它趾撵。我會(huì)討論一些異常處理的最佳實(shí)踐刊苍,也會(huì)總結(jié)最近在異常處理上的一些爭(zhēng)論。

作為程序員仲吏,我們想要寫高質(zhì)量的能夠解決問(wèn)題的代碼不铆。但是,異常經(jīng)常是伴隨著代碼產(chǎn)生的副作用裹唆。沒(méi)有人喜歡副作用誓斥,因此我們會(huì)試圖用自己的方式來(lái)解決這個(gè)問(wèn)題。我看過(guò)不少的程序用下面的方法應(yīng)對(duì)異常:

上面這段代碼的問(wèn)題在哪里许帐?

一旦一個(gè)異常被拋出之后劳坑,正常的執(zhí)行流程會(huì)停止并且將控制交給捕捉塊。捕捉塊捕獲異常成畦,然后只是把它的信息打印了一下距芬。之后程序正常運(yùn)行,就像沒(méi)有任何事情發(fā)生一樣循帐。

那下面的這種方法呢框仔?

這是一個(gè)空方法,里面沒(méi)有任何的代碼拄养。為什么一個(gè)空方法能夠拋出異常存和?JAVA并不阻止你這么做。最近衷旅,我遇到了一些和這個(gè)很相似的代碼捐腿,明明代碼塊中沒(méi)有拋出異常的語(yǔ)句,卻在方法聲明中拋出異常柿顶。當(dāng)我問(wèn)開(kāi)發(fā)人員為什么這么做茄袖,他會(huì)回答“我知道這樣會(huì)影響API,但是我之前就這么做的而且效果還不錯(cuò)”嘁锯。

C 社區(qū)花了好久才決定如何使用異常宪祥。這場(chǎng)爭(zhēng)論也在JAVA社區(qū)產(chǎn)生了聂薪。我看到不少JAVA開(kāi)發(fā)人員艱難的使用異常。如果不能夠正確使用的話蝗羊,異常會(huì)影響程序的性能藏澳,因?yàn)樗枰褂脙?nèi)存和CPU來(lái)創(chuàng)建,拋出以及捕獲耀找。如果過(guò)度使用的話翔悠,會(huì)使得代碼難以閱讀,并且影響API的使用人員野芒。我們都知道這將會(huì)帶來(lái)代碼漏洞以及壞味道蓄愁〗ρ客戶端代碼常會(huì)通過(guò)忽略這個(gè)異臣司郑或是直接將其拋出來(lái)避開(kāi)這個(gè)問(wèn)題均牢,就像之前的兩個(gè)例子那樣侧漓。

小編是一個(gè)有著5年工作經(jīng)驗(yàn)的java程序員钞翔,對(duì)于java驾霜,自己有做資料的整合赶盔,一個(gè)完整學(xué)習(xí)java的路線斤程,學(xué)習(xí)資料和工具荸恕,相信這里有很多學(xué)習(xí)java的小伙伴咽笼,我創(chuàng)立了一個(gè)2000人學(xué)習(xí)扣群,479121291戚炫。每晚都有java的直播課程剑刑。無(wú)論是初級(jí)還是進(jìn)階的小伙伴小編我都?xì)g迎!

異常的本質(zhì)

從廣義的角度來(lái)說(shuō)双肤,一共有三種不同的場(chǎng)景會(huì)導(dǎo)致異常的產(chǎn)生:

編程錯(cuò)誤導(dǎo)致的異常:這一類的異常是因?yàn)椴磺‘?dāng)?shù)木幊處?lái)的(比如 , )施掏。客戶端通常無(wú)法對(duì)這些錯(cuò)誤采取任何措施

客戶端代碼的錯(cuò)誤:客戶端代碼在API允許的范圍之外使用API茅糜,從而違背了合約七芭。客戶端可以通過(guò)異常中提供的有用信息蔑赘,采用一些替代方法狸驳。比如,當(dāng)解析格式不正確的XML文件時(shí)缩赛,會(huì)拋出異常耙箍。這個(gè)異常中包含導(dǎo)致該錯(cuò)誤發(fā)生的XML內(nèi)容的具體位置∷肘桑客戶端可以通過(guò)這些信息采取回復(fù)措施辩昆。

資源失效導(dǎo)致的異常:比如系統(tǒng)內(nèi)存不足或是網(wǎng)絡(luò)連接失敗≈继唬客戶端面對(duì)資源失效的回應(yīng)是要根據(jù)上下文來(lái)決定的汁针∈醴客戶端可以在一段時(shí)間之后試著重新連接或是記錄資源失效日志然后暫停應(yīng)用程序。

JAVA異常類型

JAVA定義了兩種異常:

需檢查的異常:從 類繼承的異常都是需檢查異常施无』源剩客戶端需要處理API拋出的這一類異常,通過(guò)try-catch或是繼續(xù)拋出猾骡。

無(wú)需檢查的異常: 也是 的子類瑞躺。但是,繼承了 的類受到了特殊的待遇卓练“客戶端代碼無(wú)需專門處理這一類異常购啄。

下圖展示了 的繼承樹(shù):

上圖中襟企, 繼承自 ,因此它也是一個(gè)無(wú)需檢查的異常狮含。

我看到過(guò)大量使用需檢查異常只在極少數(shù)時(shí)候使用無(wú)需檢查異常的顽悼。最近,JAVA社區(qū)在需檢查異常的真正價(jià)值上爆發(fā)了熱烈的討論几迄。這場(chǎng)辯論源于JAVA是第一個(gè)包含需檢查異常的主流OO框架蔚龙。C 和C#根本沒(méi)有需檢查異常。這些語(yǔ)言中所有的異常都是無(wú)需檢查的映胁。

從低層拋出的需檢查異常強(qiáng)制要求調(diào)用方捕獲或是拋出該異常木羹。如果客戶端不能有效的處理該異常,API和客戶端之間的異常協(xié)議將會(huì)帶來(lái)極大的負(fù)擔(dān)解孙】犹睿客戶端的開(kāi)發(fā)人員可能會(huì)通過(guò)將異常抑制在一個(gè)空的捕獲塊中或是直接拋出它。從而又將這個(gè)負(fù)擔(dān)交給了客戶端的調(diào)用方弛姜。

還有人指責(zé)需檢查異常會(huì)破壞封裝脐瑰,看下面這段代碼:

方法拋出了兩個(gè)需檢查異常。調(diào)用這個(gè)方法的客戶端必須明確的處理這兩種具體的異常廷臼,即使它們并不清楚 內(nèi)究竟是哪個(gè)文件訪問(wèn)或是數(shù)據(jù)庫(kù)訪問(wèn)失敗了苍在,而且它們也沒(méi)有提供文件系統(tǒng)或是數(shù)據(jù)庫(kù)的邏輯。因此荠商,這樣的異常處理導(dǎo)致方法和調(diào)用者之前出現(xiàn)了不當(dāng)?shù)膹?qiáng)耦合寂恬。

設(shè)計(jì)API的最佳實(shí)踐

在討論了這些之后,我們可以來(lái)探討一下如何設(shè)計(jì)一個(gè)正確拋出異常的良好的API莱没。

1.在選擇拋出需確定異陈咏#或是無(wú)需確定異常時(shí),問(wèn)自己這樣的一個(gè)問(wèn)題:客戶端代碼在遇到異常時(shí)會(huì)進(jìn)行怎樣的處理郊愧?

如果客戶端能夠采取措施從這個(gè)異常中恢復(fù)過(guò)來(lái)朴译,那就選擇需確定異常井佑。如果客戶端不能采取有效的措施,就選擇無(wú)需確定異常眠寿。有效的措施是指從異常中恢復(fù)的措施躬翁,而不僅僅是記錄錯(cuò)誤日志。

除此以外盯拱,盡量選擇無(wú)需確定的異常:它的優(yōu)點(diǎn)在于不會(huì)強(qiáng)迫客戶端顯式地處理這種異常盒发。它會(huì)冒泡到任何你想捕獲它的地方。JAVA API提供了許多無(wú)需檢查的異常如 , 和 狡逢。我傾向于使用JAVA提供的標(biāo)準(zhǔn)的異常宁舰,盡量不去創(chuàng)建自己的異常。

2.保留封裝

永遠(yuǎn)不要將特定于實(shí)現(xiàn)的異常傳遞到更高層奢浑。比如蛮艰,不要將數(shù)據(jù)層的 傳遞出去。業(yè)務(wù)層不需要了解 雀彼。你有兩個(gè)選擇:

將 轉(zhuǎn)換為另一個(gè)需檢查異常壤蚜,如果客戶代碼需要從異常中恢復(fù)。

將 轉(zhuǎn)換為無(wú)需檢查異常徊哑,如果客戶端代碼無(wú)法對(duì)其進(jìn)行處理袜刷。

大多數(shù)時(shí)候,客戶代碼無(wú)法解決 莺丑。這時(shí)候就將其轉(zhuǎn)化為無(wú)需檢查的異常著蟹。

這里的catch塊并沒(méi)有做任何事情。不如通過(guò)如下的方式解決它:

這里將 轉(zhuǎn)化為了 梢莽。如果 出現(xiàn)了萧豆,catch塊就會(huì)拋出一個(gè)運(yùn)行時(shí)異常。當(dāng)前執(zhí)行的線程將會(huì)停止并報(bào)告該異常蟹漓。但是炕横,該異常并沒(méi)有影響到我的業(yè)務(wù)邏輯模塊,它無(wú)需進(jìn)行異常處理葡粒,更何況它根本無(wú)法對(duì) 進(jìn)行任何操作份殿。如果我的catch塊需要根異常原因,可以使用 方法嗽交。

如果你確信業(yè)務(wù)層可以采取補(bǔ)救措施卿嘲,你可以將其轉(zhuǎn)化為一個(gè)更有意義的無(wú)需檢查異常。但是我覺(jué)得拋出RuntimeException足以適用大多數(shù)的場(chǎng)景夫壁。

3.當(dāng)無(wú)法提供更加有用信息時(shí)拾枣,不要自定義異常

下面這段代碼有什么問(wèn)題?

它沒(méi)有給客戶端代碼提供任何有用的信息,除了一個(gè)稍微具有含義的命名梅肤。不要忘了 類和別的類一樣司蔬,在里面你可以添加一下方法供客戶端調(diào)用,獲得有用的信息姨蝴。

新版本的異常提供了兩個(gè)有用的方法: 俊啼,它會(huì)返回請(qǐng)求的名字,和 左医,它會(huì)返回一組相近的可用的用戶名授帕。客戶端可以使用這些方法來(lái)獲取有用的信息浮梢。但是如果你不準(zhǔn)備添加這些額外的信息跛十,那就拋出一個(gè)標(biāo)準(zhǔn)的異常即可。

如果你覺(jué)得客戶端代碼在記錄日志之外對(duì)這個(gè)異常不能進(jìn)行任何操作秕硝,那么最好拋出無(wú)需檢查異常:

除此以外芥映,你還可以提供一個(gè)方法來(lái)檢查用戶名是否已經(jīng)被使用。

4.文檔化異常

你可以使用Javadoc的 標(biāo)記來(lái)記錄需檢查異常和無(wú)需檢查異常缝裤。但是屏轰,我傾向于寫單元測(cè)試來(lái)文檔化異常颊郎。單元測(cè)試允許我在使用中查看異常憋飞,并且作為一個(gè)可以被執(zhí)行的文檔來(lái)使用。無(wú)論你采用哪種方法姆吭,盡量使你的客戶端代碼了解你的API會(huì)拋出的異常榛做。這里提供了 的單元測(cè)試。

上面這段代碼在調(diào)用 應(yīng)當(dāng)拋出 内狸。如果沒(méi)有拋出該異常检眯,則會(huì)執(zhí)行 顯式的說(shuō)明該測(cè)試失敗了。通過(guò)為異常編寫測(cè)試昆淡,你不僅能記錄異常如何觸發(fā)锰瘸,而且使你的代碼在經(jīng)過(guò)這些測(cè)試后更加健壯。

使用異常的最佳實(shí)踐

1.自覺(jué)清理資源

如果你在使用如數(shù)據(jù)庫(kù)連接或是網(wǎng)絡(luò)連接之類的資源昂灵,要確保你及時(shí)的清理這些資源避凝。如果你調(diào)用的API僅僅出發(fā)了無(wú)需檢查異常,你仍然需要在使用后主動(dòng)清理眨补。使用 塊管削。

類關(guān)閉 連接。這里的重點(diǎn)在于在 塊中關(guān)閉連接撑螺,無(wú)論是否出現(xiàn)了異常含思。

2.永遠(yuǎn)不要使用異常來(lái)控制流

生成棧追蹤的代價(jià)很昂貴,它的價(jià)值在于debug過(guò)程中使用。在一個(gè)流程控制中含潘,棧追蹤應(yīng)當(dāng)被忽視饲做,因?yàn)榭蛻舳酥幌胫廊绾芜M(jìn)行。

在下面的代碼中遏弱, 被用來(lái)進(jìn)行流程控制:

通過(guò)無(wú)限循環(huán)來(lái)增加計(jì)數(shù)艇炎,直到拋出異常。這種方式使得代碼難以閱讀腾窝,而且影響代碼性能缀踪。只在出現(xiàn)異常的場(chǎng)景拋出異常。

3.不要無(wú)視或是壓制異常

當(dāng)API的方法會(huì)拋出異常的時(shí)候虹脯,它在提醒你應(yīng)當(dāng)采取一些措施驴娃。如果需檢查異常沒(méi)有任何意義,那就干脆將其轉(zhuǎn)化為無(wú)需檢查異常再重新拋出循集。不要單純的用catch捕獲它然后繼續(xù)執(zhí)行唇敞,仿佛什么都沒(méi)有發(fā)生一樣。

4.不要捕獲最高層異常

繼承 的異常同樣是 的子類咒彤。捕獲 的同時(shí)疆柔,也捕獲了運(yùn)行時(shí)異常:

5.只記錄異常一次

將同一個(gè)異常多次記入日志會(huì)使得檢查追蹤棧的開(kāi)發(fā)人員感到困惑,不知道何處是報(bào)錯(cuò)的根源镶柱。所以只記錄一次旷档。

覺(jué)得本文對(duì)你有幫助?請(qǐng)分享給更多人歇拆。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鞋屈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子故觅,更是在濱河造成了極大的恐慌厂庇,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件输吏,死亡現(xiàn)場(chǎng)離奇詭異权旷,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)贯溅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門拄氯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人盗迟,你說(shuō)我怎么就攤上這事坤邪。” “怎么了罚缕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵艇纺,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)黔衡,這世上最難降的妖魔是什么蚓聘? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮盟劫,結(jié)果婚禮上夜牡,老公的妹妹穿的比我還像新娘。我一直安慰自己侣签,他們只是感情好塘装,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著影所,像睡著了一般蹦肴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上猴娩,一...
    開(kāi)封第一講書(shū)人閱讀 52,584評(píng)論 1 312
  • 那天阴幌,我揣著相機(jī)與錄音,去河邊找鬼卷中。 笑死矛双,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蟆豫。 我是一名探鬼主播议忽,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼无埃!你這毒婦竟也來(lái)了徙瓶?” 一聲冷哼從身側(cè)響起毛雇,我...
    開(kāi)封第一講書(shū)人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤嫉称,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后灵疮,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體织阅,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年震捣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了荔棉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蒿赢,死狀恐怖润樱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情羡棵,我是刑警寧澤壹若,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響店展,放射性物質(zhì)發(fā)生泄漏养篓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一赂蕴、第九天 我趴在偏房一處隱蔽的房頂上張望柳弄。 院中可真熱鬧,春花似錦概说、人聲如沸碧注。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)应闯。三九已至,卻和暖如春挂捻,著一層夾襖步出監(jiān)牢的瞬間碉纺,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工刻撒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骨田,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓声怔,卻偏偏與公主長(zhǎng)得像态贤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子醋火,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理悠汽,服務(wù)發(fā)現(xiàn),斷路器芥驳,智...
    卡卡羅2017閱讀 134,714評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,317評(píng)論 25 707
  • 清早的霧氣還未散柿冲,在路上的人踏著古舊的青石板,發(fā)梢都被霧氣沾濕兆旬,衣服一摸也都是潮的假抄,城東那家茶館卻早早開(kāi)了門,咿咿...
    白燦閱讀 543評(píng)論 0 0
  • 在家呆了三天丽猬,沒(méi)有事情做宿饱,本想看看書(shū),還沒(méi)拿起書(shū)就想睡覺(jué)脚祟,睡也睡不著谬以,看看電視吧……不回家學(xué)校又想回家看看,回到家...
    張中華閱讀 204評(píng)論 0 0
  • 10月9日 星期日 力量 今天選了這張牌由桌,看到牌會(huì)覺(jué)得舒服为黎,大概因?yàn)橛X(jué)得最近累胡陪; 讀牌1:接受的力量,被看到的力量...
    回老家養(yǎng)貓閱讀 183評(píng)論 0 0