1. 개요

이 기사에서는 표준 try-catch 블록 이외의 기능적인 오류 처리 방법을 살펴보겠습니다 .

우리는 일반적인 프로그램 처리 흐름에 오류 처리를 내장하여 보다 유창하고 의식적인 API를 만들 수 있게 해주는 Vavr 라이브러리의 Try 클래스를 사용할 것입니다.

Vavr에 대한 자세한 정보를 얻으려면 이 기사 를 확인하십시오 .

2. 예외 처리 표준 방식

Response 를 반환 하거나 실패한 경우 확인된 예외인 ClientException 을 throw 하는 call() 메서드가 있는 간단한 인터페이스가 있다고 가정해 보겠습니다 .

public interface HttpClient {
    Response call() throws ClientException;
}

응답 은 하나 의 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를 생성하려는 경우 확인된 예외를 발생시키는 각 메서드는 프로그램 흐름을 방해하고 프로그램 코드는 많은 try-catch 블록으로 구성되어 있어 읽기가 매우 어렵습니다.

이상적으로는 결과 상태(성공 또는 실패)를 캡슐화하는 특수 클래스를 갖고 그 결과에 따라 작업을 연결할 수 있습니다.

3. Try 로 예외 처리하기

Vavr 라이브러리는 예외가 발생하거나 성공적으로 완료될 수 있는 계산을 나타내는 특수 컨테이너를 제공합니다 .

Try 개체 내에서 둘러싸는 작업 은 성공 또는 실패 라는 결과를 제공했습니다 . 그런 다음 해당 유형에 따라 추가 작업을 실행할 수 있습니다.

이전 예제 와 동일한 메서드 getResponse() 가 Try를 사용하는 것처럼 어떻게 보이는지 살펴보겠습니다.

public class VavrTry {
    private HttpClient httpClient;

    public Try<Response> getResponse() {
        return Try.of(httpClient::call);
    }

    // standard constructors
}

주목해야 할 중요한 사항은 반환 유형 Try<Response>입니다. 메서드가 이러한 결과 유형을 반환하면 이를 적절하게 처리해야 하며 결과 유형이 성공 또는 실패 일 수 있으므로 컴파일 시 명시적으로 처리해야 합니다.

3.1. 성공 처리

httpClient 가 성공적인 결과를 반환하는 경우에 Vavr 클래스 를 사용하는 테스트 사례를 작성해 봅시다 . getResponse() 메서드 는 Try<Resposne> 개체를 반환 합니다. 따라서 Try성공 유형일 때만 Response 에 대한 작업을 실행하는 map() 메서드를 호출할 수 있습니다.

@Test
public void givenHttpClient_whenMakeACall_shouldReturnSuccess() {
    // given
    Integer defaultChainedResult = 1;
    String id = "a";
    HttpClient httpClient = () -> new Response(id);

    // when
    Try<Response> response = new VavrTry(httpClient).getResponse();
    Integer chainedResult = response
      .map(this::actionThatTakesResponse)
      .getOrElse(defaultChainedResult);
    Stream<String> 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);
}

actionThatTakesResponse() 함수 는 단순히 Response 를 인수로 취하고 id 필드 의 hashCode반환 합니다.

public int actionThatTakesResponse(Response response) {
    return response.id.hashCode();
}

actionThatTakesResponse() 함수 를 사용하여 값을 매핑 하면 getOrElse() 메서드를 실행 합니다.

Try 안에 성공있으면 Try 값을 반환하고 그렇지 않으면 defaultChainedResult 를 반환합니다 . httpClient 실행이 성공적이어서 isSuccess 메서드 가 true를 반환합니다. 그런 다음 Response 객체 에 대해 작업을 수행하는 onSuccess() 메서드를 실행할 수 있습니다. Try 에는 해당 값이 성공 일 때 Try 값을 소비하는 Consumer 사용 하는 메서드 andThen 도 있습니다.

Try 응답을 스트림으로 처리할 수 있습니다 . 이렇게 하려면 toStream() 메서드 를 사용하여 스트림 으로 변환해야 합니다. 그런 다음 Stream 클래스 에서 사용할 수 있는 모든 작업을 사용하여 해당 결과에 대한 작업을 수행할 수 있습니다.

Try 유형에서 작업을 실행하려면 Try 를 인수로 사용하는 transform() 메서드를 사용하고 포함 된 값을 풀지 않고 작업을 수행할 수 있습니다 .

public int actionThatTakesTryResponse(Try<Response> response, int defaultTransformation){
    return response.transform(responses -> response.map(it -> it.id.hashCode())
      .getOrElse(defaultTransformation));
}

3.2. 취급 실패

HttpClient실행될 때 ClientException 을 발생 시키는 예를 작성해 봅시다 .

이전 예제와 비교하여 getOrElse 메서드는 Try실패 유형 이기 때문에 defaultChainedResult 를 반환합니다.

@Test
public void givenHttpClientFailure_whenMakeACall_shouldReturnFailure() {
    // given
    Integer defaultChainedResult = 1;
    HttpClient httpClient = () -> {
        throw new ClientException("problem");
    };

    // when
    Try<Response> response = new VavrTry(httpClient).getResponse();
    Integer chainedResult = response
        .map(this::actionThatTakesResponse)
        .getOrElse(defaultChainedResult);
     Option<Response> optionalResponse = response.toOption();

    // then
    assertTrue(optionalResponse.isEmpty());
    assertTrue(response.isFailure());
    response.onFailure(ex -> assertTrue(ex instanceof ClientException));
    assertEquals(defaultChainedResult, chainedResult);
}

getReposnse() 메서드 는 실패 를 반환 하므로 isFailure 메서드 는 true를 반환합니다.

반환된 응답에 대해 onFailure() 콜백을 실행하고 예외가 ClientException 유형인지 확인할 수 있습니다. Try 유형 의 개체는 toOption() 메서드 를 사용하여 Option 유형 에 매핑할 수 있습니다 .

모든 코드베이스에서 Try 결과 를 전달하고 싶지 않지만 Option 유형을 사용하여 명시적인 값 부재를 처리하는 메서드가 있는 경우 유용합니다. 우리의 FailureOption에 매핑하면 isEmpty() 메서드 가 true를 반환합니다. Try 객체가 toOption 을 호출 하는 성공 유형일 때 정의된 Option 을 만들 것이므로 isDefined() 메서드 는 true를 반환합니다.

3.3. 패턴 매칭 활용

httpClient 가 Exception 을 반환하면 해당 Exception 의 유형에 대해 패턴 일치를 수행할 수 있습니다. 그런 다음 recover() 메서드의 해당 예외 유형에 따라 해당 예외 에서 복구하고 실패성공 으로 전환 할지 아니면 계산 결과를 실패 로 남겨 둘지 결정할 수 있습니다 .

@Test
public void givenHttpClientThatFailure_whenMakeACall_shouldReturnFailureAndNotRecover() {
    // given
    Response defaultResponse = new Response("b");
    HttpClient httpClient = () -> {
        throw new RuntimeException("critical problem");
    };

    // when
    Try<Response> recovered = new VavrTry(httpClient).getResponse()
      .recover(r -> Match(r).of(
          Case(instanceOf(ClientException.class), defaultResponse)
      ));

    // then
    assertTrue(recovered.isFailure());

복구() 메서드 내부의 패턴 일치 는 예외 유형이 ClientException 인 경우에만 실패성공 으로 바꿉니다. 그렇지 않으면 실패()로 남게 됩니다. httpClient가 RuntimeException 을 던지고 있으므로 복구 방법이 해당 경우를 처리하지 않으므로 isFailure() 가 true를 반환합니다.

복구된 개체 에서 결과를 얻고 싶지만 심각한 오류가 발생하여 해당 예외를 다시 throw하는 경우 getOrElseThrow() 메서드를 사용하여 이를 수행할 수 있습니다.

recovered.getOrElseThrow(throwable -> {
    throw new RuntimeException(throwable);
});

일부 오류는 치명적이며 오류가 발생하면 호출자가 추가 예외 처리에 대해 결정할 수 있도록 호출 스택에서 더 높은 예외를 발생시켜 명시적으로 신호를 보내야 합니다. 이러한 경우 위의 예와 같이 예외를 다시 던지는 것은 매우 유용합니다.

클라이언트가 중요하지 않은 예외를 발생시키면 복구() 메서드 의 패턴 일치가 실패성공으로 바꿉니다. 우리는 ClientExceptionIllegalArgumentException 의 두 가지 유형의 예외로부터 복구하고 있습니다 .

@Test
public void givenHttpClientThatFailure_whenMakeACall_shouldReturnFailureAndRecover() {
    // given
    Response defaultResponse = new Response("b");
    HttpClient httpClient = () -> {
        throw new ClientException("non critical problem");
    };

    // when
    Try<Response> recovered = 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. 결론

이 기사에서는 Vavr 라이브러리 의 Try 컨테이너를 실제로 사용하는 방법을 보여줍니다 . 보다 기능적인 방식으로 실패를 처리하여 해당 구성을 사용하는 실제 예를 살펴보았습니다. Try 를 사용하면 더 기능적이고 읽기 쉬운 API를 만들 수 있습니다.

이러한 모든 예제 및 코드 스니펫의 구현은 GitHub 프로젝트 에서 찾을 수 있습니다. 이것은 Maven 기반 프로젝트이므로 그대로 가져오고 실행하기 쉬워야 합니다.

Generic footer banner