“throws Throwable”是一个好的编程实践吗?

41

过去我读了很多像这样的方法代码:

public Object doSomething() throws Throwable {
    ...
}

这是常见做法吗?

有哪些利弊呢?

throws Trowable 对我来说像是“橙剂”式的处理异常方式。

编辑


  1. 在方法中处理预期的异常

  2. 一次抛出一个未预期的异常

  3. 不关心错误

这样做是否可行?


1
可能这会给你一个解释:https://dev59.com/hHRB5IYBdhLWcg3w1Khe - Serge
“throws Throwable” 在 SDK 和框架定义中更常用。以下是 Android 测试框架中使用它的一个例子:http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.2_r1/android/test/InstrumentationTestCase.java#InstrumentationTestCase - IgorGanapolsky
9个回答

58

不应该抛出 Throwable 。原因如下。

Throwable 是可以抛出的东西的层次结构的顶部,由 Exceptions Errors 组成。由于 Errors 根据定义起源于无法挽救的情况,因此在方法声明中包含它们是没有意义的。这样就只剩下了 Exception

你应该使用 throws Exception 来声明你的方法。


请注意, throws 的范围越小越好。

如果你的方法不生成异常,而是调用其他被声明为 throws Exception 的代码,并且你希望异常在调用堆栈上传播,则将方法声明为 throws Exception 是可以接受的。

如果你的方法是产生异常的,则声明一个更窄的范围,例如 throws IOException,MyProcessingException 等。


3
我想补充一点,如果你向上冒泡的异常可以在代码中某个地方通过代码进行修正,那么声明 throws Exception() 是可以的,但如果它不是代码用来修复自身的东西,那么你应该捕获异常并重新抛出它们作为 RuntimeExceptions。在这种情况下,你不需要在方法上声明它会抛出 RuntimeException,而是应该使用 javadoc 来记录这一点。 - Didier A.

9

这是一个很有深度的问题。这并不只涉及异常处理,还关乎代码的可读性。

这取决于你从哪里获得代码示例。专业人士更倾向于在方法中抛出更具体的异常。主要原因是这样可以使您的API更加易读。例如,如果您的方法抛出Throwable,那基本上意味着任何事情都可能发生,并且您的方法不想处理它。但实际上,只有有限的一些事情可能会发生:

  • 来自您的方法中其他调用所产生的已检查异常
  • 根据您自己的断言故意抛出的已检查异常
  • 未计划好的非检查异常
  • 更全局地属于JVM和环境的错误(java.lang.Error

通过具体说明您想要抛出的异常,可以告诉API使用者应该注意什么。例如,当您使用InputStream时,您会注意到大多数方法都至少会抛出 java.io.IOException,这为您提供了一些有用的信息,让您知道应该注意什么。

编码时,通常情况下,您希望尽可能使API表达力更强。您只有一行代码来展示方法的公共API(即其签名,以及注释),因此您希望它完全表达清楚(返回类型、名称、参数,但也包括抛出的异常)。

至于捕获可抛出对象并打印堆栈跟踪,我建议您不要捕获异常,除非您可以对其进行处理。相反,让它沿着调用堆栈向上滚动,直到某个类捕获它并处理它。有时,它可能会一直升级到您的主类,最终必须捕获并打印堆栈跟踪。基本上,如果您无法对异常进行处理,请让它沿着调用堆栈向上滚动。此外,很少有情况需要您将异常静默处理(即捕获异常但不做任何处理)。这通常会在解决问题时招致麻烦。

这是一篇有趣但有关通用异常处理滥用的文章


2

在某些罕见情况下,抛出 Throwable 是可以接受的。例如,在 Spring AOP 中声明的 @Around 通知通常会抛出一个 Throwable

以下示例摘自Spring AOP文档,原文未作任何修改:

  import org.aspectj.lang.annotation.Aspect;
  import org.aspectj.lang.annotation.Around;
  import org.aspectj.lang.ProceedingJoinPoint;

  @Aspect
  public class AroundExample {

      @Around("com.xyz.myapp.SystemArchitecture.businessService()")
      public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
          // start stopwatch
          Object retVal = pjp.proceed();
          // stop stopwatch
          return retVal;
      }

  }

为什么声明 doBasicProfiling 抛出 Throwable? 因为原方法(即执行连接点)可能会抛出 ErrorRuntimeException 或已检查的异常。 所以,将 doBasicProfiling 声明为抛出 Throwable 是有意义的。


1

这是一个有争议的问题。如果方法抛出太多异常,将导致大量的错误处理代码。有时这并不是我们想要的。

但是,因为我不喜欢在签名中使用太多异常,并不意味着让我们使用所有异常的父类就可以了!这样做是行不通的。

我们可以对异常进行分类,例如BusinessExceptionServiceException,这样如果您有一个业务规则,规定账户的最低余额不能少于100美元,则会生成InsufficientBalance异常,它将是BusinessException的子类。

因此,您的方法将会是这样的:

public Object doSomething() throws BusinessException {

 if(!hasMinimumbalance())
  { 
    throw new InsufficientBalance(ErrorCode);
  }
}

这样做的作用是将相关的异常组合在一起,每当API用户想要检测特定的异常错误时,他就可以这样做,否则可以进行通用错误处理。

这里的核心点是在UI上向用户显示您已经用完了余额,无法提取资金

从更大的方面来说,为了显示可读性强的错误形式,有必要对异常进行分离。


1

从功能上讲,它与throws Exception相当,因为错误是未经检查的。

我认为没有理由声明一个方法抛出Throwable。然而,这并不意味着catch和printStackTrace是一个好的替代方案。

通常情况下,您希望在可以对其进行有意义处理的地方捕获throwables。

抛出您不期望的throwable的代码应该爆炸性地失败,以便您可以看到错误并修复错误。


2
你可以抛出Throwable的子类,但不建议抛出非Exception或Error的子类。 ;) - Peter Lawrey
当然正确。我真的希望在实践中永远不会出现这种情况。 - Buhb
1
Throwable的子类被视为已检查异常,就像Throwable一样(除了Error和RuntimeException的子类)。 - Peter Lawrey
你能抛出java.lang.Throwable本身(而不是子类)吗? - Demi
1
@PeterLawrey的评论澄清了为什么这些声明在功能上并不完全等效;客户端必须包含额外的样板文件来处理已检查的非“Exception”Throwables。 - Judge Mental

1
这是常见的做法吗?
在JDK中很少见。这主要用于处理已检查异常不清楚如何处理的情况。
有什么优缺点?
优点是您可以让代码编译而不必担心已检查的异常。
缺点是您应该处理的异常被忽略了。
难道捕获并打印堆栈跟踪不是更好吗?
未处理的异常通常会被打印,因此捕获它们并没有太大帮助。
当您可以通过捕获异常添加一些价值时,请捕获异常并将其添加到throws子句中,当您无法添加异常时。

0
抛出(和捕获)Throwable(或Exception)通常是不好的编程实践,因为它“覆盖”了您可能想要捕获的任何特定异常。然后您将不得不采用下面这样的丑陋代码:
public void myMethod() throws Throwable {
    if (x) {
        throw new MyException1();
    }
    if (y) {
        throw new MyException2();
    }
}

public void callingMethod() {
    try {
        myMethod();
    }
    catch(Throwable t) {
        if (t instanceof MyException1) {
            // handle exception 1
        }
        else if (t instanceof MyException2) {
            // handle exception 2
        }
        else {
            // handle other exceptions
        }
    }
}

这种写法容易出错(并被CheckStyle标记为代码违规)。最好的做法是像这样编写代码:

public void myMethod() throws MyException1, MyException2 {
    if (x) {
        throw new MyException1();
    }
    if (y) {
        throw new MyException2();
    }
}

public void callingMethod() {
    try {
        myMethod();
    }
    catch(MyException1 e) {
        // handle exception 1
    }
    catch(MyException2 e) {
        // handle exception 2
    }
}

仅通过调用printStackTrace()来处理异常通常不是一个好主意。printStackTrace()将堆栈跟踪发送到标准错误,可能根本不会被读取。更好的选择是使用应用程序的日志记录工具(如log4j)报告异常。即使这样,仅仅记录下来可能还不够。

我的经验法则是:

如果可以在本地处理异常,请这样做。例如,在将字符串解析为整数时,您可以捕获NumberFormatException并返回默认值:

prvate int parseAmount(String amountValue) {
    int amount;
    try {
        amount = Integer.parseInt(amountValue);
    }
    catch(NumberFormatException e) {
        // default amount
        amount = 0;
    }
    return amount;
}

如果您无法在本地处理异常,请考虑是否应该公开抛出的异常类型。如果这种类型是某些模糊的(依赖于实现)类型,则将其包装在自己的通用异常类型中可能是一个好主意:

private Customer getCustomer(int customerId) throws ServiceException {
    try {
        return customerService.getCustomer(customerId);
    }
    catch(CustomerServiceSpaghettiTangledException e) {
        throw new ServiceException("Error calling the customer service", e);
    }
}

这里的'ServiceException'是您创建的Exception子类。Spring还专门为此提供了异常层次结构

通过包装异常,您隐藏了实现细节,使您的服务层更加简单易用。

如果您决定从方法中抛出异常,则需要在调用堆栈的“更高层”处理它。这可以是Web应用程序中的通用错误页面,指出出现了问题并可能提供错误消息或代码。在某些情况下,更高级别的代码可以尝试重试或可能采用替代方法来获取所需的结果。


0
您是在特别询问Throwable吗?如果是的话,那么这不是一个好习惯。它没有为类(方法)用户提供任何有用的信息。

0
我能想到的唯一用例可能是像单元测试这样的测试代码。但亚当的反驳仍然成立:“如果是这样,那么这不是一个好的实践。它并没有为类(方法)用户提供任何有用的信息。”

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接