Java: стандартный метод упаковки проверенных исключений - PullRequest
17 голосов
/ 09 июня 2011

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

Я только что оглянулся в Гуаве и нашел 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'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();
}

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


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

Ответы [ 2 ]

7 голосов
/ 09 июня 2011

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

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

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

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

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

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

Редактировать:

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

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

0 голосов
/ 09 июня 2011

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...