Vavrで試すガイド

1概要

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

Vavr ライブラリの Try クラスを使用します。これにより、エラー処理を通常のプログラム処理フローに組み込むことで、より流暢で意識のあるAPIを作成できます。

Vavrに関する詳細情報を入手したい場合は、/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
}

注意すべき重要なことは戻り型__ Try <Response>です。メソッドがそのような結果型を返すとき、我々はそれを適切に処理する必要があります。コンパイル時に。

3.1. 処理 成功

httpClient が成功した結果を返している場合に、 Vavr クラスを使用したテストケースを書きましょう。メソッド getResponse() は、 Try <Resposne> オブジェクトを返します。そのため、 Try Success タイプの場合にのみ 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 には andThen というメソッドもあります。このメソッドは Consumer を取り、その値が Successであるときに Try の値を使います。

Try レスポンスをストリームとして扱うことができます。そのためには、 toStream() メソッドを使用してそれを Stream に変換する必要があります。その後、 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 をスローする場合の例を書きましょう。

前の例と比較すると、 Try Failure 型になるため、 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 typeを使用して明示的に値が欠落していることを処理するメソッドがあります。 Failure Optionにマップすると、 isEmpty() メソッドがtrueを返します。 Try オブジェクトが Success を呼び出して toOption を呼び出すと、定義された Option が作成されるため、メソッド isDefined()__はtrueを返します。

3.3. パターンマッチングを利用する

httpClient Exception を返すと、 recover()メソッドの の型に応じて、その例外から回復するかどうかを判断して 失敗します計算結果を 失敗のままにしたい場合は Success に入れてください。

@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());

recover() メソッド内のパターンマッチングは、Exceptionの型が ClientExceptionの場合にのみ Failure Success に変換します。それ以外の場合は、 Failure()のままにします。 methodはその場合を処理しないので、 isFailure() はtrueを返します。

recovered オブジェクトから結果を取得したいが、重大な失敗の場合には、 getOrElseThrow() メソッドを使用してその例外を再スローできます。

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

いくつかのエラーは重大であり、それらが発生したとき、呼び出し側で例外処理をさらに決定できるようにするために、呼び出しスタック内で例外をより高くスローすることによって明示的にそれを知らせます。そのような場合、上の例のように例外を再スローすることは非常に便利です。

クライアントが非致命的な例外を投げると、 recover() メソッドのパターンマッチングによって Failure Successに変わります。 ClientException IllegalArgumentException__の2種類の例外から回復しています。

@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を作成できます。

これらすべての例とコードスニペットの実装はhttps://github.com/eugenp/tutorials/tree/master/vavr[GitHubプロジェクト]にあります。そのままインポートして実行します。