Введение в NoException

Введение в NoException

1. обзор

Иногда блокиtry/catch могут приводить к многословным или даже неудобным конструкциям кода.

В этой статье мы сосредоточимся наNoException which provides concise and handy exception handlers.

2. Maven Dependency

ДобавимNoException к нашемуpom.xml:


    com.machinezoo.noexception
    noexception
    1.1.0

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. Если выдаетсяException, мы регистрируем его:

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)
...

In its most basic form, NoException provides us with a way to replace try/catch/ exceptions with a single line of code. Он выполняет лямбду, которую мы передаем вrun(), и если бросаетсяException, он регистрируется.

4.2. Добавление пользовательскогоLogger

Если мы внимательно посмотрим на вывод, то увидим, что исключения регистрируются как класс регистрации, а не как наш.

Мы можем исправить это, предоставив наш регистратор:

@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. Отправка настраиваемого сообщения журнала

Мы можем захотеть использовать другое сообщение, отличное от стандартного «Перехваченное исключение». We can do this by passing a Logger as the first argument and a String message as the second:с

@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. Указание значения по умолчанию

Exceptions может возвращать результат, заключенный вOptional. Давайте переместим вещи так, чтобы мы могли использовать его для предоставления значения по умолчанию, если цель не работает:

@Test
public void
  givenDefaultValue_whenDefaultNoException_thenCatchAndLogPrintDefault() {
    System.out.println("Result is " + Exceptions
      .log(logger, "Something went wrong:")
      .get(() -> Integer.parseInt("foobar"))
      .orElse(-1));
}

Мы по-прежнему видим нашException:

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‘sExceptionHandler и выполним одно из двух действий в зависимости от типа исключения:

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)

Это исключение зарегистрировано. Давайте попробуем сError:

@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.");
}

И мы видим, чтоError был повторно брошен в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 мы можем упростить обработку исключений в каждом конкретном случае с помощью одной строчки кода.

Код можно найти вthis GitHub project.