1. Обзор
В этой статье мы рассмотрим функциональный способ обработки ошибок, отличный от стандартного try-catch блока.
Мы будем использовать класс Try из библиотеки Var , который позволит нам создать более свободный и сознательный API, встроив обработку ошибок в обычный поток обработки программ.
Если вы хотите получить дополнительную информацию о Var, ознакомьтесь с этой статьей .
2. Стандартный способ обработки исключений
Допустим, у нас есть простой интерфейс с методом call () , который возвращает Ответ или выдает ClientException , который является проверяемым исключением в случае сбоя:
public interface HttpClient { Response call() throws ClientException; }
Response – это простой класс с одним полем id :
public class Response { public final String id; public Response(String id) { this.id = id; } }
Допустим, у нас есть служба, которая вызывает этот HttpClient, затем нам нужно обработать это проверенное исключение в стандартном try-catch блоке:
public Response getResponse() { try { return httpClient.call(); } catch (ClientException e) { return null; } }
Когда мы хотим создать API, который свободно работает и написан функционально, каждый метод, который создает проверенные исключения, нарушает поток программы и наш программный код
В идеале мы захотим иметь специальный класс, который инкапсулирует состояние результата ( успех или неудача ), а затем мы сможем цеплять операции в соответствии с этим результатом.
3. Обработка Исключений С Помощью Try
Библиотека Var предоставляет нам специальный контейнер, представляющий вычисление, которое может либо привести к исключению, либо успешно завершить .
Заключительная операция внутри Try object дал нам результат, который является либо Успехом , либо Неудачей. Затем мы можем выполнять дальнейшие операции в соответствии с этим типом.
Заключительная операция внутри Try object дал нам результат, который является либо Успехом
public class VavrTry { private HttpClient httpClient; public TrygetResponse() { return Try.of(httpClient::call); } // standard constructors }
Важно отметить, что тип возврата Try. Когда метод возвращает такой тип результата, нам нужно правильно обработать его и иметь в виду, что этот тип результата может быть Success или Failure , поэтому нам нужно явно обработать это во время компиляции.
3.1. Успешная обработка
Давайте напишем тестовый случай, который использует наш класс Vavr в случае, когда HttpClient возвращает успешный результат. Метод GetResponse() возвращает Try объект. Поэтому мы можем вызвать на нем метод map () , который выполнит действие на Response только тогда, когда Попробуйте будет иметь Успех тип:
@Test public void givenHttpClient_whenMakeACall_shouldReturnSuccess() { // given Integer defaultChainedResult = 1; String id = "a"; HttpClient httpClient = () -> new Response(id); // when Tryresponse = new VavrTry(httpClient).getResponse(); Integer chainedResult = response .map(this::actionThatTakesResponse) .getOrElse(defaultChainedResult); Stream stream = response.toStream().map(it -> it.id); // then assertTrue(!stream.isEmpty()); assertTrue(response.isSuccess()); response.onSuccess(r -> assertEquals(id, r.id)); response.andThen(r -> assertEquals(id, r.id)); assertNotEquals(defaultChainedResult, chainedResult); }
Функция действие, которое принимает Response() просто принимает Response в качестве аргумента и возвращает Хэш-код поля id:
public int actionThatTakesResponse(Response response) { return response.id.hashCode(); }
Как только мы сопоставляем наше значение с помощью действия, которое принимает функцию Response () , мы выполняем метод getOrElse() .
Если Try имеет Успех внутри него, он возвращает значение Tri, o therwise, он возвращает Цепной результат по умолчанию . Наше выполнение HttpClient было успешным, поэтому метод isSuccess возвращает true. Затем мы можем выполнить метод onSuccess() , который выполняет действие над объектом Response . Try также имеет метод , а затем , который принимает Потребителя , который потребляет значение Try , когда это значение является Успехом.
Мы можем рассматривать наш Try ответ как поток. Для этого нам нужно преобразовать его в Stream с помощью метода stream () , тогда все операции, доступные в классе Stream , могут быть использованы для выполнения операций с этим результатом.
Если мы хотим выполнить действие на Попробуйте тип, мы можем использовать transform() метод, который принимает Try в качестве аргумента и выполняет действие над ним, не разворачивая вложенное значение :
public int actionThatTakesTryResponse(Tryresponse, int defaultTransformation){ return response.transform(responses -> response.map(it -> it.id.hashCode()) .getOrElse(defaultTransformation)); }
3.2. Сбой Обработки
Давайте напишем пример, когда наш HttpClient бросит ClientException при выполнении.
По сравнению с предыдущим примером, наш метод getOrElse вернет Цепной результат по умолчанию , потому что Они будут иметь тип Сбой :
@Test public void givenHttpClientFailure_whenMakeACall_shouldReturnFailure() { // given Integer defaultChainedResult = 1; HttpClient httpClient = () -> { throw new ClientException("problem"); }; // when Tryresponse = new VavrTry(httpClient).getResponse(); Integer chainedResult = response .map(this::actionThatTakesResponse) .getOrElse(defaultChainedResult); Option optionalResponse = response.toOption(); // then assertTrue(optionalResponse.isEmpty()); assertTrue(response.isFailure()); response.onFailure(ex -> assertTrue(ex instanceof ClientException)); assertEquals(defaultChainedResult, chainedResult); }
Метод getresponse() возвращает Сбой этот метод является сбоем возвращает true.
Мы могли бы выполнить OnFailure() обратный вызов при возвращенном ответе и увидеть, что исключение имеет тип ClientException . Объект типа Dry может быть сопоставлен с типом Option с помощью метода to Option () .
Это полезно, когда мы не хотим выполнять Try result во всей кодовой базе, но у нас есть методы, которые обрабатывают явное отсутствие значения с помощью Option type. Когда мы сопоставляем наш Failure с опцией, тогда метод isEmpty() возвращает true. Когда Объект является типом Success/| вызов to Option на нем будет Option , который определен этим методом определен() вернет true.
3.3. Использование Сопоставления Шаблонов
Когда наш HttpClient возвращает Исключение , мы можем выполнить сопоставление шаблонов для типа этого Исключения. Затем в соответствии с типом этого Исключения в recovery() методе мы можем решить, хотим ли мы восстановиться после этого исключения и превратить наш Сбой в Успех или если мы хотим оставить результат наших вычислений как Сбой:
@Test public void givenHttpClientThatFailure_whenMakeACall_shouldReturnFailureAndNotRecover() { // given Response defaultResponse = new Response("b"); HttpClient httpClient = () -> { throw new RuntimeException("critical problem"); }; // when Tryrecovered = new VavrTry(httpClient).getResponse() .recover(r -> Match(r).of( Case(instanceOf(ClientException.class), defaultResponse) )); // then assertTrue(recovered.isFailure());
Сопоставление шаблонов внутри метода recovery() превратит Сбой в Успех только в том случае, если тип исключения является Исключение клиента. В противном случае он оставит его как Failure(). Мы видим, что наш HttpClient выбрасывает RuntimeException таким образом, наш метод восстановления не будет обрабатывать этот случай, поэтому isFailure() возвращает true.
Если мы хотим получить результат от recovered объекта, но в случае критического сбоя выбрасывает это исключение, мы можем сделать это с помощью метода getOrElseThrow() :
recovered.getOrElseThrow(throwable -> { throw new RuntimeException(throwable); });
Некоторые ошибки являются критическими, и когда они возникают, мы хотим явно сигнализировать об этом, выбрасывая исключение выше в стеке вызовов, чтобы позволить вызывающему абоненту принять решение о дальнейшей обработке исключений. В таких случаях повторное исключение, как в приведенном выше примере, очень полезно.
Когда наш клиент выдает некритическое исключение, наше сопоставление шаблонов в методе recovery() превратит наш Сбой в Успех. Мы восстанавливаемся после двух типов исключений ClientException и IllegalArgumentException :
@Test public void givenHttpClientThatFailure_whenMakeACall_shouldReturnFailureAndRecover() { // given Response defaultResponse = new Response("b"); HttpClient httpClient = () -> { throw new ClientException("non critical problem"); }; // when Tryrecovered = new VavrTry(httpClient).getResponse() .recover(r -> Match(r).of( Case(instanceOf(ClientException.class), defaultResponse), Case(instanceOf(IllegalArgumentException.class), defaultResponse) )); // then assertTrue(recovered.isSuccess()); }
Мы видим, что isSuccess() возвращает true, поэтому наш код обработки восстановления успешно работал.
4. Заключение
В этой статье показано практическое использование контейнера Try из библиотеки Var. Мы рассмотрели практические примеры использования этой конструкции путем обработки сбоя более функциональным способом. Использование Try позволит нам создать более функциональный и читаемый API.
Реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub – это проект на основе Maven, поэтому он