【问题标题】:Is "throws Throwable" good practice是“Throws Throwable”的好习惯吗
【发布时间】:2012-09-24 11:45:21
【问题描述】:

在过去,我会使用以下方法阅读大量代码:

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

这样做是常见的做法吗?

有什么优点和缺点?

throws Trowable 在我看来就像完成异常事务的“Agent Orange”方式

编辑


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

  2. 抛出意外异常(一一)

  3. 不关心错误

这是要走的路吗?

【问题讨论】:

标签: java


【解决方案1】:

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

Throwable 是可以抛出的事物层次结构的顶部,由ExceptionsErrors 组成。由于Errors 的定义是由无法挽救的条件引起的,因此将它们包含在您的方法声明中是没有意义的。就剩下Exception

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


请注意throws 的范围越窄越好。

如果您的方法不生成异常,而是调用声明为 throws Exception 的其他代码并且您希望异常渗透到调用堆栈中,则可以将您的方法声明为 throws Exception

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

【讨论】:

  • 我要补充一点,如果您正在渗透的异常可以通过代码在链上的某个点进行更正,那么声明 throws Exception() 是可以的,但如果不是这样的话被代码用来修复自身,那么您应该捕获异常并将它们作为 RuntimeExceptions 重新抛出。在这种情况下,您不要在方法上声明它会引发运行时异常,而是用 javadoc 记录它。
【解决方案2】:

这是一个加载的问题。这与其说是关于异常处理,不如说是关于代码的可读性。

这取决于您从哪里获得代码示例。专业人士在抛弃一种方法时更喜欢更具体。主要原因是它使您的 API 更具可读性。例如,如果您的方法抛出 Throwable,这基本上意味着任何事情都可能发生,而您的方法无论如何都不想处理它。但实际上,只会发生有限数量的事情:

  • 您在方法中进行的其他调用导致的任何已检查异常
  • 根据自己的断言故意抛出的任何已检查异常
  • 任何你没有计划的未经检查的异常
  • 错误 (java.lang.Error) 对 JVM 和环境更具有全局性

通过明确说明您要抛出的异常,您是在告诉 API 的用户他们应该注意什么。例如,当您使用InputStream 时,您会注意到大多数方法至少会抛出java.io.IOException,这为您提供了一些关于您应该注意什么的有用信息。

在编码时,作为一般规则,您希望尽可能保持 API 的表现力。你基本上有一行代码来显示一个方法的公共 API(即它的签名,我猜也是注释),所以你希望它完全具有表现力(返回类型、名称、参数,还有抛出的异常)。

就捕获可抛出对象和打印堆栈跟踪而言,我会说您不应该捕获异常,除非您可以对此采取措施。相反,让它滚动调用堆栈,直到某个类抓住它来做一些事情。有时,它可能会一直滚动到您的主类,我想它必须抓住它并打印堆栈跟踪作为最后的手段。基本上,如果你不能对异常采取行动,那就让它进入调用堆栈。此外,您极少会发现自己处于应该使异常保持沉默的情况(即捕获它但不采取任何措施)。当需要解决问题时,这通常会引发问题。

这里是一个有趣但有趣的article,关于一般异常处理的滥用。

【讨论】:

    【解决方案3】:

    在极少数情况下,抛出Throwables 是可以接受的。例如,Spring AOP 中的 @Around 建议通常声明为抛出 Throwable

    以下示例逐字复制自Spring AOP docs

      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 才有意义。

    【讨论】:

      【解决方案4】:

      在功能上,它等同于throws Exception,因为错误是未经检查的。

      我认为没有理由声明一个抛出 Throwable 的方法。但是,这并不意味着 catch 和 printStackTrace 是一个不错的选择。

      通常,您希望捕获可以对它们做一些明智的事情的 throwable。

      抛出你没想到的 throwable 的代码应该会爆炸,这样你就可以看到错误并修复错误。

      【讨论】:

      • 您可以抛出 Throwable 的子类,它不是异常或错误,但不建议这样做。 ;)
      • 正确,当然。我真的希望这在实践中并不重要。
      • Throwable 的子类与 Throwable 一样检查(Error 和 RuntimeException 的子类除外)
      • 你能抛出 java.lang.Throwable 本身(不是子类)吗?
      • @PeterLawrey 的 cmets 阐明了为什么这些声明在功能上并不完全等效;客户端必须包含额外的样板来处理检查的非ExceptionThrowables
      【解决方案5】:

      这样做是常见的做法吗?

      在 JDK 中很少见。这主要在不清楚如何处理已检查的异常时使用。

      有什么优点和缺点?

      优点是你可以编译你的代码而不用担心检查异常。s

      缺点是你应该处理的异常被忽略了。

      catch 和 printStackTrace() 不是更好吗?

      通常会打印未处理的异常,因此捕获它们并没有多大帮助。

      当您可以通过这样做添加一些值时,您应该捕获异常,并在您不能时将异常添加到throws 子句。

      【讨论】:

        【解决方案6】:

        这确实是一个值得商榷的问题。 让方法抛出太多异常将导致大量错误处理代码。有时不是故意的。

        但是因为我不喜欢签名中的太多异常并不意味着让我们使用所有异常的父级并且我们完成了!它不会起作用。

        可以做的是对异常进行分类,例如BusinessException,ServiceException,这样如果您有一条业务规则说minimum balance in account can not be less than say 100$,那么将生成InsufficientBalance异常,它将是BusinessException的子级

        所以你的方法会像

        public Object doSomething() throws BusinessException {
        
         if(!hasMinimumbalance())
          { 
            throw new InsufficientBalance(ErrorCode);
          }
        }
        

        这将做的是与俱乐部相关的异常一起,只要 API 用户想要检测异常特定的错误,他就可以做到,否则通用错误处理是可能的。

        这里的核心点是在 UI 上,您应该向用户显示 您的余额已用完且无法取款

        您可以从更大的方面说,为了显示人类可读的错误形式,确实有必要分离异常。

        【讨论】:

          【解决方案7】:

          您是在专门询问 Throwable 吗?如果是这样,那么这不是一个好习惯。它不向类(方法)用户提供任何有用的信息。

          【讨论】:

            【解决方案8】:

            抛出(和捕获)Throwable(或异常)通常是不好的做法,因为它会“掩盖”您可能想要捕获的任何特定异常。那么你将不得不诉诸如下丑陋:

            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)来报告异常。即便如此,仅仅记录它可能还不够。

            我的经验法则是:

            如果您可以在本地处理异常,请执行此操作。例如,当将 String 解析为 Integer 时,您可以捕获 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 还专门为此目的提供了exception hierarchy

            通过包装异常,您可以隐藏实现细节,让您的服务层更易于使用。

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

            【讨论】:

              【解决方案9】:

              我能想到的唯一用例是单元测试之类的测试代码。但亚当的对立面仍然是“如果是这样,那么这不是一个好的做法。它没有为类(方法)用户提供任何有用的信息。”

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2016-01-03
                • 2016-06-27
                • 2021-07-17
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多