Anleitung zum Testen in Vavr

Anleitung zum Ausprobieren in Vavr

1. Überblick

In diesem Artikel werdenwe’ll look at a functional way of error handling other than a standard try-catch block.

Wir verwenden die KlasseTryaus der BibliothekVavr, mit der wir eine flüssigere und bewusstere API erstellen können, indem wir die Fehlerbehandlung in den normalen Programmverarbeitungsablauf einbetten.

Wenn Sie weitere Informationen zu Vavr erhalten möchten, aktivieren Siethis article.

2. Standard für den Umgang mit Ausnahmen

Nehmen wir an, wir haben eine einfache Schnittstelle mit einer Methodecall(), dieResponse zurückgibt oderClientException auslöst, was im Falle eines Fehlers eine geprüfte Ausnahme darstellt:

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

Response ist eine einfache Klasse mit nur einemid-Feld:

public class Response {
    public final String id;

    public Response(String id) {
        this.id = id;
    }
}

Nehmen wir an, wir haben einen Dienst, derHttpClient, aufruft, dann müssen wir diese überprüfte Ausnahme in einem Standardblocktry-catchbehandeln:

public Response getResponse() {
    try {
        return httpClient.call();
    } catch (ClientException e) {
        return null;
    }
}

Wenn wir eine API erstellen möchten, die fließend und funktional geschrieben ist, stört jede Methode, die geprüfte Ausnahmen auslöst, den Programmfluss, und unser Programmcode besteht aus vielentry-catch-Blöcken, was das Lesen sehr erschwert.

Im Idealfall möchten wir eine spezielle Klasse haben, die den Ergebnisstatus (Erfolg oder Misserfolg) kapselt, und dann können wir Operationen entsprechend diesem Ergebnis verketten.

3. Behandlung von Ausnahmen mitTry

Die Vavr-Bibliothek gibt unsspecial container that represents a computation that may either result in an exception or complete successfully.

Das Einschließen einer Operation innerhalb desTry-Objekts ergab ein Ergebnis, das entwederSuccess oderFailure. ist. Dann können wir weitere Operationen entsprechend diesem Typ ausführen.

Schauen wir uns an, wie die gleiche MethodegetResponse() wie in einem vorherigen Beispiel mitTry: aussieht

public class VavrTry {
    private HttpClient httpClient;

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

    // standard constructors
}

Das Wichtigste ist, dass ein RückgabetypTry<Response>. zurückgegeben wird. Wenn eine Methode einen solchen Ergebnistyp zurückgibt, müssen wir dies richtig handhaben und berücksichtigen, dass der ErgebnistypSuccess oderFailure sein kann. Daher müssen wir dies bei einer Kompilierung explizit behandeln.

3.1. Umgang mitSuccess

Schreiben wir einen Testfall, der unsereVavr-Klasse verwendet, wennhttpClient ein erfolgreiches Ergebnis zurückgibt. Die MethodegetResponse() gibt das ObjektTry<Resposne>zurück. Daher können wir die Methodemap() aufrufen, die nur dann eine Aktion fürResponse ausführt, wennTry vom TypSuccessist:

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

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

Die FunktionactionThatTakesResponse() nimmt einfachResponse als Argument und gibthashCode vonid field: zurück

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

Sobald wirmap unseren Wert mitactionThatTakesResponse() Funktion haben, führen wir die MethodegetOrElse() aus.

WennTry einSuccess enthält, gibt es einen Wert vonTry, otherwise zurück, es gibtdefaultChainedResult zurück. Die Ausführung vonhttpClientwar erfolgreich, daher gibt die MethodeisSuccesstrue zurück. Dann können wir die MethodeonSuccess()ausführen, die eine Aktion für das ObjektResponseausführt. Try hat auch eine MethodeandThen, dieConsumer verwendet, die einen Wert vonTry verbrauchen, wenn dieser Wert aSuccess. ist

Wir können die Antwort vonTryals Stream behandeln. Dazu müssen wir es mit der MethodetoStream() inStream konvertieren. Dann können alle Operationen, die in der KlasseStreamverfügbar sind, verwendet werden, um Operationen für dieses Ergebnis durchzuführen.

Wenn wir eine Aktion für den TypTryausführen möchten, können wir die Methodetransform()verwenden, dieTry als Argument verwendet, und eine Aktion darauf ausführen, ohne den eingeschlossenen Wert: zu entpacken

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

3.2. Umgang mitFailure

Schreiben wir ein Beispiel, in dem unsereHttpClient bei der AusführungClientException auslösen.

Im Vergleich zum vorherigen Beispiel gibt unseregetOrElse-MethodedefaultChainedResult zurück, daTry vom TypFailure ist:

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

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

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

Die MethodegetReposnse() gibtFailure zurück, daher gibt die MethodeisFailure true zurück.

Wir könnten den Rückruf vononFailure()bei zurückgegebener Antwort ausführen und feststellen, dass die Ausnahme vom TypClientExceptionist. Das Objekt vom TypTrykann mit der MethodetoOption()dem TypOptionzugeordnet werden.

Dies ist nützlich, wenn wir das Ergebnis vonTrynicht in der gesamten Codebasis übertragen möchten, sondern Methoden verwenden, die das explizite Fehlen von Werten mit dem TypOptionbehandeln. Wenn wir unsereFailureOption, zuordnen, gibt die MethodeisEmpty() true zurück. Wenn das ObjektTryein TypSuccessist, dertoOption aufruft, wirdOption definiert, sodass die MethodeisDefined() true zurückgibt.

3.3. Verwenden von Pattern Matching

Wenn unserhttpClient einException zurückgibt, könnten wir einen Mustervergleich für einen Typ diesesException. durchführen. Dann gemäß einem Typ diesesException inrecover() a-Methode Wir können entscheiden, ob wir uns von dieser Ausnahme erholen und unsereFailure inSuccess umwandeln möchten oder ob wir unser Berechnungsergebnis alsFailure: belassen möchten

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

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

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

Der Mustervergleich innerhalb derrecover()-Methode verwandeltFailure nur dann inSuccess, wenn ein AusnahmetypClientException. ist. Andernfalls wird er alsFailure(). belassen Wir sehen, dass unser httpClientRuntimeException auslöst, daher wird unsere Wiederherstellungsmethode diesen Fall nicht behandeln, daher gibtisFailure() true zurück.

Wenn wir das Ergebnis vom Objektrecoverederhalten möchten, aber bei einem kritischen Fehler diese Ausnahme erneut auslösen, können wir dies mit der MethodegetOrElseThrow()tun:

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

Einige Fehler sind kritisch, und wenn sie auftreten, möchten wir dies explizit signalisieren, indem wir die Ausnahme in einem Aufrufstapel höher werfen, damit der Aufrufer über die weitere Ausnahmebehandlung entscheiden kann. In solchen Fällen ist es sehr nützlich, eine Ausnahme wie im obigen Beispiel erneut auszulösen.

Wenn unser Client eine unkritische Ausnahme auslöst, verwandelt unser Mustervergleich in einerrecover()-Methode unsereFailure inSuccess.. Wir erholen uns von zwei Arten von AusnahmenClientException undIllegalArgumentException:

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

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

    // then
    assertTrue(recovered.isSuccess());
}

Wir sehen, dassisSuccess() true zurückgibt, sodass unser Code für die Wiederherstellungsbehandlung erfolgreich funktioniert hat.

4. Fazit

Dieser Artikel zeigt eine praktische Verwendung desTry-Containers aus der Vavr-Bibliothek. Wir haben uns die praktischen Beispiele für die Verwendung dieses Konstrukts angesehen, indem wir Fehler auf funktionalere Weise behandelt haben. MitTry können wir eine funktionalere und lesbarere API erstellen.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie inGitHub project - dies ist ein Maven-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.