JAVA 中异常处理的最佳实践

前言

异常处理的问题之一是知道何时以及如何去使用它。我会讨论一些异常处理的最佳实践,也会总结最近在异常处理上的一些争论。

作为程序员,我们想要写高质量的能够解决问题的代码。但是,异常经常是伴随着代码产生的副作用。没有人喜欢副作用,因此我们会试图用自己的方式来解决这个问题。我看过不少的程序用下面的方法应对异常:

上面这段代码的问题在哪里?

一旦一个异常被抛出之后,正常的执行流程会停止并且将控制交给捕捉块。捕捉块捕获异常,然后只是把它的信息打印了一下。之后程序正常运行,就像没有任何事情发生一样。

那下面的这种方法呢?

这是一个空方法,里面没有任何的代码。为什么一个空方法能够抛出异常?JAVA并不阻止你这么做。最近,我遇到了一些和这个很相似的代码,明明代码块中没有抛出异常的语句,却在方法声明中抛出异常。当我问开发人员为什么这么做,他会回答“我知道这样会影响API,但是我之前就这么做的而且效果还不错”。

C 社区花了好久才决定如何使用异常。这场争论也在JAVA社区产生了。我看到不少JAVA开发人员艰难的使用异常。如果不能够正确使用的话,异常会影响程序的性能,因为它需要使用内存和CPU来创建,抛出以及捕获。如果过度使用的话,会使得代码难以阅读,并且影响API的使用人员。我们都知道这将会带来代码漏洞以及坏味道。客户端代码常会通过忽略这个异常或是直接将其抛出来避开这个问题,就像之前的两个例子那样。

小编是一个有着5年工作经验的java程序员,对于java,自己有做资料的整合,一个完整学习java的路线,学习资料和工具,相信这里有很多学习java的小伙伴,我创立了一个2000人学习扣群,479121291。每晚都有java的直播课程。无论是初级还是进阶的小伙伴小编我都欢迎!

异常的本质

从广义的角度来说,一共有三种不同的场景会导致异常的产生:

编程错误导致的异常:这一类的异常是因为不恰当的编程带来的(比如 , )。客户端通常无法对这些错误采取任何措施

客户端代码的错误:客户端代码在API允许的范围之外使用API,从而违背了合约。客户端可以通过异常中提供的有用信息,采用一些替代方法。比如,当解析格式不正确的XML文件时,会抛出异常。这个异常中包含导致该错误发生的XML内容的具体位置。客户端可以通过这些信息采取回复措施。

资源失效导致的异常:比如系统内存不足或是网络连接失败。客户端面对资源失效的回应是要根据上下文来决定的。客户端可以在一段时间之后试着重新连接或是记录资源失效日志然后暂停应用程序。

JAVA异常类型

JAVA定义了两种异常:

需检查的异常:从 类继承的异常都是需检查异常。客户端需要处理API抛出的这一类异常,通过try-catch或是继续抛出。

无需检查的异常: 也是 的子类。但是,继承了 的类受到了特殊的待遇。客户端代码无需专门处理这一类异常。

下图展示了 的继承树:

上图中, 继承自 ,因此它也是一个无需检查的异常。

我看到过大量使用需检查异常只在极少数时候使用无需检查异常的。最近,JAVA社区在需检查异常的真正价值上爆发了热烈的讨论。这场辩论源于JAVA是第一个包含需检查异常的主流OO框架。C 和C#根本没有需检查异常。这些语言中所有的异常都是无需检查的。

从低层抛出的需检查异常强制要求调用方捕获或是抛出该异常。如果客户端不能有效的处理该异常,API和客户端之间的异常协议将会带来极大的负担。客户端的开发人员可能会通过将异常抑制在一个空的捕获块中或是直接抛出它。从而又将这个负担交给了客户端的调用方。

还有人指责需检查异常会破坏封装,看下面这段代码:

方法抛出了两个需检查异常。调用这个方法的客户端必须明确的处理这两种具体的异常,即使它们并不清楚 内究竟是哪个文件访问或是数据库访问失败了,而且它们也没有提供文件系统或是数据库的逻辑。因此,这样的异常处理导致方法和调用者之前出现了不当的强耦合。

设计API的最佳实践

在讨论了这些之后,我们可以来探讨一下如何设计一个正确抛出异常的良好的API。

1.在选择抛出需确定异常或是无需确定异常时,问自己这样的一个问题:客户端代码在遇到异常时会进行怎样的处理?

如果客户端能够采取措施从这个异常中恢复过来,那就选择需确定异常。如果客户端不能采取有效的措施,就选择无需确定异常。有效的措施是指从异常中恢复的措施,而不仅仅是记录错误日志。

除此以外,尽量选择无需确定的异常:它的优点在于不会强迫客户端显式地处理这种异常。它会冒泡到任何你想捕获它的地方。JAVA API提供了许多无需检查的异常如 , 和 。我倾向于使用JAVA提供的标准的异常,尽量不去创建自己的异常。

2.保留封装

永远不要将特定于实现的异常传递到更高层。比如,不要将数据层的 传递出去。业务层不需要了解 。你有两个选择:

将 转换为另一个需检查异常,如果客户代码需要从异常中恢复。

将 转换为无需检查异常,如果客户端代码无法对其进行处理。

大多数时候,客户代码无法解决 。这时候就将其转化为无需检查的异常。

这里的catch块并没有做任何事情。不如通过如下的方式解决它:

这里将 转化为了 。如果 出现了,catch块就会抛出一个运行时异常。当前执行的线程将会停止并报告该异常。但是,该异常并没有影响到我的业务逻辑模块,它无需进行异常处理,更何况它根本无法对 进行任何操作。如果我的catch块需要根异常原因,可以使用 方法。

如果你确信业务层可以采取补救措施,你可以将其转化为一个更有意义的无需检查异常。但是我觉得抛出RuntimeException足以适用大多数的场景。

3.当无法提供更加有用信息时,不要自定义异常

下面这段代码有什么问题?

它没有给客户端代码提供任何有用的信息,除了一个稍微具有含义的命名。不要忘了 类和别的类一样,在里面你可以添加一下方法供客户端调用,获得有用的信息。

新版本的异常提供了两个有用的方法: ,它会返回请求的名字,和 ,它会返回一组相近的可用的用户名。客户端可以使用这些方法来获取有用的信息。但是如果你不准备添加这些额外的信息,那就抛出一个标准的异常即可。

如果你觉得客户端代码在记录日志之外对这个异常不能进行任何操作,那么最好抛出无需检查异常:

除此以外,你还可以提供一个方法来检查用户名是否已经被使用。

4.文档化异常

你可以使用Javadoc的 标记来记录需检查异常和无需检查异常。但是,我倾向于写单元测试来文档化异常。单元测试允许我在使用中查看异常,并且作为一个可以被执行的文档来使用。无论你采用哪种方法,尽量使你的客户端代码了解你的API会抛出的异常。这里提供了 的单元测试。

上面这段代码在调用 应当抛出 。如果没有抛出该异常,则会执行 显式的说明该测试失败了。通过为异常编写测试,你不仅能记录异常如何触发,而且使你的代码在经过这些测试后更加健壮。

使用异常的最佳实践

1.自觉清理资源

如果你在使用如数据库连接或是网络连接之类的资源,要确保你及时的清理这些资源。如果你调用的API仅仅出发了无需检查异常,你仍然需要在使用后主动清理。使用 块。

类关闭 连接。这里的重点在于在 块中关闭连接,无论是否出现了异常。

2.永远不要使用异常来控制流

生成栈追踪的代价很昂贵,它的价值在于debug过程中使用。在一个流程控制中,栈追踪应当被忽视,因为客户端只想知道如何进行。

在下面的代码中, 被用来进行流程控制:

通过无限循环来增加计数,直到抛出异常。这种方式使得代码难以阅读,而且影响代码性能。只在出现异常的场景抛出异常。

3.不要无视或是压制异常

当API的方法会抛出异常的时候,它在提醒你应当采取一些措施。如果需检查异常没有任何意义,那就干脆将其转化为无需检查异常再重新抛出。不要单纯的用catch捕获它然后继续执行,仿佛什么都没有发生一样。

4.不要捕获最高层异常

继承 的异常同样是 的子类。捕获 的同时,也捕获了运行时异常:

5.只记录异常一次

将同一个异常多次记入日志会使得检查追踪栈的开发人员感到困惑,不知道何处是报错的根源。所以只记录一次。

觉得本文对你有帮助?请分享给更多人。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,652评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,079评论 25 707
  • 清早的雾气还未散,在路上的人踏着古旧的青石板,发梢都被雾气沾湿,衣服一摸也都是潮的,城东那家茶馆却早早开了门,咿咿...
    白灿阅读 534评论 0 0
  • 在家呆了三天,没有事情做,本想看看书,还没拿起书就想睡觉,睡也睡不着,看看电视吧……不回家学校又想回家看看,回到家...
    张中华阅读 202评论 0 0
  • 10月9日 星期日 力量 今天选了这张牌,看到牌会觉得舒服,大概因为觉得最近累; 读牌1:接受的力量,被看到的力量...
    回老家养猫阅读 181评论 0 0