检查异常:Java最大的错误(2014)

2020-09-11 22:44:04

倡导者声称,他们确保了从故障中进行检查和恢复。批评者说,“catch”块几乎永远无法从异常中恢复,并且是错误的常见来源。

同时,Java8和lambdas也在这里。在Java世界中,检查异常是否已经过时了?

在90年代中期,Sun的詹姆斯·高斯林提出了一种新的语言。

当时,C++编程要求对每个函数返回进行错误检查。他认为必须有更好的方法,并将“异常”的概念构建到Java中。

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

这是为了鼓励软件可靠性和弹性。有一种意图是从意外情况中“恢复”-成功以外的可预测结果,例如尝试付款时出现的资金不足异常。至于“复苏”到底需要什么,就不那么清楚了。

Java中还包括运行时异常。由于空指针、数据错误和非法状态/访问可能出现在代码中的任何位置,因此将这些设置为RuntimeException的子类型。

运行时异常可以在任何地方抛出,不需要声明,而且更加方便。但是用它们来代替是正确的吗?

这里的关键点是,运行时和检查过的异常在功能上是等价的。如果没有检查过的异常可以做的处理或恢复,那么运行时异常就做不到。

反对“已检查”异常的最大理由是大多数异常无法修复。简单的事实是,我们不拥有崩溃的代码/子系统。“我们看不到实现,我们不对它负责,也不能修复它。

特别有问题的是JDBC(SQLException)和RMI for EJB(RemoteException)领域。与按照最初的“检查异常”概念识别可修复的意外情况不同,这些普遍存在的、实际上无法修复的系统可靠性问题被广泛宣布。

对于任何方法,失败的可能性包括它调用的所有子方法。潜在故障会累积调用树。在方法签名上声明这些不再为开发人员提供特定的本地高亮显示,以查看遍及整个调用树的声明异常。

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

许多开发人员被告知要捕获低级异常,并将它们作为较高级别(应用程序级)检查异常重新抛出。这需要大量的--每个项目2000个以上--不起作用的“接球”块。

吞噬异常、隐藏原因、双重记录和返回‘null’/未初始化的数据都变得很常见。大多数项目可以统计出600多个错误编码或完全错误。

最终,开发人员反抗了大量的“catch”块,而这些块已经成为错误的来源。

然后我们来看看Java8,它具有新的函数编程和特性--比如lambdas、Streams和函数组合。

这些功能构建在泛型之上-参数和amp;返回类型是泛化的,因此可以编写执行公共操作的迭代和流操作(forEach、map、flatMap),而不考虑项目类型。

在Java中不可能提供流操作(例如,使用声明某些检查异常的lambda的流操作(例如,.Stream.map)),&;会将相同的检查异常透明地传递给周围的代码。

这一直是反对检查异常的要点-所有介入代码,在抛出和接收“catch”块之间,都必须知道异常。

将其“包装”在RuntimeException中的解决方法隐藏了异常的原始类型-使原始概念中设想的特定于异常的“catch”块变得毫无用处。

最后,我们可以通过注意Java8中的新“函数接口”没有声明检查异常来概括Java的新理念。

与早期语言相比,Java中的异常在可靠性和错误处理方面提供了重大优势。Java以C/C++无法实现的方式启用了可靠的服务器和业务软件。

在最初的形式中,检查的异常是试图处理意外情况,而不是失败。值得称赞的目标是突出具体的可预测点(无法连接、找不到文件等)&确保开发人员处理这些问题。

最初的概念中从来没有包括的是,强迫宣布一系列无法挽回的系统性失败。这些失败从来都不是正确的,不能被宣布为已检查的异常。

失败通常在代码中是可能的,EJB、web&;Swing/AWT容器已经通过提供最外层的“失败请求”异常处理程序迎合了这种情况。最基本的正确策略是回滚事务&;返回错误。

运行时异常允许对检查的异常进行任何可能的异常处理,但避免了限制性的编码限制。这简化了编码&;使其更容易遵循“早抛、晚接”的最佳实践,即在最外层/最高级别处理异常。

领先的Java框架和影响力现在已经明确地远离了受控异常。Spring、Hibernate和现代Java框架/应用程序供应商只使用运行时异常,这种便利性是它们受欢迎的一个主要因素。

乔什·布洛赫(Java Collection Framework)、罗德·约翰逊(Rod Johnson)、安德斯·海尔斯伯格(Anders Hejlsberg)(C#之父)、加文·金(Gavin King)和斯蒂芬·科尔伯恩(Stephen Colebourn)(JodaTime)等人物都站出来反对已检查的异常。

现在,在Java 8中,λ是向前迈出的最基本的一步,这些语言功能将从内部的函数操作中抽象出“控制流”。正如我们已经看到的,这使得检查异常&;要求“立即声明或处理”已经过时。

对于开发人员来说,注意可靠性和诊断可能的故障点(意外情况)(如文件打开、数据库连接等)总是很重要的。如果我们在这一点上提供良好的错误信息,我们将创建自诊断软件-这是工程成就的巅峰。

但是我们应该用未检查的异常来做这件事,如果我们必须重新抛出,应该总是使用RuntimeException或特定于应用程序的子类。

正如Stephen Colebourn所说,如果您的项目仍然在使用或倡导检查异常,那么您的技能已经过时了5-10年。Java已经向前看了。