1. Обзор
Иногда блоки try/catch могут привести к многословным или даже неудобным конструкциям кода.
В этой статье мы сосредоточимся на No Exception , который предоставляет краткие и удобные обработчики исключений.
2. Зависимость Maven
Давайте добавим Без исключений к вашему 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); } }
Мы начинаем с выделения регистратора , а затем вводим 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. Обработка Исключений Без Исключений
4.1. Обработчик ведения журнала по умолчанию
Давайте заменим это на No Exception стандартный обработчик исключений:
@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) ...
В своей самой основной форме No Exception предоставляет нам способ заменить try/catch исключения одной строкой кода. Он выполняет лямбду , которую мы передаем в 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. Предоставление пользовательского сообщения журнала
Возможно, мы захотим использовать другое сообщение, чем “Пойманное исключение по умолчанию”.” Мы можем сделать это, передав Регистратор в качестве первого аргумента и Строку сообщение в качестве второго :
@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) ...
Но что, если мы хотим сделать больше , чем просто регистрировать Исключения , например, вставить запасное значение, когда 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 . Что делать, если мы хотим повторно использовать обработчик с другим поведением?
Давайте расширим No Exception ‘s Обработчик исключений класс и выполним одну из двух вещей в зависимости от типа исключения:
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; } } }
Возвращаясь ложный когда мы видим Ошибка или Исключение RuntimeException мы рассказываем Обработчик исключений чтобы снова бросить. Возвращаясь истинный для всего остального мы указываем, что исключение было обработано.
Во-первых, мы запустим это со стандартным исключением:
@Test public void givenCustomHandler_whenError_thenRethrowError() { CustomExceptionHandler customExceptionHandler = new CustomExceptionHandler(); customExceptionHandler.run(() -> "foo".charAt(5)); }
Мы передаем нашу функцию методу run() в нашем пользовательском обработчике, унаследованном от обработчика исключений :
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. Заключение
С Без исключений мы можем упростить обработку исключений в каждом конкретном случае с помощью одной строки кода.
Код можно найти в этом проекте GitHub .