在Java中,是使用throws Exception而不是抛出多个特定异常的良好实践?

时间:2009-06-12 13:42:13

标签: java exception exception-handling

在查看Spring MVC框架时,我注意到,除非我误解,否则它的开发人员会抛出异常而不是抛出多个异常。

我意识到这个问题的核心是检查与未检查的异常辩论,避免宗教战争,使用抛出一般异常是一种好习惯吗?

15 个答案:

答案 0 :(得分:18)

不,绝对没有。您应该指定要抛出的异常,以便调用者可以对每个异常执行正确的操作。如果不这样做,“throws Exception”会被链接传递,调用者可以做的最好的事情就是printStackTrace()并死掉。

更新:为了对抗某些“如果我覆盖方法”的反对意见,我会更进一步,并说任何时候你有一个抛出异常的包(而不是从调用者传递异常) ),你应该在该包中声明一个异常类。因此,如果你覆盖我的“addToSchedule()抛出ScheduleConflictException”,你就完全能够将ScheduleConflictException子类化为你需要的东西。

答案 1 :(得分:16)

对于像Spring MVC这样需要开放以适应各种不同用例的库而言,有意义的是,在编写特定应用程序时,您不一定有意义。这是其中一种情况。

如果要引用诸如Controller接口之类的类作为方法签名,例如

handleRequest(HttpServletRequest request, HttpServletResponse response) 
   throws Exception

这很可能是因为从调用Controller的Spring类(如DispatcherServlet)的角度来看,它们并不关心代码调用的Exception类型 - 库代码如{{1只需要知道这个类可能抛出一个Exception,因此能够在一般情况下处理Exception。

换句话说,DispatcherServlet不需要知道控制器可能抛出的异常的特定类型 - 它会将它们中的任何一个视为“错误”。这就是方法签名为DispatcherServlet的原因。

现在,API作者可以使签名使用自定义异常类型throws Exception,但这只会强制您在SpringMvcException方法中强制处理任何已检查的异常类型,简单地包装它们,这是繁琐的制作样板代码。因此,由于Spring的所有内容都旨在使您尽可能简单轻量地进行集成,因此更容易指定接口方法handleRequest

答案 2 :(得分:7)

这是抛出特定异常的问题...假设某人扩展了您的类并希望覆盖您的方法。假设他们的新实现需要抛出不同类型的异常。 (你怎么能预测一个重写方法可能需要抛出的异常?)编写覆盖方法的人只有两个选择:1)自己处理异常(可能是一个糟糕的选择),或2)包装真实其中一个允许的异常类型中的异常并重新抛出。

但是选项2有两个问题。首先,当您将异常转储到日志文件时,您将获得长期丑陋的嵌套异常链。更重要的是,您将失去捕获特定异常的能力。例如,假设重写方法调用另一个与数据库对话的方法,并在结果SQL导致死锁时抛出DeadlockException。重写方法必须捕获此异常,将其包装在一个允许的类型中,然后重新抛出。这使得堆栈中的代码无法捕获并检测DeadlockException。

您的问题最终会进入关于已检查与未检查异常的争论的核心。你可以在Google上找到辩论双方的许多论据。我认为最终,如果您相信已检查的异常,您应该非常清楚方法抛出的异常。如果您不喜欢已检查的异常,则应声明每个方法都抛出异常。我落入后一阵营。

顺便说一句,对于那些不喜欢检查异常的人,我不喜欢在任何地方使用RuntimeException的想法。问题是您可能需要合并使用Exception而不是RuntimeException的第三方库。然后,您的代码将必须从库中捕获所有Exception并将它们包装在RuntimeException中。这造成了一团糟。

所以,如果我再次从头开始一个Java项目,我只是声明每个方法都抛出异常。

答案 3 :(得分:3)

没有

Throwing Exception强制调用代码来捕获异常,这些异常可能由于各种原因而无法完成。

答案 4 :(得分:3)

IMO不抛出异常的最大原因是,如果他们真正有兴趣采取某些行动,它会迫使我们的客户catch(Exception e)

catch(Exception e)的问题是异常,在检查时,也是RuntimeException的超类,因此强制客户端捕获所有RuntimeExceptions 。 / p>

可悲的是,没有捕获(CheckedException e)技术。假设你的糟糕客户端不需要捕获RuntimeExceptions,他们必须执行一个instanceof检查并重新抛出它的RuntimeException。

答案 5 :(得分:2)

恕我直言,所有的设计讨论和建议都说明了一般应该做些什么,并且他们有一个支持他们的原则。但通常这些建议并不适用于所有情况。

在异常处理的情况下,使用已检查异常的想法是,通常以不同方式捕获和管理异常通常是好的,具体取决于您的需要。但是,如果你确定它们都会以同样的方式被捕获和管理,那么一直检查类型是没有意义的。

答案 6 :(得分:1)

在我看来是的,因为这可以让你抛出你想要的任何异常。

随着程序的增长,您可能需要抛出与您原先想到的不同的异常。如果您定义了每个单独的异常类型,则必须修改方法签名,然后修改调用它的所有方法以正确处理新异常。

只使用'throws Exception'允许任何调用方法的方法正确处理你列出的任何方法,但每当你在函数中添加新的异常时,它都不会破坏这些其他方法。 (虽然你应该更新它们以处理新的异常,但这不是必需的。)

答案 7 :(得分:1)

Brian Goetz谈到了其中一些问题here。另请参阅Josh Bloch的Effective Java第2版。他专门讨论了例外情况。此外,Spring Framework的创始人Rod Johnson详细介绍了他对J2EE Design and Development中Java异常处理的看法。你可能会也可能不同意他的异常处理哲学,但至少它可能更有意义,为什么他们做出他们所做的决定。

alt text
(来源:sun.com
alt text

答案 8 :(得分:1)

您需要区分通用代码和更具体的代码。

将函数的返回类型视为类比。如果您正在编写类似“ArrayList”的类,那么您希望它非常通用,因此参数和返回值通常是通用的“对象”。但是当你编写更具体的应用程序代码时,比如“getRetiredEmployees”函数,返回Object将是一个非常糟糕的主意。您更可能想要返回Employee []或类似的东西。

当然,像Spring这样的框架会期望泛型异常,因为它不知道你的特定应用程序会抛出什么异常。 Spring的作者无法知道您将抛出“EmployeeNotInSpecifiedDepartment”异常或其他任何内容。他们怎么样?但是如果您正在编写sendPensionChecks函数并调用getRetiredEmployees,那么您可以合理地期望知道该函数可能抛出的特定异常,以及您应该如何处理它们。

Clint Miller提出了一个关于不知道如何扩展课程的有效观点。我承认这是使您的例外具体化的问题。但从那里开始“只是让一切都成为一般的例外”就太容易放弃了。这就像是说,因为有一天某人可能会扩展我们的getRetiredEmployees函数,在某些情况下会返回EmployeeSpouse和Employee,因此我们应该放弃并返回类型为Object。如果这是您在内部使用并且您可以控制的代码,那么这是一个非问题:如果您需要向函数添加新的异常,则添加它。如果这是您要向全世界发布的API,那么是的,问题就更棘手了。我要说的是,一般的解决方案是尝试合理地思考哪些例外是有意义的并将它们全部包括在内,即使它们目前并非全部实施。如果你的一个客户正在做一些完全没有预料到的事情,那么,这是一个没有简单答案的问题。

让一切都变得通用并不是正确的答案,因为它使一切都难以理解,难以维护,并且难以验证准确性。

答案 9 :(得分:1)

我实际上已从编译器中删除了已检查的异常。

这比人们猜测的更好。

目前,配置错误的很大一部分显示为未处理的异常,但阅读第一行文字会使真正的问题变得明显。

我一直在计划修改顶级异常处理程序,以显示错误消息框,而不是几个异常类型(主要是IOException)的未处理异常框。我已经检查了一个ErrorMessage异常。

答案 10 :(得分:1)

列出每个例外。这样:
 *明确定义可能出现的问题  *班级用户可以完全了解他们应该处理的例外情况。

此外,最重要的方法应该做同样的事情,但是以不同的方式。因此,你怎么能有另一个异常,它不是已经抛出的异常的子类?你不应该。 此外,你不应该在子类中抛出新的异常,因为子类应该能够替换它的父类,因此如果它被作为“p”传递给

public void method(Parent p);

“方法”如何知道它必须处理一些新的异常?它不应该。它意味着不正确的设计(父类或子类)。

答案 11 :(得分:1)

一般来说,“抛出异常”很少是一个好主意。

在运行计算机代码时,运行时错误大致可分为两大类。有些是错误代码的结果 - 可能是被调用方法本身的逻辑错误,或者传递给它的错误输入。然后是那些由于程序员无法控制的情况而导致的问题。也许系统正在尝试打开另一个进程刚刚删除的文件,或者通过刚刚关闭的网络访问远程服务器。

如果发生第一种异常,您的程序没有理由抓住并继续。它应该干净利落地显而易见,明确发生了什么,这样你就可以得到警告,解决出错的问题。

第二种例外是你想捕获的那种。您不知道在任何给定的运行中是否会出现这些问题,但您可以很好地了解代码中可能出现的WHERE - 基本上,只要您需要依赖程序控制之外的资源。

声明抛出的异常本质上是方法的作者与使用它的客户之间的契约,告诉您应该会出现什么样的错误。毯子“抛出异常”相当于抛弃你的手并说客户必须准备好任何事情出错,甚至在早期调试中已经找到并修复的问题。呼叫者必须要么抓住这些例外 - 并且通常只是吃它们并继续,因为合同范围很广,以至于他们根本不知道问题出在哪里 - 或者将它们放在上面,迫使其他人处理相同的问题。

答案 12 :(得分:0)

是和否。是的,只是抛出或捕获一个异常并且完成它是“方便的”,但是在任何尝试从异常中优雅地恢复它确实会伤害你。某些例外可以而且应该优雅地处理,这样你的程序就不会死于可怕的死亡。如果一个方法只是抛出异常,我不知道出了什么问题,所以我没有任何希望修复和从问题中恢复。

一个可能的错误和解决方案包括:

解析整数时的

- NumberFormatException。我们可以有一个默认值

最重要的是,我认为保持个别例外是一种更好的做法。

答案 13 :(得分:0)

抛出异常也会导致J2EE中的事务管理出现大量问题。使用catch异常是纯粹的邪恶!

我没有给你奖励小猫!

答案 14 :(得分:0)

棘手。

理想情况下,指定异常构成合同的一部分,告诉用户可能会出现什么异常。这有助于他们决定要处理什么,以及什么不处理。

编写框架或库时虽然无法说 -

abstract T func(...)抛出A,B,C;

因为任何扩展func(...)容器的类都可能会覆盖func。

我自己不得不使用'抛出异常'的宽松规范,但作为经验法则

  • 为私有方法提供严格的异常规范(在本讨论的上下文中,我认为方法是私有的,因为它们的契约是不可变的),对所有其他方法都是松散的规范。