Подтвердить что ты не робот

Java: стандартный метод обертывания проверенных исключений

У меня есть довольно подробный вопрос о правильном способе обернуть проверенное исключение и то, как это делает 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 отдельно, так как я знаю, что его семантика заключается в переносе проверенного исключения, поэтому я написал этот вспомогательный класс:

/**
 * 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, поэтому он действительно должен быть в базовой библиотеке, которую я могу включая всюду.

Что заставляет меня думать, может быть, у Гуавы есть класс WrappedException где-то, так как теперь я включаю Guava как зависимость по умолчанию. Поэтому я могу просто сделать

throw new WrappedException(e);

или

throw Exceptions.wrap(e);

или

Exceptions.rethrow(e);

Я просто посмотрел в Гуаву и нашел Throwables, у которого Throwables.propagate(), который похож, но он просто переносит проверенные исключения в RuntimeException, а не специальный подкласс RuntimeException.

Какой подход лучше? Должен ли я использовать специальное исключение WrappedException по сравнению с RuntimeException? Мой код верхнего уровня хочет знать самое верхнее исключение, которое добавляет информационное значение.

Если у меня есть RuntimeException, которое обертывает исключение NastyException, которое обертывает исключение NullPointerException, обертывание RuntimeException не добавляет информационное значение, и я не забочусь об этом, поэтому ошибка, которую я регистрировал бы, была бы NastyException.

Если у меня есть исключение IllegalArgumentException, которое обертывает NastyException, исключение 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 probably useful
   else if (re.getMessage() != null)
      return re;
   // if a runtime exception has no cause, it certainly
   // going to be more useful than null
   else if (re.getCause() == null)
      return re;
   else
      return re.getCause();
}

Философия чувствует себя хорошо для меня, но реализация плохо. Есть ли лучший способ обработки завернутых исключений?


связанные вопросы:

4b9b3361

Ответ 1

Spring - единственная библиотека, которую я знаю с чем-то относительно похожим. У них есть вложенные исключения: NestedRuntimeException и NestedCheckedException, Эти исключения имеют полезные методы, такие как getMostSpecificCause() или contains(Class exType). Их метод getMessage() возвращает сообщение причины (оно добавляется, если в обморочном исключении уже есть сообщение).

Он используется в иерархии исключений доступа к данным Spring. Идея заключается в том, что каждый поставщик базы данных предоставляет различные исключения в своих драйверах JDBC. Spring ловит их и переводит их в более общий DataAccessExceptions. Другим преимуществом этого является то, что проверенные исключения автоматически преобразуются в исключения во время выполнения.

Говоря это, это не очень сложный код, и я уверен, что вы могли бы сделать что-то подобное в своей базе кода. Не нужно добавлять зависимостей Spring только для этого.

Если бы я был вами, я бы не стал слишком быстро "раскручивать" исключения, если только вы не сможете справиться с ними сразу же и там. Я бы разрешил им пузыриться (завернутый или нет) с глобальным ExceptionHandler, который будет смотреть на свою причинную цепочку, найти первое "осмысленное" исключение и извлечь интеллектуальное сообщение об ошибке. Если вы этого не сделаете, я просто напечатаю "техническую ошибку" и добавлю всю трассировку стека в деталях сообщения об ошибке или в каком-то журнале, для целей отчетов об ошибках. Затем вы исправили ошибку и/или выбросили более значимое исключение.

Guava Throwables.getCausalChain() также может представлять интерес для упрощения обработки исключений:

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

Edit:

Я подумал немного больше о вашей проблеме, и я думаю, вы не должны беспокоиться об обертывании своих исключений с помощью определенного типа WrapperException. Вы должны обернуть их, используя то, что имеет наибольший смысл: либо простой RuntimeException (Guava Throwables.propagate() может быть там полезен), a RuntimeException с дополнительным сообщением об ошибке, либо более значимый тип исключения, если это необходимо.

Механизм причинно-следственной цепи Java позволит вам в любом случае перейти к первопричине. Вам не нужно беспокоиться об обтекании исключения в вашем коде. Создайте глобальный обработчик исключений, чтобы управлять им, и используйте стандартную буксировку исключений везде.

Ответ 2

В общем, лучше избегать обертывания проверенных исключений с неконтролируемым исключением (см. здесь и здесь). Некоторые способы обойти это:

  • Используйте Callable вместо Runnable
  • Используйте фреймворк Executors, например, предложенный Bohemian
  • Подкласс Runnable (или любой другой интерфейс/класс, который вы используете) и добавьте способ проверки исключений во время запуска после факта, возможно, метод public Exception getRunException() или аналогичный.

Если вы должны обернуть проверенное исключение, я думаю, что лучший способ сделать это, как вы уже это сделали, определить подкласс RuntimeException. Но я постараюсь избежать этого, если это вообще возможно.