1. Обзор
Иногда блоки try/catch
могут привести к многословным или даже неуклюжим конструкциям кода.
В этой статье мы сосредоточимся на NoException
, который предоставляет краткие и удобные обработчики исключений.
2. Зависимость от Maven
Давайте добавим NoException
в наш pom.xml
:
<dependency>
<groupId>com.machinezoo.noexception</groupId>
<artifactId>noexception</artifactId>
<version>1.1.0</version>
</dependency>
3. Стандартная обработка исключений
Начнем с часто встречающейся идиомы:
private static Logger logger = LoggerFactory.getLogger(NoExceptionUnitTest.class);
@Test
public void whenStdExceptionHandling_thenCatchAndLog() {
try {
logger.info("Result is " + Integer.parseInt("foobar"));
} catch (Throwable exception) {
logger.error("Caught exception:", exception);
}
}
Мы начинаем с выделения Logger
, а затем вводим блок try
. Если возникает исключение
, мы регистрируем его:
09:29:28.140 [main] ERROR c.b.n.NoExceptionUnitTest
- Caught exception
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...
4. Обработка исключений с помощью NoException
4.1. Обработчик ведения журнала по умолчанию
Давайте заменим это стандартным обработчиком исключений NoException
:
@Test
public void whenDefaultNoException_thenCatchAndLog() {
Exceptions
.log()
.run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}
Этот код дает нам почти тот же результат, что и выше:
09:36:04.461 [main] ERROR c.m.n.Exceptions
- Caught exception
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...
В своей самой простой форме NoException
предоставляет нам способ заменить try/catch
/exceptions одной строкой кода. Он выполняет лямбду, которую мы передаем в run()
, и если возникает исключение
, оно регистрируется.
4.2. Добавление пользовательского регистратора
Если мы внимательно посмотрим на вывод, мы увидим, что исключения регистрируются как класс ведения журнала, а не как наш.
Мы можем исправить это, предоставив наш регистратор:
@Test
public void whenDefaultNoException_thenCatchAndLogWithClassName() {
Exceptions
.log(logger)
.run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}
Что дает нам этот вывод:
09:55:23.724 [main] ERROR c.b.n.NoExceptionUnitTest
- Caught exception
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...
4.3. Предоставление пользовательского сообщения журнала
Мы можем захотеть использовать сообщение, отличное от сообщения по умолчанию «Caught Exception». Мы можем сделать это, передав Logger
в качестве первого аргумента и сообщение String
в качестве второго :
@Test
public void whenDefaultNoException_thenCatchAndLogWithMessage() {
Exceptions
.log(logger, "Something went wrong:")
.run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}
Что дает нам этот вывод:
09:55:23.724 [main] ERROR c.b.n.NoExceptionUnitTest
- Something went wrong:
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...
Но что, если мы хотим сделать больше, чем просто регистрировать Exceptions
, например, вставить резервное значение, когда parseInt()
терпит неудачу?
4.4. Указание значения по умолчанию
Исключения
могут возвращать результат, завернутый в необязательный
. Давайте переместим вещи, чтобы мы могли использовать его для предоставления значения по умолчанию, если цель терпит неудачу:
@Test
public void
givenDefaultValue_whenDefaultNoException_thenCatchAndLogPrintDefault() {
System.out.println("Result is " + Exceptions
.log(logger, "Something went wrong:")
.get(() -> Integer.parseInt("foobar"))
.orElse(-1));
}
Мы все еще видим наше исключение
:
12:02:26.388 [main] ERROR c.b.n.NoExceptionUnitTest
- Caught exception java.lang.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...
Но мы также видим, что наше сообщение также выводится на консоль:
Result is -1
5. Создание пользовательского обработчика ведения журнала
На данный момент у нас есть хороший способ избежать повторения и сделать код более читабельным в простых сценариях try/catch/log .
Что, если мы хотим повторно использовать обработчик с другим поведением?
Давайте расширим класс NoException
ExceptionHandler
и выполним одно из двух действий в зависимости от типа исключения:
public class CustomExceptionHandler extends ExceptionHandler {
Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);
@Override
public boolean handle(Throwable throwable) {
if (throwable.getClass().isAssignableFrom(RuntimeException.class)
|| throwable.getClass().isAssignableFrom(Error.class)) {
return false;
} else {
logger.error("Caught Exception", throwable);
return true;
}
}
}
Возвращая false
, когда мы видим Error
или RuntimeException
, мы сообщаем ExceptionHandler
о повторном генерировании. Возвращая true
для всего остального, мы указываем, что исключение было обработано.
Во-первых, мы запустим это со стандартным исключением:
@Test
public void givenCustomHandler_whenError_thenRethrowError() {
CustomExceptionHandler customExceptionHandler = new CustomExceptionHandler();
customExceptionHandler.run(() -> "foo".charAt(5));
}
Мы передаем нашу функцию методу run()
в нашем пользовательском обработчике, унаследованном от ExceptionHandler:
18:35:26.374 [main] ERROR c.b.n.CustomExceptionHandler
- Caught Exception
j.l.StringIndexOutOfBoundsException: String index out of range: 5
at j.l.String.charAt(String.java:658)
at c.b.n.CustomExceptionHandling.throwSomething(CustomExceptionHandling.java:20)
at c.b.n.CustomExceptionHandling.lambda$main$0(CustomExceptionHandling.java:10)
at c.m.n.ExceptionHandler.run(ExceptionHandler.java:1474)
at c.b.n.CustomExceptionHandling.main(CustomExceptionHandling.java:10)
Это исключение регистрируется. Попробуем с ошибкой
:
@Test(expected = Error.class)
public void givenCustomHandler_whenException_thenCatchAndLog() {
CustomExceptionHandler customExceptionHandler = new CustomExceptionHandler();
customExceptionHandler.run(() -> throwError());
}
private static void throwError() {
throw new Error("This is very bad.");
}
И мы видим, что ошибка
была повторно выброшена в main()
, а не зарегистрирована:
Exception in thread "main" java.lang.Error: This is very bad.
at c.b.n.CustomExceptionHandling.throwSomething(CustomExceptionHandling.java:15)
at c.b.n.CustomExceptionHandling.lambda$main$0(CustomExceptionHandling.java:8)
at c.m.n.ExceptionHandler.run(ExceptionHandler.java:1474)
t c.b.n.CustomExceptionHandling.main(CustomExceptionHandling.java:8)
Итак, у нас есть многоразовый класс, который можно использовать во всем проекте для единообразной обработки исключений.
6. Заключение
С помощью NoException
мы можем упростить обработку исключений в каждом конкретном случае с помощью одной строки кода.
Код можно найти в этом проекте GitHub .