【问题标题】:Standard method of wrapping checked Java exceptions包装已检查 Java 异常的标准方法
【发布时间】:2011-06-08 21:24:15
【问题描述】:

我有一个相当详细的问题,关于包装检查异常的正确方法,以及 Guava 的做法。 (抱歉篇幅太长,但我想让我的思考过程慢下来)


标准的Runnable 接口如下所示:

public interface Runnable
{
   public void run();
}

run() 不能抛出检查异常。

因此,如果我想要一个 Runnable 用于包装引发检查异常的任务,并且我打算让调用 Runnable.run() 的东西处理这些异常,而不是在 Runnable.run() 本身中,我有将异常包装在未经检查的异常中。

所以有一段时间我在使用:

Runnable r = new Runnable {
   @Override public void run()
   {
       try {
          doNastyStuff();
       }
       catch (NastyException e)
       {
          throw new RuntimeException(e);
       }
   }      
};

然后我可以在上层处理RuntimeException。除了我想,我真正想要的是单独处理一个包装的异常,因为我知道它的语义是包装一个检查的异常,所以我写了这个帮助类:

/**
 * Wrapped exception: the purpose of this is just to wrap another exception,
 * and indicate that it is a wrapped exception
 */
public class WrappedException extends RuntimeException
{
    /**
     * @param t any throwable
     */
    public WrappedException(Throwable t)
    {
        super(t);
    }
}

然后我可以这样做:

/* place that produces the exception */
...
catch (NastyException e)
{
   throw new WrappedException(e);
}

...
/* upper level code that calls Runnable.run() */
try
{
   ...
   SomeOtherNastyCode();
   r.run();
   ...
}
catch (SomeOtherNastyException e)
{
   logError(e);
}
catch (WrappedException e)
{
   logError(e.getCause());
}

而且看起来效果很好。

但是现在我在想,如果我想在一个库以及使用该库的应用程序中使用它,现在它们都依赖于WrappedException,所以它应该真的在一个基础库中我可以包括任何地方。

这让我想到,也许 Guava 在某个地方有一个标准的 WrappedException 类,因为我现在默认将 Guava 作为依赖项。所以我只能这样做

throw new WrappedException(e);

throw Exceptions.wrap(e);

Exceptions.rethrow(e);

我刚刚在 Guava 中环顾四周,发现 ThrowablesThrowables.propagate() 看起来相似,但它只是将检查的异常包装在 RuntimeException 中,而不是 RuntimeException 的特殊子类。

哪种方法更好?与RuntimeException 相比,我不应该使用特殊的WrappedException 吗?我的顶级代码想知道增加信息价值的最顶层异常。

如果我有一个 RuntimeException 包装了一个 NastyException 包装了一个 NullPointerException,那么包装 RuntimeException 不会增加信息价值,我不在乎它,所以我会记录错误将是NastyException

如果我有一个包含NastyExceptionIllegalArgumentException,则IllegalArgumentException 通常会添加信息价值。

所以在我执行错误记录的顶级代码中,我必须执行以下操作:

catch (RuntimeException re)
{
   logError(getTheOutermostUsefulException(re));
}

/** 
 * heuristics to tease out whether an exception
 * is wrapped just for the heck of it, or whether
 * it has informational value
 */
Throwable getTheOutermostUsefulException(RuntimeException re)
{        
   // subclasses of RuntimeException should be used as is
   if (re.getClass() != RuntimeException)
      return re;
   // if a runtime exception has a message, it's probably useful
   else if (re.getMessage() != null)
      return re;
   // if a runtime exception has no cause, it's certainly
   // going to be more useful than null
   else if (re.getCause() == null)
      return re;
   else
      return re.getCause();
}

这种理念对我来说是正确的,但实施起来感觉很糟糕。有没有更好的方法来处理包装的异常?


相关问题:

【问题讨论】:

  • 除了日志之外,您的顶级处理代码还做什么?当您记录链式异常的堆栈跟踪时,无论如何您都会获得整个跟踪,因此它是否被包装并不重要。
  • 如果你只是在记录,无论如何你都会记录一切,如果你在做一些实际的处理,那么只捕获或尝试解开你可以处理的异常。 getCause 是您解包所需的全部内容。
  • @artbristol, @trutheality:是的,我想过在 RuntimeException 上调用 logError()。但是我的应用程序中logError() 的另一部分是向用户显示,我确实需要显示最有用的消息而没有任何噪音;他们不会通过异常堆栈来找出问题所在,而是会查看消息,如果他们能自己找出问题,那就太好了,否则我会收到“我遇到了 RuntimeException,我该怎么办?”
  • 那么将这段代码放在logError() 中提取消息给用户的部分可能是有意义的。

标签: java exception guava


【解决方案1】:

Spring 是我所知道的唯一一个具有相对相似功能的库。它们有嵌套异常:NestedRuntimeExceptionNestedCheckedException。这些异常具有有用的方法,例如getMostSpecificCause()contains(Class exType)。他们的getMessage() 方法返回原因的消息(如果包装异常已经有消息,则附加该消息)。

它用于 Spring 的数据访问异常层次结构中。这个想法是每个数据库供应商在他们的 JDBC 驱动程序中公开不同的异常。 Spring 捕获这些并将它们转换为更通用的DataAccessExceptions。这样做的另一个好处是检查的异常会自动转换为运行时异常。

话虽如此,这并不是很复杂的代码,我相信您可以在您的代码库中执行类似的操作。无需为此添加 Spring 依赖项。

如果我是你,我不会试图过早地“解开”异常,除非你真的可以立即处理它们。我会让它们冒泡(包装或不包装),使用全局 ExceptionHandler 来查看它们的因果链,找到第一个“有意义的”异常,并提取智能错误消息。当无法这样做时,我会打印“技术错误”并将整个堆栈跟踪添加到错误消息的详细信息或某种日志中,以用于错误报告目的。然后,您将修复错误和/或引发更有意义的异常。

Guava 的 Throwables.getCausalChain() 也可能有助于简化异常处理:

Iterables.filter(Throwables.getCausalChain(e), IOException.class));

编辑:

我对您的问题进行了更多思考,我认为您不应该真正担心使用特定的“WrapperException”类型包装您的异常。您应该使用最有意义的方式来包装它们:简单的RuntimeException(Guava 的Throwables.propagate() 可能在那里有用)、带有附加错误消息的RuntimeException,或者在适当的时候使用更有意义的异常类型。

Java 的因果链机制无论如何都会让您找到根本原因。您不必担心代码中到处都有异常包装。编写一个全局异常处理程序来管理它,并在其他任何地方使用标准异常冒泡。

【讨论】:

    【解决方案2】:

    一般来说,最好避免用未经检查的异常包装已检查的异常(请参阅herehere)。一些绕过它的方法:

    1. 使用 Callable 代替 Runnable
    2. 像 Bohemian 建议的那样使用 Executors 框架
    3. 子类Runnable(或您正在使用的任何接口/类)并添加一些方法来在事后运行期间检查异常,可能是public Exception getRunException() 或类似方法。

    如果你必须包装一个已检查的异常,我认为最好的方法是就像你已经这样做了,定义一个RuntimeException 的子类。但如果可能的话,我会尽量避免。

    【讨论】:

    • 我认为 Runnable 只是一个例子来说明包装检查异常的必要性。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-02-12
    • 1970-01-01
    • 2010-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多