java:包装已检查异常的标准方法

Jas*_*n S 17 java exception guava

我有一个相当详细的问题,关于包装已检查异常的正确方法,以及Guava的方式.(道歉,但我想让我的思考过程失败)


标准的Runnable接口如下所示:

public interface Runnable
{
   public void run();
}
Run Code Online (Sandbox Code Playgroud)

哪里run()不能抛出一个检查过的异常.

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

所以有一段时间我用的是:

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

然后我可以在上层处理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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我可以这样做:

/* 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());
}
Run Code Online (Sandbox Code Playgroud)

它看起来很棒.

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

这让我想到,也许Guava在某处有一个标准的WrappedException类,因为我现在默认将Guava包含为依赖.所以我可以做到

throw new WrappedException(e);
Run Code Online (Sandbox Code Playgroud)

要么

throw Exceptions.wrap(e);
Run Code Online (Sandbox Code Playgroud)

要么

Exceptions.rethrow(e);
Run Code Online (Sandbox Code Playgroud)

我刚才看了一下四周番石榴,发现将Throwable其中有Throwables.propagate()看起来类似,但它只是包装检查的异常RuntimeException,而不是RuntimeException的一个特殊子类.

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

如果我有一个包装NastyException的RuntimeException包装NullPointerException,包装RuntimeException不会添加信息值,我不关心它,所以我记录的错误将是NastyException.

如果我有一个包含NastyException的IllegalArgumentException,则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();
}
Run Code Online (Sandbox Code Playgroud)

这种理念适合我,但实施感觉很糟糕.有没有更好的方法来处理包装异常?


相关问题:

Eti*_*veu 7

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

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

话虽如此,代码并不是很复杂,我相信你可以在你的代码库中做类似的事情.不需要为此添加Spring依赖项.

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

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

Iterables.filter(Throwables.getCausalChain(e), IOException.class));
Run Code Online (Sandbox Code Playgroud)

编辑:

我更多地考虑了你的问题,我认为你不应该真的担心用特定的"WrapperException"类型包装你的异常.您应该使用最有意义的东西来包装它们:要么是简单的RuntimeException(Guava Throwables.propagate()可能在那里有用),RuntimeException带有额外的错误消息,或者在适当的时候使用更有意义的异常类型.

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