摘要:無需檢查的異常也是的子類。從低層拋出的需檢查異常強(qiáng)制要求調(diào)用方捕獲或是拋出該異常。當(dāng)前執(zhí)行的線程將會停止并報告該異常。單元測試允許我在使用中查看異常,并且作為一個可以被執(zhí)行的文檔來使用。不要捕獲最高層異常繼承的異常同樣是的子類。
前言
異常處理的問題之一是知道何時以及如何去使用它。我會討論一些異常處理的最佳實踐,也會總結(jié)最近在異常處理上的一些爭論。
作為程序員,我們想要寫高質(zhì)量的能夠解決問題的代碼。但是,異常經(jīng)常是伴隨著代碼產(chǎn)生的副作用。沒有人喜歡副作用,因此我們會試圖用自己的方式來解決這個問題。我看過不少的程序用下面的方法應(yīng)對異常:
public void consumeAndForgetAllExceptions(){ try { ...some code that throws exceptions } catch (Exception ex){ ex.printStacktrace(); } }
上面這段代碼的問題在哪里?
一旦一個異常被拋出之后,正常的執(zhí)行流程會停止并且將控制交給捕捉塊。捕捉塊捕獲異常,然后只是把它的信息打印了一下。之后程序正常運(yùn)行,就像沒有任何事情發(fā)生一樣。
那下面的這種方法呢?
public void someMethod() throws Exception{ }
這是一個空方法,里面沒有任何的代碼。為什么一個空方法能夠拋出異常?JAVA并不阻止你這么做。最近,我遇到了一些和這個很相似的代碼,明明代碼塊中沒有拋出異常的語句,卻在方法聲明中拋出異常。當(dāng)我問開發(fā)人員為什么這么做,他會回答“我知道這樣會影響API,但是我之前就這么做的而且效果還不錯”。
C++社區(qū)花了好久才決定如何使用異常。這場爭論也在JAVA社區(qū)產(chǎn)生了。我看到不少JAVA開發(fā)人員艱難的使用異常。如果不能夠正確使用的話,異常會影響程序的性能,因為它需要使用內(nèi)存和CPU來創(chuàng)建,拋出以及捕獲。如果過度使用的話,會使得代碼難以閱讀,并且影響API的使用人員。我們都知道這將會帶來代碼漏洞以及壞味道。客戶端代碼常會通過忽略這個異?;蚴侵苯訉⑵鋻伋鰜肀荛_這個問題,就像之前的兩個例子那樣。
異常的本質(zhì)從廣義的角度來說,一共有三種不同的場景會導(dǎo)致異常的產(chǎn)生:
編程錯誤導(dǎo)致的異常:這一類的異常是因為不恰當(dāng)?shù)木幊處淼模ū热?b>NullPointerException,IllegalArgumentException)??蛻舳送ǔo法對這些錯誤采取任何措施
客戶端代碼的錯誤:客戶端代碼在API允許的范圍之外使用API,從而違背了合約??蛻舳丝梢酝ㄟ^異常中提供的有用信息,采用一些替代方法。比如,當(dāng)解析格式不正確的XML文件時,會拋出異常。這個異常中包含導(dǎo)致該錯誤發(fā)生的XML內(nèi)容的具體位置??蛻舳丝梢酝ㄟ^這些信息采取回復(fù)措施。
資源失效導(dǎo)致的異常:比如系統(tǒng)內(nèi)存不足或是網(wǎng)絡(luò)連接失敗。客戶端面對資源失效的回應(yīng)是要根據(jù)上下文來決定的??蛻舳丝梢栽谝欢螘r間之后試著重新連接或是記錄資源失效日志然后暫停應(yīng)用程序。
JAVA異常類型JAVA定義了兩種異常:
需檢查的異常:從Exception類繼承的異常都是需檢查異常??蛻舳诵枰幚鞟PI拋出的這一類異常,通過try-catch或是繼續(xù)拋出。
無需檢查的異常:RuntimeException也是Exception的子類。但是,繼承了RuntimeException的類受到了特殊的待遇??蛻舳舜a無需專門處理這一類異常。
下圖展示了NullPointerException的繼承樹:
上圖中,NullPointerException繼承自RuntimeException,因此它也是一個無需檢查的異常。
我看到過大量使用需檢查異常只在極少數(shù)時候使用無需檢查異常的。最近,JAVA社區(qū)在需檢查異常的真正價值上爆發(fā)了熱烈的討論。這場辯論源于JAVA是第一個包含需檢查異常的主流OO框架。C++和C#根本沒有需檢查異常。這些語言中所有的異常都是無需檢查的。
從低層拋出的需檢查異常強(qiáng)制要求調(diào)用方捕獲或是拋出該異常。如果客戶端不能有效的處理該異常,API和客戶端之間的異常協(xié)議將會帶來極大的負(fù)擔(dān)??蛻舳说拈_發(fā)人員可能會通過將異常抑制在一個空的捕獲塊中或是直接拋出它。從而又將這個負(fù)擔(dān)交給了客戶端的調(diào)用方。
還有人指責(zé)需檢查異常會破壞封裝,看下面這段代碼:
public List getAllAccounts() throws FileNotFoundException, SQLException{ ... }
getAllAccounts()方法拋出了兩個需檢查異常。調(diào)用這個方法的客戶端必須明確的處理這兩種具體的異常,即使它們并不清楚getAllAccount()內(nèi)究竟是哪個文件訪問或是數(shù)據(jù)庫訪問失敗了,而且它們也沒有提供文件系統(tǒng)或是數(shù)據(jù)庫的邏輯。因此,這樣的異常處理導(dǎo)致方法和調(diào)用者之前出現(xiàn)了不當(dāng)?shù)膹?qiáng)耦合。
設(shè)計API的最佳實踐在討論了這些之后,我們可以來探討一下如何設(shè)計一個正確拋出異常的良好的API。
1.在選擇拋出需確定異常或是無需確定異常時,問自己這樣的一個問題:客戶端代碼在遇到異常時會進(jìn)行怎樣的處理?
如果客戶端能夠采取措施從這個異常中恢復(fù)過來,那就選擇需確定異常。如果客戶端不能采取有效的措施,就選擇無需確定異常。有效的措施是指從異常中恢復(fù)的措施,而不僅僅是記錄錯誤日志。
除此以外,盡量選擇無需確定的異常:它的優(yōu)點在于不會強(qiáng)迫客戶端顯式地處理這種異常。它會冒泡到任何你想捕獲它的地方。JAVA API提供了許多無需檢查的異常如NullPointerException, IllegalArgumentException和IllegalStateException。我傾向于使用JAVA提供的標(biāo)準(zhǔn)的異常,盡量不去創(chuàng)建自己的異常。
2.保留封裝
永遠(yuǎn)不要將特定于實現(xiàn)的異常傳遞到更高層。比如,不要將數(shù)據(jù)層的SQLException傳遞出去。業(yè)務(wù)層不需要了解SQLException。你有兩個選擇:
將SQLException轉(zhuǎn)換為另一個需檢查異常,如果客戶代碼需要從異常中恢復(fù)。
將SQLException轉(zhuǎn)換為無需檢查異常,如果客戶端代碼無法對其進(jìn)行處理。
大多數(shù)時候,客戶代碼無法解決SQLException。這時候就將其轉(zhuǎn)化為無需檢查的異常。
public void dataAccessCode(){ try{ ..some code that throws SQLException }catch(SQLException ex){ ex.printStacktrace(); } }
這里的catch塊并沒有做任何事情。不如通過如下的方式解決它:
public void dataAccessCode(){ try{ ..some code that throws SQLException }catch(SQLException ex){ throw new RuntimeException(ex); } }
這里將SQLException轉(zhuǎn)化為了RuntimeException。如果SQLException出現(xiàn)了,catch塊就會拋出一個運(yùn)行時異常。當(dāng)前執(zhí)行的線程將會停止并報告該異常。但是,該異常并沒有影響到我的業(yè)務(wù)邏輯模塊,它無需進(jìn)行異常處理,更何況它根本無法對SQLException進(jìn)行任何操作。如果我的catch塊需要根異常原因,可以使用getCause()方法。
如果你確信業(yè)務(wù)層可以采取補(bǔ)救措施,你可以將其轉(zhuǎn)化為一個更有意義的無需檢查異常。但是我覺得拋出RuntimeException足以適用大多數(shù)的場景。
3.當(dāng)無法提供更加有用信息時,不要自定義異常
下面這段代碼有什么問題?
public class DuplicateUsernameException extends Exception {}
它沒有給客戶端代碼提供任何有用的信息,除了一個稍微具有含義的命名。不要忘了Exception類和別的類一樣,在里面你可以添加一下方法供客戶端調(diào)用,獲得有用的信息。
public class DuplicateUsernameException extends Exception { public DuplicateUsernameException (String username){....} public String requestedUsername(){...} public String[] availableNames(){...} }
新版本的異常提供了兩個有用的方法:requestedUsername(),它會返回請求的名字,和availableNames(),它會返回一組相近的可用的用戶名??蛻舳丝梢允褂眠@些方法來獲取有用的信息。但是如果你不準(zhǔn)備添加這些額外的信息,那就拋出一個標(biāo)準(zhǔn)的異常即可。
throw new Exception("Username already taken");
如果你覺得客戶端代碼在記錄日志之外對這個異常不能進(jìn)行任何操作,那么最好拋出無需檢查異常:
throw new RuntimeException("Username already taken");
除此以外,你還可以提供一個方法來檢查用戶名是否已經(jīng)被使用。
4.文檔化異常
你可以使用Javadoc的@throws標(biāo)記來記錄需檢查異常和無需檢查異常。但是,我傾向于寫單元測試來文檔化異常。單元測試允許我在使用中查看異常,并且作為一個可以被執(zhí)行的文檔來使用。無論你采用哪種方法,盡量使你的客戶端代碼了解你的API會拋出的異常。這里提供了IndexOutOfBoundsException的單元測試。
public void testIndexOutOfBoundsException() { ArrayList blankList = new ArrayList(); try { blankList.get(10); fail("Should raise an IndexOutOfBoundsException"); } catch (IndexOutOfBoundsException success) {} }
上面這段代碼在調(diào)用blankList.get(10);應(yīng)當(dāng)拋出IndexOutOfBoundsException。如果沒有拋出該異常,則會執(zhí)行fail("Should raise an IndexOutOfBoundsException");顯式的說明該測試失敗了。通過為異常編寫測試,你不僅能記錄異常如何觸發(fā),而且使你的代碼在經(jīng)過這些測試后更加健壯。
使用異常的最佳實踐1.自覺清理資源
如果你在使用如數(shù)據(jù)庫連接或是網(wǎng)絡(luò)連接之類的資源,要確保你及時的清理這些資源。如果你調(diào)用的API僅僅出發(fā)了無需檢查異常,你仍然需要在使用后主動清理。使用try-catch塊。
public void dataAccessCode(){ Connection conn = null; try{ conn = getConnection(); //..some code that throws SQLException }catch(SQLException ex){ ex.printStacktrace(); } finally{ DBUtil.closeConnection(conn); } } class DBUtil{ public static void closeConnection (Connection conn){ try{ conn.close(); } catch(SQLException ex){ logger.error("Cannot close connection"); throw new RuntimeException(ex); } } }
DBUtil類關(guān)閉Connection連接。這里的重點在于在finally塊中關(guān)閉連接,無論是否出現(xiàn)了異常。
2.永遠(yuǎn)不要使用異常來控制流
生成棧追蹤的代價很昂貴,它的價值在于debug過程中使用。在一個流程控制中,棧追蹤應(yīng)當(dāng)被忽視,因為客戶端只想知道如何進(jìn)行。
在下面的代碼中,MaximumCountReachedException被用來進(jìn)行流程控制:
public void useExceptionsForFlowControl() { try { while (true) { increaseCount(); } } catch (MaximumCountReachedException ex) { } //Continue execution } public void increaseCount() throws MaximumCountReachedException { if (count >= 5000) throw new MaximumCountReachedException(); }
useExceptionsForFlowControl()通過無限循環(huán)來增加計數(shù),直到拋出異常。這種方式使得代碼難以閱讀,而且影響代碼性能。只在出現(xiàn)異常的場景拋出異常。
3.不要無視或是壓制異常
當(dāng)API的方法會拋出異常的時候,它在提醒你應(yīng)當(dāng)采取一些措施。如果需檢查異常沒有任何意義,那就干脆將其轉(zhuǎn)化為無需檢查異常再重新拋出。不要單純的用catch捕獲它然后繼續(xù)執(zhí)行,仿佛什么都沒有發(fā)生一樣。
4.不要捕獲最高層異常
繼承RuntimeException的異常同樣是Exception的子類。捕獲Exception的同時,也捕獲了運(yùn)行時異常:
try{ .. }catch(Exception ex){ }
5.只記錄異常一次
將同一個異常多次記入日志會使得檢查追蹤棧的開發(fā)人員感到困惑,不知道何處是報錯的根源。所以只記錄一次。
想要了解更多開發(fā)技術(shù),面試教程以及互聯(lián)網(wǎng)公司內(nèi)推,歡迎關(guān)注我的微信公眾號!將會不定期的發(fā)放福利哦~
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/68595.html
摘要:是指可能導(dǎo)致程序終止的非常嚴(yán)重的時間。具有最高的級別,旨在關(guān)閉中的日志功能。因此為每一個消息選擇一個合適的日志級別是非常重要的。日志的個小建議將日志訪日代碼塊它能顯著的減少因為字符串拼接而帶來的性能的影響。 前言 首先,這篇文章沒有進(jìn)行任何的日志功能的詳細(xì)介紹,而是對日志提出了幾種最佳實踐。適合對日志記錄有所了解的同學(xué)閱讀。下面是正文: JAVA日志管理既是一門科學(xué),又是一門藝術(shù)??茖W(xué)...
摘要:我們會寫切面來攔截對這些業(yè)務(wù)類和類的調(diào)用。切面定義何時攔截一個方法以及做什么和在一起成為切面連接點當(dāng)代碼開始執(zhí)行,并且切點的條件滿足時,通知被調(diào)用。 前言 這篇文章會幫助你使用Spring Boot Starter AOP實現(xiàn)AOP。我們會使用AspectJ實現(xiàn)四個不同的通知(advice),并且新建一個自定義的注解來追蹤方法的執(zhí)行時間。 你將會了解 什么是交叉分割關(guān)注點(cross...
摘要:這個例子想要說明兩個事情中以為結(jié)尾的方法將會異步執(zhí)行默認(rèn)情況下即指沒有傳入的情況下,異步執(zhí)行會使用實現(xiàn),該線程池使用一個后臺線程來執(zhí)行任務(wù)。這個例子展示了如何使用一個固定大小的線程池來實現(xiàn)大寫操作。 前言 這篇博客回顧JAVA8的CompletionStageAPI以及其在JAVA庫中的標(biāo)準(zhǔn)實現(xiàn)CompletableFuture。將會通過幾個例子來展示API的各種行為。 因為Compl...
摘要:前言上一篇文章請參考貓頭鷹的深夜翻譯核心并發(fā)一安全發(fā)布發(fā)布一個對象是指該對象的引用對當(dāng)前的域之外也可見比如,從方法中獲取一個引用。任務(wù)的功能性接口表示一個沒有返回值的任務(wù)表示一個包含返回值的計算。 前言 上一篇文章請參考貓頭鷹的深夜翻譯:核心JAVA并發(fā)(一) 安全發(fā)布 發(fā)布一個對象是指該對象的引用對當(dāng)前的域之外也可見(比如,從getter方法中獲取一個引用)。要確保一個對象被安全的發(fā)...
摘要:由于需要跨進(jìn)程訪問網(wǎng)絡(luò)上的高速緩存,因此延遲,故障和對象序列化會導(dǎo)致性能下降。應(yīng)用程序高速緩存會自動清除條目以保持其內(nèi)存占用。緩存統(tǒng)計高速緩存統(tǒng)計信息可幫助識別高速緩存的運(yùn)行狀況并提供有關(guān)高速緩存行為和性能的信息。 前言 這篇文章探索了現(xiàn)有的各種JAVA緩存基數(shù),它們對各種場景下提高應(yīng)用的性能起著重要的作用。 近十年來,信息技術(shù)極高的提升了業(yè)務(wù)流程,它已經(jīng)成為了全球企業(yè)的戰(zhàn)略性方案。它...
閱讀 3340·2021-11-19 11:36
閱讀 2943·2021-09-27 13:34
閱讀 2003·2021-09-22 15:17
閱讀 2412·2019-08-30 13:49
閱讀 765·2019-08-26 13:58
閱讀 1366·2019-08-26 10:47
閱讀 2547·2019-08-23 18:05
閱讀 607·2019-08-23 14:25