处理异常的标准姿势,你学废了吗?
前言
在Java中应该如何处理异常,这个话题看似简单,不就是 try...catch 嘛,但是往往BUG更容易出现在一些简单的、容易忽略的地方。大多数成熟的开发团队对于如何进行异常处理都有一套规范和最佳实践。
本期内容我整理了一些在我的团队正在使用的9个最佳实践,希望能让你对异常处理有所帮助。 1、使用finally或try...with...resource关闭资源
如果我们在try代码块中需要使用到一些资源,比如 InputStream ,在使用完之后我们需要将资源关闭。
这是一个错误示例 public void tryResource() { FileInputStream inputStream = null; try { File file = new File("./小黑说Java.txt"); inputStream = new FileInputStream(file); // 使用inputStream读取文件 // 不要这样做 inputStream.close(); } catch (FileNotFoundException e) { log.error("文件未找到", e); } catch (IOException e) { log.error("文件读取异常", e); } }
在上面这段代码中,只要在文件读取时没有出现异常,这段代码是可以正常工作的,但是只要在try块中的 close() 方法中抛出异常,资源就不会被关闭。
所以这种情况我们应该将资源关闭的代码放在 finally 中或者使用try...with...resource 语句。 使用finally
在finally块中的代码不管是否出现异常,都会被执行,因此可以确保资源对象被关闭。 public void closeResourceInFinally() { FileInputStream inputStream = null; try { File file = new File("./小黑说Java.txt"); inputStream = new FileInputStream(file); // 使用inputStream读取文件 } catch (FileNotFoundException e) { log.error("文件未找到", e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { log.error("资源关闭异常", e); } } } } 使用try...with...resource
如果你使用的JDK版本是1.7+,那么也可以选择使用 try...with...resource 语句。如果你使用的资源类实现AutoCloseable 接口,则可以使用这种方式。
Java中的大多数标准资源类API都实现了这个接口。在try子句中打开资源,将会在try代码块执行完毕或异常处理后自动关闭资源对象。 public void useTryWithResource() { File file = new File("./小黑说Java.txt"); try (FileInputStream inputStream = new FileInputStream(file);) { // 使用inputStream读取文件 } catch (FileNotFoundException e) { log.error("文件未找到", e); } catch (IOException e) { log.error("文件读取异常", e); } } 2、使用更明确的异常
如果我们的方法需要向外抛出异常,那么异常类型越具体越好。因为在外部调用你代码的其他人对你内部的实现逻辑可能并不清楚,所以要确保能提供给他尽可能多的信息,可以让别人在使用你的方法时更容易理解,这样调用方可以更好地处理抛出的异常。
比如,在你的方法内容抛出 NumberFormatException 比抛出IllegalArgumentException 或者直接抛出Exception ,所代表的含义就会更明确。 3、方法注释中对异常进行说明
如果你的方法声明了可能会抛出异常,那么在方法的文档注释中,应该对异常进行说明。这和上一条的目的一样,都是为了让方法的调用者能提前获得更多的信息,方便他避免在调用你方法时出现异常,或者更明确如果进行异常处理。
所以,我们应该在方法的文档注释中添加@throws声明,并说明什么情况下会抛出对应的异常。 /** * 这个方法内部做了什么什么事情... * * @param input * @throws BusinessException 如果出现xxx情况,则会抛出这个异常 */ public void doSomething(String input) throws BusinessException { ... } 4、在异常中携带足够的描述信息
这一点和前两条做法的目的类似。在异常中携带足够的描述信息,是为了在出现该异常时,能够在日志文件中查看异常信息时,能看到更有用的信息。
所以我们应该尽可能准确地描述出为什么抛出了这个异常,并提供最相关的数据信息让别人定位。
当然这里也不能太极端,你洋洋洒洒写一篇小作文,应该使用简短的一段信息描述,让运维同事能了解到这个问题的严重性,更轻松地分析问题所在。
也不用提供一堆额外的冗余信息,尽量做到足够精准。比如当你再创建一个Long对象时如果传入一个字符串,就会抛出 NumberFormatException 。 public static void testLong() { try { Long abc = new Long("ABC"); } catch (NumberFormatException e) { log.error("格式异常", e); } }
NumberFormatException 的类名已经告诉我们出现的是数字格式化异常,所以在message 中只需要提供输入的字符串。如果你定义的异常类名不能很明确的表达出是什么异常,比如BusinessException ,你就应该在message 中表达出更多的信息。
5、先捕获更明确的异常
一般在我们使用的IDE中,如果当你在做异常捕获时,先捕获了不太具体的异常比如Exception,然后再捕获更具体的异常如IOException,都会提示我们后面的catch块无法到达。所以我们应该先捕获最具体的异常类,将不太具体的异常类的捕获放在最后。 public void catchExceptions() { try { doSomething("小黑说Java"); } catch (NumberFormatException e) { log.error("格式异常", e); } catch (IllegalArgumentException e) { log.error("非法参数", e); } } 6、不要捕获Throwable
Throwable 是所有Exception 和Error 的父类。
虽然可以在 catch 块中捕获它,但是我们不应该这样去做。因为如果使用了Throwable ,那么不仅会对所有抛出的Exception 进行捕获,还会捕获所有的Error 。
而当我们的程序抛出 Error 时表示是一个无法处理的严重问题,例如典型的OutofMemoryError ,StackOverflowError 等,这两个Error 都是由程序无法控制并且不能处理的情况引起的。所以说,最好不要在你的catch 中捕获Throwable ,除非你非常确定try 块中的代码抛出的是可以处理的异常情况。 public void catchThrowable() { try { // 一些业务代码 } catch (Throwable t) { // 不要这样做 } } 7、不要将异常忽略
在你开发的时候可能非常确定不会抛出异常,并且在你开发时确实没有发生过抛出异常的情况,所以你在catch块中没有对异常做任何处理。 public void doNotIgnoreExceptions() { try { // 一些业务代码 } catch (NumberFormatException e) { // 认为永远不会执行到这里 } }
但是,你其实不确定在将来会不会有人在你的try块中添加新的代码,并且他可能也不会意识到他添加的代码会导致有异常抛出,这将会导致在线上真的有异常产生,但是没有一个人知道。
所以,你至少应该在catch中打印一行日志,告诉运维同事,"警报,这里出现了一个不可能会出现的异常"。 public void doNotIgnoreExceptions() { try { // 一些业务代码 } catch (NumberFormatException e) { log.error("警报,这里出现了一个不可能会出现的异常", e); } } 8、不要打印日志后又将异常抛出
这一条可能绝大多数人都会犯过,我见过非常多别人的代码在异常处理时,先打印了一行异常日志,然后将异常抛出,或者转成一个 RuntimeException 抛出。
甚至在一些开源框架中都有出现过。 public void testCatchEx() { try { new Long("heihei"); } catch (NumberFormatException e) { log.error("数字格式异常", e); throw e; } }
你可能会认为这样做很直观,也没什么错,让调用你方法的人去处理就好了。但是这样一来,在日志中会对抛出的一个异常打印多条错误信息。
重复的日志并没有带来任何有价值的信息,参考上面第4条中描述,在异常信息中应该携带足够的信息,并且要做到精准。如果需要在添加其他信息,你应该将捕获到的异常封装在你的自定义异常中再进行抛出。 public void wrapException(String input) throws BusinessException { try { // do something } catch (NumberFormatException e) { throw new BusinessException("一段对异常的描述信息.", e); } }
所以,我们应该只有在想对异常进行处理时捕获,否则就应该在抛出去,并且在方法前面上加以说明,让调用方去处理。 9、在包装异常时使用原始异常
通常在项目开发时,都会有一套自定义的异常,用于将API中的标准异常封装到自定义异常中,可以用于在外层做一些统一的异常处理。
但是我们在使用自定义异常对原始异常进行封装时,需要确保将原始异常作为 cause 保存在自定义异常中,否则你在外层将会丢失原始异常的堆栈跟踪信息,到你你无法通过异常信息分析抛出异常的具体原因。 public void wrapException(String input) throws BusinessException { try { // do something } catch (NumberFormatException e) { // 将e作为构造参数中的cause throw new MyBusinessException("一段对异常的描述信息.", e); } } 总结
在抛出或者捕获异常时,我们应该考虑很多不同的事情,上面所说的大多数都是为了提高代码的可读性和提供给别人的API更易用。
异常不仅是一种错误处理机制,同时还具备一定的信息传递作用。我们应该遵循这些异常处理的规则和最佳实践,写出更规范、不被别人吐槽的好代码。
猪油不能吃,会导致心脑血管疾病?真的有百害而无一利吗说到猪油大家都知道,这个是从猪肉上提炼而来的油,刚提炼出来的猪油是半透明状的液体食用油,等温度下降后就会形成浅黄色或者白色的固体油,因为它具有特殊的香味,所以很受大家的喜爱,其实猪
京城舌尖味道!盘点北京风味最正宗的美食街!隆冬时节,老北京街头巷尾的馋人滋味儿格外地吸引人。今天,小北带你用北京老百姓的方式,打开真正的京城美食线路,走起!1hr牛街小北相信,不管你在哪里查阅北京的美食攻略,不少肉食动物的
一处宣堂寺丹霞,留下千年的文化历史与遗址,舌尖美食风味大家好,我叫唤醒,一个爱分享美食的90后,每天用简单食材,做出美味佳肴。说起甘肃的丹霞地貌,很多人第一反应的都是张掖的七彩丹霞,这里还有一片未被开发的丹霞地区,名为宣堂寺丹霞。一直
法国现状,带你看看真实的法国法国面积550000平方公里,比我国青海省小17万平方公里,比四川省稍大一点。是一个本土位于西欧的国家。法国的交通也和咱一样,靠右行驶,但当地的马路普遍不如我国的宽阔,这里的双车道
房车旅行虽然惬意,但是有些事情不可忽略肉体和灵魂,总要有一样是在路上。因此人的生活中需要有两件东西,一件是阅读,另一件就是更为直接的旅行。随着社会对人的有形无形压力的增强,人们对于外出放松心情有了很大的需求。那么,与其
陪着情人自驾西藏游记(十七)9月28号星期六晴金寨南京行程310公里茶卡盐湖后,基本都是睡到自然醒。一路高速,开起来舒服,坐在里面也舒服,不像在青藏线上,一会儿把你颠到云霄,一会儿又把你抛入谷底,骨头都散架了
那些年,你遇到的最让你叹为观止的事情是什么时光荏苒,光阴似箭。现在已经是2022年的虎年了。回想自己2014年马年去西藏转山的情景,现在依然是历历在目。在那里,我见到了太多令人叹为观止的事情。现在有一个纪录片第三极上边纪录
日本特色的胶囊旅店,两人挤在太空舱,体验神奇众所周知,日本很小,这种小不仅体现在地域上,在某些习惯上当地人也受这一局限影响颇深,就比如我们今天要讲到的胶囊旅店,毕竟日本寸土寸金,睡觉这么没有剩余价值的东西胶囊就够了?不知道他
匈牙利真实的生活现状是怎样的,当地有哪些有趣的事情?匈牙利趣事1有些人觉得单身是自由而不是痛苦的,但在匈牙利做单身狗可不一样。因为匈牙利规定,凡是单身的人,需要为自己单身而缴纳单身税,而且税率很高,一般是总收入的510,所以在匈牙利
情人节怎么过才浪漫?2天1夜约会攻略收好给爱情一个浪漫的仪式,让彼此再用心爱一次。情人节马上就要到啦!今年带TA去哪玩呢?这是一条想要情侣约会和努力脱单的宝子们都不能错过的情人节攻略哦2天游玩推荐路线飞瀑冰封拍照定格浪漫
情人节礼赤诚相见爱在璀璨星河一所经由知名设计师雕琢打磨伫立风浪间接受洗礼的隐世酒店此刻正期待着与你美好相逢月色很美,风也温柔山海间卸下一身束缚在情人节的浪漫时空中邂逅一份舒缓浪漫的水疗体验情人节专享套餐购买使