摘要:异常处理作用调试程序定位缺陷。对异常的处理是系统容错可靠性的一环。抛出需要具体子异常捕获同时也需要具体子异常不同子异常处理会不同。如有基础基类异常调用者可以选择使用这个基类该类下面的所有子异常使用统一处理也可以多带带对子异常处理。
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等同学的回复
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/74116.html
摘要:对象的自动清除对象回收是由垃圾回收线程负责方法可以要求系统进行垃圾回收,仅仅是建议系统没有析构方法,但的有类似方法系统在回收时会自动调用对象的方法子类的方法可以在里面释放系统资源,一般来说,子类的方法中应该调用父类的方法。 对象的自动清除 对象回收是由垃圾回收线程负责 System.gc()方法可以要求系统进行垃圾回收,仅仅是建议系统 java没有析构方法,但Object的final...
摘要:本章中的大部分内容适用于构造函数和方法。第项其他方法优先于序列化第项谨慎地实现接口第项考虑使用自定义的序列化形式第项保护性地编写方法第项对于实例控制,枚举类型优先于第项考虑用序列化代理代替序列化实例附录与第版中项目的对应关系参考文献 effective-java-third-edition 介绍 Effective Java 第三版全文翻译,纯属个人业余翻译,不合理的地方,望指正,感激...
摘要:简介职责链模式有时候也叫责任链模式,它是一种对象行为的设计模式。中的就是使用了责任链模式。纯的责任链模式的实际例子很难找到,一般看到的例子均是不纯的责任链模式的实现。如果坚持责任链不纯便不是责任链模式,那么责任链模式便不会有太大意义了。 Java设计模式之职责链模式 前几天复习java的异常处理时,接触到了责任链模式。在企业级应用中,从前台发过来的请求在后台抛出异常,异常处理的设计一般...
阅读 1799·2023-04-26 00:59
阅读 3108·2021-11-15 18:10
阅读 3035·2021-09-22 16:02
阅读 748·2021-09-02 15:15
阅读 3695·2019-08-30 15:56
阅读 1895·2019-08-30 15:54
阅读 2793·2019-08-29 16:31
阅读 2002·2019-08-29 16:10