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 유형을 사용하여 명시적인 값 부재를 처리하는 메서드가 있는 경우 유용합니다. 우리의 Failure 를 Option에 매핑하면 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);
});
일부 오류는 치명적이며 오류가 발생하면 호출자가 추가 예외 처리에 대해 결정할 수 있도록 호출 스택에서 더 높은 예외를 발생시켜 명시적으로 신호를 보내야 합니다. 이러한 경우 위의 예와 같이 예외를 다시 던지는 것은 매우 유용합니다.
클라이언트가 중요하지 않은 예외를 발생시키면 복구() 메서드 의 패턴 일치가 실패 를 성공으로 바꿉니다. 우리는 ClientException 및 IllegalArgumentException 의 두 가지 유형의 예외로부터 복구하고 있습니다 .
@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 기반 프로젝트이므로 그대로 가져오고 실행하기 쉬워야 합니다.