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();
}
Философия чувствует себя хорошо для меня, но реализация плохо. Есть ли лучший способ обработки завернутых исключений?
связанные вопросы:
Ответы
Ответ 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
. Но я постараюсь избежать этого, если это вообще возможно.