1. 概要

この記事では、標準のtry-catchブロック以外のエラー処理の機能的な方法を見ていきます。

Try class from Vavr libraryを使用します。これにより、エラー処理を通常のプログラム処理フローに組み込むことで、より流暢で意識的なAPIを作成できます。

Vavrの詳細については、この記事を確認してください。

2. 例外を処理する標準的な方法

Response を返すか、失敗した場合にチェックされた例外である ClientExceptionをスローするメソッドcall()を使用した単純なインターフェイスがあるとします。 :

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

Response は、idフィールドが1つしかない単純なクラスです。

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 オブジェクト内に操作を含めると、 SuccessまたはFailureのいずれかの結果が得られました。次に、そのタイプに応じてさらに操作を実行できます。

前の例と同じメソッドgetResponse() Try:を使用するとどのように見えるかを見てみましょう。

public class VavrTry {
    private HttpClient httpClient;

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

    // standard constructors
}

注意すべき重要なことはリターンタイプです試すメソッドがそのような結果タイプを返す場合、それを適切に処理する必要があり、その結果タイプは次のようになる可能性があることに注意してください。 成功また失敗 、したがって、コンパイル時に明示的に処理する必要があります。

3.1. 成功の処理

httpClient が成功した結果を返す場合に、Vavrクラスを使用するテストケースを作成してみましょう。 方法 getResponse() 戻り値試す物体。 したがって、TrySuccessタイプの場合にのみ、 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()関数を使用して値を map したら、メソッド getOrElse()を実行します。

Tryの中にSuccessがある場合は、 Tryの値を返します。それ以外の場合は、defaultChainedResultを返します。 httpClient の実行が成功したため、isSuccessメソッドはtrueを返します。 次に、 Responseオブジェクトに対してアクションを実行するonSuccess()メソッドを実行できます。 試す方法もありますその後それはかかります消費者の値を消費する試すその値が成功。

Try応答をストリームとして扱うことができます。 そのためには、 toStream()メソッドを使用して Stream に変換する必要があります。そうすると、Streamクラスで使用できるすべての操作を使用して操作を行うことができます。その結果に。

Try typeでアクションを実行する場合は、[X105X] 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をスローする例を書いてみましょう。

前の例と比較すると、TryFailureタイプであるため、getOrElseメソッドは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() Failure を返します。したがって、メソッドisFailureはtrueを返します。

返された応答に対してonFailure()コールバックを実行すると、例外がClientExceptionタイプであることがわかります。 Try タイプのオブジェクトは、 toOption()メソッドを使用してOptionタイプにマップできます。

Try の結果をすべてのコードベースに伝達したくない場合に便利ですが、Optionタイプを使用して値の明示的な欠如を処理するメソッドがあります。 Failure Optionにマップすると、メソッド isEmpty()はtrueを返します。 TryオブジェクトがタイプSuccessである場合、 toOption を呼び出すと、 Option が作成され、メソッド isDefined()[ X153X]はtrueを返します。

3.3. パターンマッチングの活用

httpClientExceptionを返す場合、そのExceptionのタイプでパターンマッチングを実行できます。次に、そのException[のタイプに従ってX175X] restore()メソッドで、その例外から回復して失敗成功に変えるか、それともそのままにするかを決定できます。 障害としての計算結果:

@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を返します。

が回復したオブジェクトから結果を取得したいが、重大な障害が発生した場合にその例外を再スローする場合は、 getOrElseThrow()メソッドを使用してそれを行うことができます。

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

一部のエラーは重大です。エラーが発生した場合は、呼び出しスタックの上位に例外をスローすることで明示的に通知し、呼び出し元がさらに例外を処理するかどうかを決定できるようにします。 このような場合、上記の例のように例外を再スローすると非常に便利です。

クライアントが重要ではない例外をスローすると、 restore()メソッドでのパターンマッチングにより、FailureSuccessに変わります。2つのタイプから回復しています例外の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ベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。