如何处理异常
最近,我與一個(gè)朋友進(jìn)行了討論,他是一個(gè)相對(duì)初級(jí)但很聰明的軟件開(kāi)發(fā)人員。 她問(wèn)我有關(guān)異常處理的問(wèn)題。 這些問(wèn)題指出了一種技巧和竅門,肯定有它們的清單。 但是我堅(jiān)信我們編寫軟件的方式背后的背景和動(dòng)機(jī),因此我決定從這種角度寫關(guān)于異常的想法。
編程中的異常(使用Java作為故事的舞臺(tái))用于通知我們?cè)趫?zhí)行代碼期間發(fā)生了問(wèn)題。 異常是類的特殊類別。 使它們與眾不同的是,它們擴(kuò)展了Exception類,而后者又?jǐn)U展了Throwable類。 作為Throwable的實(shí)現(xiàn),我們可以在必要時(shí)“拋出”它們。 那么,如何發(fā)生異常? 異常類的實(shí)例從JVM或使用throw語(yǔ)句在代碼段中引發(fā)。 那是怎么回事,但是為什么呢?
我敢肯定,當(dāng)我們看到異常發(fā)生時(shí),我們大多數(shù)人都會(huì)畏縮,但這是使我們受益的工具。 在引發(fā)異常之前,返回了特殊值或錯(cuò)誤代碼,以使我們知道操作沒(méi)有成功。 忘記(或不知道)檢查此類錯(cuò)誤代碼,可能會(huì)導(dǎo)致我們的應(yīng)用程序發(fā)生不可預(yù)測(cè)的行為。 所以對(duì)
在我撰寫以上內(nèi)容時(shí),我想到了兩件事。 異常是一個(gè)不好的事件,因?yàn)閯?chuàng)建異常時(shí)我們知道發(fā)生了問(wèn)題。 異常是一種有用的構(gòu)造,因?yàn)楫惓?梢詾槲覀兲峁┯嘘P(guān)發(fā)生錯(cuò)誤的有價(jià)值的信息,并允許我們針對(duì)每種情況采取適當(dāng)?shù)男袨椤?試圖散布此設(shè)計(jì)問(wèn)題的實(shí)質(zhì): 觸發(fā)方法/請(qǐng)求執(zhí)行某項(xiàng)操作,但它可能會(huì)失敗–我們?nèi)绾巫詈玫赝ㄖ{(diào)用方它失敗了? 我們?nèi)绾蝹鬟_(dá)有關(guān)發(fā)生的情況的信息? 我們?nèi)绾螏椭蛻魶Q定下一步該怎么做? 使用異常的問(wèn)題在于我們“放棄了”,而不僅僅是“放棄”。 我們以“爆炸性”的方式來(lái)做,我們服務(wù)的客戶/呼叫者必須處理混亂 。
因此,關(guān)于異常是我的第一個(gè)建議,因?yàn)樗鼈兪且患氖?#xff0c;請(qǐng)盡量避免 。 在您所控制的軟件部分中,實(shí)施難以出錯(cuò)的設(shè)計(jì)。 您可以使用支持此行為的語(yǔ)言功能。 我相信Java中最常見(jiàn)的異常是NullPointerException,而Optionals可以幫助我們避免它們。 讓我們考慮一下我們想要檢索具有指定id的雇員:
public Optional<Employee> tryGetEmployee(String employeeId) {return Optional.ofNullable(employeeService.getEmployee(employeeId)); }現(xiàn)在好多了。 但是除了語(yǔ)言的功能之外,我們還可以通過(guò)某種方式設(shè)計(jì)代碼,以免發(fā)生錯(cuò)誤。 如果我們考慮一種只能接收正整數(shù)作為輸入的方法,則可以設(shè)置代碼,這樣客戶端就極不可能錯(cuò)誤地傳遞無(wú)效輸入。 首先,我們創(chuàng)建一個(gè)PositiveInteger類:
public class PositiveInteger {private Integer integerValue;public PositiveInteger(Integer inputValue) {if(inputValue <= 0) {throw new IllegalArgumentException("PositiveInteger instances can only be created out of positive integers");}this.integerValue = inputValue;}public Integer getIntegerValue() {return integerValue;} }然后,對(duì)于只能使用正整數(shù)作為輸入的方法:
public void setNumberOfWinners(PositiveInteger numberOfWinners) { … } 這些當(dāng)然是簡(jiǎn)單的例子,我確實(shí)認(rèn)為,問(wèn)題的核心是偶爾發(fā)生問(wèn)題,然后我們必須告知客戶發(fā)生的情況。 假設(shè)我們從外部后端系統(tǒng)檢索了一個(gè)員工列表,那么事情可能會(huì)出錯(cuò)。 如何處理呢?
我們可以將響應(yīng)對(duì)象設(shè)置為GetEmployeesResponse,如下所示:
但是,讓我們成為現(xiàn)實(shí)主義者,您無(wú)法控制代碼庫(kù)的每個(gè)部分,也不會(huì)更改所有內(nèi)容。 確實(shí)會(huì)發(fā)生異常,因此,讓我們從它們的簡(jiǎn)要背景信息開(kāi)始。
如前所述,Exception類擴(kuò)展了Throwable類。 所有異常都是異常類的子類。 可以將異常分為已檢查和未檢查的異常。 這僅表示某些異常(已檢查的異常)要求我們?cè)诰幾g時(shí)指定在異常發(fā)生時(shí)應(yīng)用程序的行為方式。 未檢查的異常不要求我們進(jìn)行編譯時(shí)間處理。 要?jiǎng)?chuàng)建此類異常,您可以擴(kuò)展RuntimeException類,該類是Exception的直接子類。 關(guān)于已檢查與未檢查的較舊且常見(jiàn)的指導(dǎo)原則是,運(yùn)行時(shí)異常用于表示應(yīng)用程序通常無(wú)法預(yù)期或從中恢復(fù)的情況,而已檢查異常是編寫良好的應(yīng)用程序應(yīng)從中預(yù)期并從中恢復(fù)的情況。
好吧,我主張僅使用運(yùn)行時(shí)異常 。 而且,如果我使用的庫(kù)具有帶有檢查異常的方法,那么我將創(chuàng)建一個(gè)包裝器方法,將其轉(zhuǎn)換為運(yùn)行時(shí)。 那為什么不檢查異常呢? Bob叔叔在他的“ Clean Code”一書中指出, 它們違反了Open / Closed原理 ,因?yàn)槭褂眯碌膖hrows聲明更改簽名可能會(huì)對(duì)調(diào)用該方法的程序的各個(gè)級(jí)別產(chǎn)生影響。
現(xiàn)在,無(wú)論是檢查還是未檢查,由于異常是一種結(jié)構(gòu),可讓我們洞悉出了什么問(wèn)題,因此應(yīng)該對(duì)所發(fā)生的事情盡可能地具體和豐富 。 因此, 嘗試使用標(biāo)準(zhǔn)異常,其他人將更容易理解發(fā)生的情況。 看到NullPointerException時(shí),任何人都清楚原因。 如果您自己設(shè)置例外,請(qǐng)使其明智且具體。 例如,ValidationException使我知道某個(gè)驗(yàn)證失敗,AgeValidationException使我指出特定的驗(yàn)證失敗。 具體而言,既可以更輕松地診斷發(fā)生了什么,又可以根據(jù)發(fā)生的事情(異常類型)指定不同的行為。 這就是為什么您必須始終首先捕獲最具體的異常的原因! 因此,這里出現(xiàn)了另一個(gè)常見(jiàn)建議,指示不要抓住“異常”。 這是一個(gè)有效的建議,我偶爾不遵循。 在我的api的邊界(比如說(shuō)我的REST服務(wù)的端點(diǎn))中,我總是有通用的catch Exception子句。 我不希望任何意外,以及我在代碼中無(wú)法預(yù)測(cè)或防范的事情,可能會(huì)向外界揭示事物。
具有描述性,但也可以根據(jù)抽象級(jí)別提供例外 。 考慮創(chuàng)建一個(gè)異常層次結(jié)構(gòu),以不同的抽象級(jí)別提供語(yǔ)義信息。 如果從程序的較低層引發(fā)了異常,例如與數(shù)據(jù)庫(kù)相關(guān)的異常,則不必向API的調(diào)用者提供詳細(xì)信息。 捕獲異常并引發(fā)一個(gè)更抽象的異常,該異常僅通知調(diào)用者其嘗試的操作失敗。 似乎這與“僅在可能的情況下才捕獲”的常見(jiàn)方法背道而馳,但事實(shí)并非如此。 只是在這種情況下,我們的“處理”是觸發(fā)新異常。 在這些情況下,通過(guò)將原始異常傳遞給新異常的構(gòu)造函數(shù),可以使整個(gè)異常歷史在拋出之間可用。
“手柄”一詞已被多次使用。 這是什么意思? 當(dāng)異常在我們熟悉的catch子句中被“捕獲”時(shí),被視為已處理。 引發(fā)異常時(shí),首先它將在發(fā)生異常的代碼中搜索異常處理,如果未找到異常,它將進(jìn)入所包含方法的調(diào)用上下文,依此類推,直到找到異常處理程序或程序?yàn)橹箤⒔K止。
我再次喜歡Bob叔叔的一件好事,就是try-catch-finally塊定義了程序中的作用域。 除詞匯范圍外,我們還應(yīng)考慮其概念范圍,將try塊視為事務(wù) 。 如果出問(wèn)題了該怎么辦? 我們?nèi)绾未_保使程序保持有效狀態(tài)? 不要忽略例外! 我猜程序員對(duì)許多小時(shí)的不滿是由無(wú)聲異常引起的。 捕獲并最終阻止是您進(jìn)行清理的地方。 確保等待,直到掌握了正確處理異常的所有信息為止。 這可以與早起早發(fā)的原則聯(lián)系在一起。 我們提早拋出,因此我們不必進(jìn)行由于異常而不得不稍后恢復(fù)的操作,而為了及時(shí)掌握所有信息以正確處理異常,我們就可以遲到。 順便說(shuō)一句,當(dāng)您捕獲異常時(shí),只有在解決它們時(shí)才記錄日志,否則單個(gè)異常事件會(huì)導(dǎo)致日志混亂。 最后,對(duì)于異常處理,我個(gè)人更喜歡創(chuàng)建一個(gè)可以在代碼的不同部分中使用的錯(cuò)誤處理服務(wù) ,并在日志記錄,重新拋出,清理資源等方面采取適當(dāng)?shù)拇胧K辛宋业腻e(cuò)誤處理行為,避免了代碼重復(fù),并幫助我從更高級(jí)的角度了解應(yīng)用程序中如何處理錯(cuò)誤。
因此,既然我們有了足夠的上下文,悖論,規(guī)則及其例外,我們可以總結(jié)一下:
- 盡量避免例外。 使用語(yǔ)言功能和適當(dāng)?shù)脑O(shè)計(jì)以實(shí)現(xiàn)它
- 使用運(yùn)行時(shí)異常,將方法與檢查的異常包裝在一起,然后將其轉(zhuǎn)換為運(yùn)行時(shí)
- 嘗試使用標(biāo)準(zhǔn)例外
- 使您的例外情況具有特定性和描述性
- 首先捕獲最具體的異常
- 不要趕上異常
- 但是要在api的邊界上抓住Exception。 完全掌控一切
- 創(chuàng)建與應(yīng)用程序的層和功能相匹配的異常層次結(jié)構(gòu)
- 在適當(dāng)?shù)某橄蠹?jí)別上拋出異常。 逐層移動(dòng)時(shí)捕獲異常并引發(fā)更高級(jí)別的異常
- 通過(guò)在新的構(gòu)造函數(shù)中提供異常,在重新拋出時(shí)傳遞完整的異常歷史記錄
- 將try-catch-finally塊視為事務(wù)。 當(dāng)出現(xiàn)問(wèn)題時(shí),請(qǐng)確保將程序保持在有效狀態(tài)
- 在可以處理時(shí)捕獲異常
- 永遠(yuǎn)不要有空的catch子句
- 處理異常時(shí)記錄
- 擁有全局異常處理服務(wù),并具有如何處理錯(cuò)誤的策略
就是這樣! 繼續(xù)前進(jìn),成為非凡!
翻譯自: https://www.javacodegeeks.com/2017/12/how-to-deal-with-exceptions.html
總結(jié)
- 上一篇: jboss加载组件_直接从JBoss A
- 下一篇: 弹簧和线程:事务