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:00 3326
用户头像

发布了 439 篇内容, 共 163.8 次阅读, 收获喜欢 896 次。

关注

评论 3 条评论

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

【计算机网络】为什么要三次握手四次挥手?

烫烫烫个喵啊

TCP 计算机网络

编程核心能力之抽象

顿晓

抽象 编程日课

ARTS 04 - 使用 Gitlab + Generic Webhook Trigger 触发 Jenkins 自动化构建

jerry.mei

算法 ARTS 打卡计划 CI/CD 函数式编程 Elixir

海南的七星彩网站系统盘口代码解析

网站,小程序,APP开发定制

代码

手把手整合SSM框架

JavaPub

观智能化浪潮如何改变产业链创新

CECBC区块链专委会

[译] 图说前端-图解 React

梦见君笑

前端 前端框架 React 框架 前端训练

一致性hash算法及标准差验证

Damon

分布式系统设计理念这么难学?

架构师修行之路

架构 分布式

[译] 图说前端-图解 React Native

梦见君笑

前端 前端框架 前端进阶训练营 漫画编程

[译] 图说前端-组件、Prop 和 State

梦见君笑

前端 前端框架 React 前端进阶训练营 漫画编程

极客时间 - 架构师培训 - 6 期作业

Damon

ARTS-WEEK6

一周思进

ARTS 打卡计划

java8的parallelStream提升数倍查询效率

网站,小程序,APP开发定制

java8

架构师训练营第六周学习总结

锦澄

每周学习总结 - 架构师培训 6期

Damon

进程、线程基础知识全家桶,30 张图一套带走

小林coding

Linux 操作系统 计算机基础 进程 进程线程区别

JavaScript 混淆与逆向必读之 AST 节点类型名词基础

今日长剑在握

JavaScript

vue项目发布时去除console语句

网站,小程序,APP开发定制

【计算机网络】如何实现可靠数据传输?

烫烫烫个喵啊

ARTS打卡-06

Geek_yansheng25

昆明市成立两大“高端”中心,区块链赋能生物医药和高原特色农业

CECBC区块链专委会

ARTS WEEK5

紫枫

ARTS 打卡计划

架构师课程第六周 作业

杉松壁

每周学习总结 - 架构师培训 5 期

Damon

ARTS打卡 - Week 07

teoking

负载均衡方式

羽球

负载均衡

[译] 图解前端-深入理解 Props 和 State

梦见君笑

前端 前端框架 React 前端进阶训练营 漫画编程

技术解读:单集群如何做到2万+规模

FI洞见

大数据 FusionInsight 华为云 大集群

架构师训练营第六周作业--doris临时失效时序图

锦澄

时序图

万字详解加拿大央行CBDC分析报告

CECBC区块链专委会

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