Java最大的错误:检查异常

2020 年 10 月 01 日

Java最大的错误:检查异常

本文最初发布于 literatejava.com 网站,经原作者授权由 InfoQ 中文站翻译并分享。


检查异常(checked exception)一直是 Java 语言中一个有争议的特性。


拥护者声称,这一特性可以确保故障检查并从故障中恢复。而批评者则说,“catch”块几乎永远无法从异常中恢复,并且是错误的常见来源。


今天,Java 8 和 lambda 已经出现了。在 Java 世界中,检查异常是否已经过时了呢?


检查异常的意图


在 90 年代中期,Sun 公司的 James Gosling 提出了一种新的语言。


当时,C++编程要求每个返回的函数都要检查是否有错误。他认为必须找出更好的方法,并因此将“异常”的概念构建到 Java 语言中。


检查异常的目的是在本地标志,并强制开发人员处理可能的异常。已检查的异常必须在方法签名上声明或处理。


这是为了增强软件的可靠性和弹性。人们希望从意外状况中“恢复”——提供可预测的失败结果,例如尝试付款时发生的 InsufficientFundsException。关于实际上需要怎样的“恢复”工作,当时的人们并不清楚。


运行时异常(runtime exception)也包含进了 Java 中。由于空指针、数据错误和非法状态/访问都可能在代码中的任何地方发生,因此将它们作为运行时异常的子类型。


运行时异常可以在任何地方抛出,而无需声明,并且更加方便。但是改用它们是否是正确的选择呢?


缺点


这里的关键在于运行时和检查异常在功能上是等效的。没有什么处理或恢复是检查异常能做,而运行时异常做不了的。


反对“检查的”异常的最大论点是,大多数异常无法修复。一个简单的事实是,我们无法控制损坏的代码/子系统。我们看不到实现,对此不承担任何责任,也无法修复它。


尤其成问题的是 JDBC(SQLException)和 EJB 的 RMI(RemoteException)。这些带来了普遍存在的、系统性的、实际上无法修复的可靠性问题,并不会像原始的“受检查的异常”理念确定可修复的突发事件。


对于任何方法,它调用的所有子方法都会有失败的可能性。潜在的故障会累积在调用树中。在方法签名上声明它们已经无法为开发人员提供需要关注的重点位置了——因为声明的异常已经遍布整个调用树。


大多数 EJB 开发人员都经历过这种情况——通过层或整个代码库的方法都需要声明的异常。调用一个具有不同异常的方法需要调整成打方法。


许多开发人员被告知要捕获低级别的异常,然后将其重新抛出为更高级别(应用程序级别)的检查异常。这需要大量的(每个项目可多达 2000 个)非功能性的“catch-throw”代码块。


吞下异常、隐藏原因、重复记录以及返回“空”/未初始化的数据,这些都是很常见的情况。大多数项目可以统计出 600 多个错误的编码或完全错误。


最终,开发人员开始抵触大量的“catch”代码块,不想再看到它们成为错误的来源。


检查异常——与函数式编程不兼容


然后,我们来看看 Java 8,它具有新的函数式编程特性——例如 lambda、Stream 和函数组合。


这些特性建立在泛型的基础上——参数和返回类型已泛化,因此无论项目类型如何,我们都可以编写执行通用操作的迭代和流操作(forEach、map、flatMap)。


但与数据类型不同,声明的异常无法泛化。


在 Java 中,这样的流操作是不可行的(例如 Stream.map):它需要一个 lambda 来声明某些已检查的异常,并透明地将这个检查异常传递给周围的代码。


这一直是反对检查异常的主要论据——抛出和接收“catch”块之间介入的所有代码都必须意识到异常。


解决方法是将其“包装”在一个运行时异常中,从而隐藏异常的原始类型——这让原始概念中设想的特定于异常的“catch”块失去了用武之地。


最后,我们注意到 Java 8 中没有任何新的“函数式接口”声明检查异常,因此可以说 Java 的新理念已经和以前不同了。


结论


与之前的语言相比,Java 异常在可靠性和错误处理方面提供了重要优势。Java 提供了可靠的服务器和商业软件,使用的是 C/C++永远无法做到的方式。


检查异常从其出发点来看,是处理“突发事件”而不是“失败”的尝试。其明确目标是高亮显示特定的可预测点(无法连接、找不到文件等)并确保开发人员能够处理这些点。


最初的概念从未包含在内的是,要强制声明发生了大量系统性和不可恢复的故障。这些失败永远都不是正确的,不能被声明为检查异常。


通常,代码中可能会发生故障,而 EJB、Web 和 Swing/AWT 容器已经通过提供最外部的“失败请求”异常处理程序来解决这个问题。最基本的正确策略是回滚事务并返回错误。


运行时异常允许对检查异常进行任何异常处理,但避免了限制性的编码约束。这简化了代码,并使其更容易遵循尽早抛出、较晚捕获,在最外层/最高级别处理异常的最佳实践。


主流 Java 框架和影响者现在已经明确地摆脱了检查异常。Spring、Hibernate 和现代 Java 框架/供应商仅使用运行时异常,而这种便利性是它们流行的主要因素。


像是 Josh Bloch(Java Collections 框架)、Rod Johnson、Anders Hejlsberg(C#之父)、Gavin King 和 Stephen Colebourn(JodaTime)等大牛都反对检查异常。


现在,在 Java 8 中,lambda 是向前迈出的关键一步。这些语言特性从内部的函数式操作中抽象出“控制流”。如我们所见,这使得检查异常和“立即声明或处理”的要求过时了。


对于开发人员而言,关注可靠性并诊断可能的故障点(突发事件)(例如文件打开、数据库连接等)一直都是很重要的事情。如果我们在此时提供良好的错误消息,我们就能创建自诊断软件——这会是工程成就的巅峰之作。


但我们应该使用未检查的异常来做到这一点,并且如果必须重新抛出,则应始终使用运行时异常或特定于应用程序的子类。


正如 Stephen Colebourn 所说,如果你的项目仍在使用或提倡检查异常,则你的技能已过时 5-10 年了。Java 已经前进了很长的路程。


原文链接:


http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake


2020 年 10 月 01 日 08:003550
用户头像

发布了 489 篇内容, 共 175.3 次阅读, 收获喜欢 1010 次。

关注

评论 3 条评论

发布
用户头像
我说怎么还在拿EJB举例呢,原文是14年的……
2020 年 10 月 12 日 14:35
回复
用户头像
确实容易混淆 Checked/Uncheckd异常,可以简单点就好
2020 年 10 月 12 日 11:50
回复
用户头像
对于像Checked,RuntimeException等等这些读者很容易看懂的词,就不要翻译成中文了。反而增加阅读的困难。
2020 年 10 月 12 日 09:41
回复
没有更多评论了
发现更多内容

一把年龄,技术一般,怎么去面试

escray

学习 面试 面试现场

netty案例,netty4.1基础入门篇八《NettyClient半包粘包处理、编码解码处理、收发数据方式》

小傅哥

Netty 小傅哥

netty案例,netty4.1中级拓展篇一《Netty与SpringBoot整合》

小傅哥

Java Netty

PHP浮点数精度损失问题

flyer0126

php 弱类型语言

没有亮点的简历,要用详历来弥补

escray

学习 面试 简历 面试现场

世界很大,我想去看看

escray

学习 面试 面试现场

netty案例,netty4.1中级拓展篇六《SpringBoot+Netty+Elasticsearch收集日志信息数据存储》

小傅哥

Java Netty

API 中签名的使用

flyer0126

接口安全

netty案例,netty4.1基础入门篇二《NettyServer接收数据》

小傅哥

Java Netty 小傅哥

netty案例,netty4.1基础入门篇五《NettyServer字符串编码器》

小傅哥

Java Netty

netty案例,netty4.1基础入门篇九《自定义编码解码器,处理半包、粘包数据》

小傅哥

Java Netty

netty案例,netty4.1基础入门篇十一《netty udp通信方式案例Demo》

小傅哥

Java Netty

netty案例,netty4.1基础入门篇十二《简单实现一个基于Netty搭建的Http服务》

小傅哥

Java Netty

netty案例,netty4.1中级拓展篇七《Netty请求响应同步通信》

小傅哥

Java Netty 小傅哥

Stream 流

HeGuang

Java

netty案例,netty4.1基础入门篇三《NettyServer字符串解码器》

小傅哥

Java Netty 小傅哥

netty案例,netty4.1基础入门篇四《NettyServer收发数据》

小傅哥

Java Netty 小傅哥

netty案例,netty4.1中级拓展篇二《Netty使用Protobuf传输数据》

小傅哥

Java Netty 小傅哥

netty案例,netty4.1中级拓展篇五《基于Netty搭建WebSocket,模仿微信聊天页面》

小傅哥

Java Netty 小傅哥

在java中使用SPI创建可扩展的应用程序

程序那些事

Java spi 可扩展程序 可扩展应用

[租房]刚步入社会的小萌新,休想坑小妹妹,安排!

我是程序员小贱

实战 | Vue + Element UI 页面创建

简爱W

Java 架构师

netty案例,netty4.1中级拓展篇三《Netty传输Java对象》

小傅哥

Java Netty 小傅哥

理论 | 三天两夜,万字长文,吃透TCP/IP

简爱W

Java TCP

netty案例,netty4.1基础入门篇六《NettyServer群发消息》

小傅哥

Java Netty 小傅哥

netty案例,netty4.1基础入门篇十《关于ChannelOutboundHandlerAdapter简单使用》

小傅哥

Netty 小傅哥

netty案例,netty4.1基础入门篇零《初入JavaIO之门BIO、NIO、AIO实战练习》

小傅哥

Java Netty 小傅哥

netty案例,netty4.1中级拓展篇四《Netty传输文件、分片发送、断点续传》

小傅哥

Netty 小傅哥

LeetCode题解:11. 盛最多水的容器,for循环双指针,JavaScript,详细注释

Lee Chen

LeetCode 前端进阶训练营

netty案例,netty4.1基础入门篇一《嗨!NettyServer》

小傅哥

Java Netty

netty案例,netty4.1基础入门篇七《嗨!NettyClient》

小傅哥

Netty 小傅哥

Java最大的错误:检查异常-InfoQ