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プロジェクト]にあります。そのままインポートして実行します。