摘要:異常處理作用調試程序定位缺陷。對異常的處理是系統容錯可靠性的一環。拋出需要具體子異常捕獲同時也需要具體子異常不同子異常處理會不同。如有基礎基類異常調用者可以選擇使用這個基類該類下面的所有子異常使用統一處理也可以多帶帶對子異常處理。
1 背景
1年前的文章,源于Sonar靜態代碼掃描中,項目歷史代碼里有兩個規則的ISSUE是異常相關的。
對于如何使用異常和設計異常,借助于業界的經驗,拋磚引玉給大家分享下。盡量引用Java API和Spring的例子來說明。
2 為什么要有異常處理正常運行情況下,程序順利運行下來不存在異常情況。但是往往程序正確運行依賴各種條件,既有代碼編寫邏輯正確,也有外部軟件、硬件運行正常。其中一項無法正常工作,程序就會發生異常。
因此,在程序語言層面,自然就會有異常處理。或捕獲錯誤故障,記錄并處理;或拋出錯誤故障 ,讓上一層調用方捕獲知道,并做下一步處理。
而Java語言程序語言層面,內置支持異常處理。
3 異常處理作用調試程序,定位缺陷。
異常是一種調試手段:什么出錯(異常類型),在哪出錯(異常堆棧),為什么錯(異常信息)
向前恢復,繼續服務。
對異常的處理,是系統容錯、可靠性的一環。
4 如何使用異常 4.1 對公共接口的參數進行檢驗通過當參數約定不符時,拋出unchecked異常,如ArrayList.get
/** * Returns the element at the specified position in this list. * * @param index index of the element to return * @return the element at the specified position in this list * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { rangeCheck(index); return elementData(index); } /** * Checks if the given index is in range. If not, throws an appropriate * runtime exception. This method does *not* check if the index is * negative: It is always used immediately prior to an array access, * which throws an ArrayIndexOutOfBoundsException if index is negative. */ private void rangeCheck(int index) { if (index >= size) // 拋出unchecked異常 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
異常的信息必須清楚,包括但不限于引起異常的參數值。
/** * Constructs an IndexOutOfBoundsException detail message. * Of the many possible refactorings of the error handling code, * this "outlining" performs best with both server and client VMs. */ private String outOfBoundsMsg(int index) { return "Index: "+index+", Size: "+size; }4.2 不要嘗試處理代碼錯誤
對代碼錯誤,最好的策略是馬上失敗不要catch unchecked異常。
如NullPointerExceptionIndexOutOfBoundsException,
留下問題的審計日志,便于追蹤問題。
// Noncompliant public void foo(String bar) throws Throwable { throw new RuntimeException("My Message"); } // Compliant public void foo(String bar) { throw new MyOwnRuntimeException("My Message"); }4.4 當轉換異常時使用異常鏈
try { /* ... */ } catch (Exception e) { // Noncompliant - exception is lost throw new RuntimeException("context"); }
上面的代碼就會把原始異常丟失,正確應該保留原始異常。
try { /* ... */ } catch (Exception e) { throw new RuntimeException(e); }4.5 記錄日志或拋出異常,但不要同時都做
對同一個代碼問題,多種的日志信息反而會讓開發人員難以簡單清晰定位問題。
try { /* ... */ } catch (Exception e) { LOGGER.error("系統執行出錯",e); throw new RuntimeException(e); }
雖然成熟的日志系統有調用鏈ID,方便我們把請求下面的日志全部拖出來。但上面的例子,最終系統日志會出現多個異常日志記錄,反而不及最終一個異常(帶異常鏈)唯一記錄到日志來得清晰。
4.6 不要在finally里拋出異常try { //執行時拋出異常e1 doSomethingThrowExceptionFirst(); } finally { //同時拋出異常e2 doFinallyThrowExceptionSecond(); }
try{}異常e1,finally{}異常e2,當同時出現異常時,如果e2拋出e1丟失。
正常做法,doFinallyThrowExceptionSecond內處理異常或者記錄異常到日志。
Java內置異常在能明確表達當前代碼異常情況下可以拿來重用。
4.8 異常提供上下文異常在java中是對象,保持和提供足夠信息 引起異常的參數值、錯誤細節描述、錯誤文本、關于改正的信息(當前重試的次數)。
如org.springframework.core.convert.ConversionFailedException
/** * Create a new conversion exception. * @param sourceType the value"s original type * @param targetType the value"s target type * @param value the value we tried to convert * @param cause the cause of the conversion failure */ public ConversionFailedException(TypeDescriptor sourceType, TypeDescriptor targetType, Object value, Throwable cause) { super("Failed to convert from type " + sourceType + " to type " + targetType + " for value "" + ObjectUtils.nullSafeToString(value) + """cause); this.sourceType = sourceType; this.targetType = targetType; this.value = value; }4.9 盡可能地在接近問題產生處處理異常
往往對異常能做出正確決定的是直接調用者。
異常離源問題代碼越遠,越難跟蹤到源問題,也更難做有用的處理。
有時最好的異常處理是顯示被設計來控制流程的對象。
對同一個異常只記錄一次,注意日志級別。
// 日志級別反例 String productsRequest = prepareProductsRequest(productId); // 生產上日志級別一般為INFO,此處不會打印到日志文件 logger.debug (productsRequest); try { String response = retrieveProducts(productsRequest); logger.debug (response); } catch (NoSuchProductException e) { //當發生異常時,只記錄了異常,沒有productsRequest這個值 logger.error(e); }
//日志級別正例 String productsRequest = prepareProductsRequest(productId); try { String response = retrieveProducts(productsRequest); } catch (NoSuchProductException e) { //當發生異常時,把productsRequest也打印出來 logger.error("request:" + productsRequest, e); }5 如何設計異常 5.1 異常命名明確
名字體現異常是什么。例如Java API中的FileNotFoundException,EOFException。拋出需要具體子異常,捕獲同時也需要具體子異常,不同子異常處理會不同。
5.2 異常定義歸類、有層次按邏輯子模塊定義一個異常或者相關的一系列異常。
如org.springframework.core.NestedRuntimeException
有基礎基類異常,調用者可以選擇使用這個基類catch該類下面的所有子異常使用統一處理,也可以多帶帶對子異常處理。
//選擇使用這個基類catch該類下面的所有子異常使用統一處理 private Object getPropertyValue(Object obj) { try { this.beanWrapper.setWrappedInstance(obj); return this.beanWrapper.getPropertyValue(this.sortDefinition.getProperty()); }catch (BeansException ex) { //調用者可以選擇使用這個基類catch該類下面的所有子異常使用統一處理 logger.info("PropertyComparator could not access property - treating as null for sorting", ex); return null; } }
//也可以多帶帶對子異常處理 } catch (IllegalStateException ex) { singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { throw ex; } } catch (BeanCreationException ex) { //也可以多帶帶對子異常處理 if (recordSuppressedExceptions) { for (Exception suppressedException : this.suppressedExceptions) { ex.addRelatedCause(suppressedException); } } throw ex; }5.3 異常的抽象層次和接口的抽象層次一致 5.3.1 為什么?
異常拋出聲明是接口定義的一部分 高級別的處理代碼catch低級別的異常,上下文缺少,不能很好的做出處理 如果不進行抽象,違反封裝原則(對外信息隱藏)減少系統可重用和清晰性。
//異常抽象層次和接口抽象層次一致 @Override public Object getPropertyValue(String propertyName) throws BeansException { Field field = this.fieldMap.get(propertyName); if (field == null) { //NotReadablePropertyException是InvalidPropertyException的子類 throw new NotReadablePropertyException(this.target.getClass(), propertyName, "Field "" + propertyName + "" does not exist"); } try { ReflectionUtils.makeAccessible(field); return field.get(this.target); } catch (IllegalAccessException ex) { //封裝IllegalAccessException異常拋出和接口同一抽象層次的異常,InvalidPropertyException是BeansException的子類 throw new InvalidPropertyException(this.target.getClass(), propertyName, "Field is not accessible", ex); } }5.3.2 在facade下的模塊化設計下,嚴格完整的定義異常。
大前提:是指如果一個子模塊以package形式組織,對外提供少量public的facade方法,內部的異常設計。
異常嚴格完整設計(對于其他情況下的代碼也是需要的,5.2 異常定義歸類、有層次)
完整列出有可能異常、checked/unchecked異常;
如果異常能被組織成繼承結構,不僅是父類異常,所有異常都要定義。
讓異常可表達、保護封裝(前面已經說明)
使用checked異常(注意前提說明,Spring里BeanException是unchecked異常,跟本身Bean在Spring中設計定位有關)
5.4 什么時候使用檢測性異常和非檢測性異常(運行時異常) 5.4.1 當異常是可處理的,使用檢測性異常。有可補償條件
調用方有協定可以處理
5.4.2 當異常是可處理的,使用運行時異常(非檢測性異常)程序代碼錯誤
*接口定義被破壞,如參數是非法的,拋出IllegalArgumentException
5.5 錯誤類型編碼表述轉為異常表述錯誤類型編碼能對錯誤進行分類定義,一般出現沒有內置異常處理的編程語言,但有時第三方或者網絡協議都會有使用錯誤類型編碼這種方式。
Spring RestClientException的例子,HttpStatusCodeException下定義兩個子異常,分別是
HttpClientErrorException代表收到4xx
HttpServe rErrorException代表收到5xx
6 處理異常情況的策略判斷請求不能正確執行時,不執行
請失敗時清理環境返回來異常,讓請求能根據異常選擇備選方案
守護掛起,直接條件允許正確執行,嘗試完成請求
暫時的完成,請求完成,但不提交它直到可以完成。
恢復,通過可接受的備選方案完成。備份資源須簡單便于使用。
上升到更高的處理。如請求人員來對系統進行操作處理
回滾,失敗時不產生影響
重試,通過重試在系統正常時完成請求。
參考資料Exception Handling
知乎:如何優雅的處理異常(java)?ylxfc等同學的回復
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74116.html
摘要:對象的自動清除對象回收是由垃圾回收線程負責方法可以要求系統進行垃圾回收,僅僅是建議系統沒有析構方法,但的有類似方法系統在回收時會自動調用對象的方法子類的方法可以在里面釋放系統資源,一般來說,子類的方法中應該調用父類的方法。 對象的自動清除 對象回收是由垃圾回收線程負責 System.gc()方法可以要求系統進行垃圾回收,僅僅是建議系統 java沒有析構方法,但Object的final...
摘要:本章中的大部分內容適用于構造函數和方法。第項其他方法優先于序列化第項謹慎地實現接口第項考慮使用自定義的序列化形式第項保護性地編寫方法第項對于實例控制,枚舉類型優先于第項考慮用序列化代理代替序列化實例附錄與第版中項目的對應關系參考文獻 effective-java-third-edition 介紹 Effective Java 第三版全文翻譯,純屬個人業余翻譯,不合理的地方,望指正,感激...
摘要:簡介職責鏈模式有時候也叫責任鏈模式,它是一種對象行為的設計模式。中的就是使用了責任鏈模式。純的責任鏈模式的實際例子很難找到,一般看到的例子均是不純的責任鏈模式的實現。如果堅持責任鏈不純便不是責任鏈模式,那么責任鏈模式便不會有太大意義了。 Java設計模式之職責鏈模式 前幾天復習java的異常處理時,接觸到了責任鏈模式。在企業級應用中,從前臺發過來的請求在后臺拋出異常,異常處理的設計一般...
閱讀 1829·2023-04-26 00:59
閱讀 3130·2021-11-15 18:10
閱讀 3072·2021-09-22 16:02
閱讀 766·2021-09-02 15:15
閱讀 3716·2019-08-30 15:56
閱讀 1917·2019-08-30 15:54
閱讀 2858·2019-08-29 16:31
閱讀 2035·2019-08-29 16:10