2016-06-29 8 views
4

In meinem Code habe ich einen Abschnitt, der versucht, eine Verbindung zu einer externen Schnittstelle, wenn es fehlschlägt, dann wird es versuchen es eine feste Anzahl von Malen. Der Code funktioniert, ist aber etwas hässlich. Ich frage mich, ob dies mit einigen ausgefallenen Java8-Funktionen auf elegantere Weise möglich ist?Wie vereinfache ich Wiederholungscode Block mit Java 8 Funktionen

int count = 0; 
final int maxRetries = 3; 
while (count < maxRetries) 
{ 
    try 
    { 
    // Some Code 
    // break out of loop on success 
    } 
    catch (final ExecutionException e) 
    { 
     LOG.debug("retrying..."); 
     if (++count >= maxRetries) 
     { 
     LOG.debug("do something else..."); 
     //do something else 
     } 
    } 
} 
+2

Was ist in diesem Code hässlich? Um es erneut zu versuchen, müssen Sie eine Schleife (oder Rekursion) durchführen. (verwandt: http://stackoverflow.com/questions/34740091/apply-retries-in-a-rxjava und http://stackoverflow.com/questions/30989558/java-8-retry-a-method-until-a -condition-is-in-intervalls erfüllt) – Tunaki

+0

Ja, ich weiß, vielleicht bin ich hier zu pingelig, aber ich frage mich nur, ob das "funktional" oder "deklarativ" gemacht werden kann, weil das oft mehr ist lesbar ... – Moonlit

+1

Was hässlich ist, ist, dass es den Code für eine bestimmte Aufgabe mit der Wiederholungs-/Fehlermanagementlogik zusammenfasst. Das bedeutet, dass Sie die Wiederholungslogik überall kopieren würden, wahrscheinlich durch Ausschneiden und Einfügen, wodurch es praktisch unmöglich wird, Ihr Wiederholungsverhalten später konsistent anzupassen. –

Antwort

12

Sie können die Wiederholungslogik trennen. Sie werden einige Hilfsgerüste benötigen:

interface ThrowingTask { 
    void run() throws ExecutionException; 
} 

Nun schreiben Sie:

boolean runWithRetries(int maxRetries, ThrowingTask t) { 
    int count = 0; 
    while (count < maxRetries) { 
     try { 
      t.run(); 
      return true; 
     } 
     catch (ExecutionException e) { 
      if (++count >= maxRetries) 
       return false; 
     } 
    } 
} 

Jetzt können Sie die Dinge mit Wiederholungen ausführen, ohne Ihre Aufgabe Logik mit Wiederholungslogik vermengen mit:

runWithRetries(MAX_RETRIES,() -> { /* do stuff */ }); 

Sie können dies zwicken wie Sie möchten, um Lambdas zu akzeptieren, die beim erneuten Versuch aufgerufen werden, die Anzahl der Wiederholungen, etc. zurückgeben. Aber das Spiel ist, Methoden wie runWithRetries zu schreiben, die das erfassen Kontrollfluss, aber abstrakt darüber, welches Verhalten ausgeführt werden muss - Sie müssen also nur einmal die Wiederholungsschleife schreiben und dann das gewünschte Verhalten eingeben, wo immer es gebraucht wird.

4

Nun, funktionaler Ansatz meiner Meinung nach wird Try Monade zu verwenden, die leider für uns in jdk nicht gibt es 8 :(

Trotzdem noch Sie better-monads library, die es bietet. Mit, dass Sie verwenden können, mit einigen Umsetzung kommen wie diese können:

public static <Out> Try<Out> tryTimes(int times, TrySupplier<Out> attempt) { 
     Supplier<Try<Out>> tryAttempt =() -> Try.ofFailable(attempt::get); 

     return IntStream.range(1, times) 
       .mapToObj(i -> tryAttempt) 
       .reduce(tryAttempt, (acc, current) ->() -> acc.get().recoverWith(error -> current.get())) 
       .get(); 
    } 

Lange Rede kurzer Sinn diese Funktion nur Ketten Anrufe von tryAttempt und bei gescheiterten Versuch, versucht, den nächsten cal recoverWith l von tryAttempt. Client-Code wird wie folgt aussehen:

tryTimes(10,() -> { 
      // all the logic to do your possibly failing stuff 
     } 
); 

Als Ergebnis Client-Code wird Try<T> erhalten, die durch direkten Aufruf von .get() entpackt werden kann (im Erfolgsfall liefert den Wert, im Fall des Scheiterns Würfe zugrunde liegenden Ausnahme) oder mit anderen in der Bibliotheksdokumentation beschriebenen Methoden.

Ich hoffe, es hilft.

UPDATE:

Dies auch in funktioneller Art und Weise getan werden kann, mit den filter, findFirst und limit und ohne externe Bibliotheken:

interface ThrowingSupplier<Out> { Out supply() throws Exception; } 

public static <Out> Optional<Out> tryTimes(int times, ThrowingSupplier<Out> attempt) { 
    Supplier<Optional<Out>> catchingSupplier =() -> { 
     try { 
      return Optional.ofNullable(attempt.supply()); 
     } catch (Exception e) { 
      return Optional.empty(); 
     } 
    }; 
    return Stream.iterate(catchingSupplier, i -> i) 
      .limit(times) 
      .map(Supplier::get) 
      .filter(Optional::isPresent) 
      .findFirst() 
      .flatMap(Function.identity()); 
} 

Der Client-Code bleibt gleich. Beachten Sie auch, dass der Ausdruck times nicht ausgewertet wird, sondern beim ersten erfolgreichen Versuch gestoppt wird.

+0

Für die zweite Lösung ist es möglich, den IntStream.range der ersten Lösung (0, mal) .mapToObj (i -> catchingSupplier) zu verwenden, um den gebundenen catchingSupplier-Stream zu generieren? – srborlongan

+1

@srborlongan ja ist es. Ich wollte nur die zweite Lösung so minimalistisch wie möglich halten, was bedeutet, dass ich die Erwähnung von 0 und IntStream (nicht sehr hilfreich, um IMO zu verstehen) überhaupt nicht mag und die Limit-Methode bevorzuge. – tkachuko

1

Mit Failsafe:

RetryPolicy retryPolicy = new RetryPolicy() 
    .retryOn(ExecutionException.class) 
    .withMaxRetries(3); 

Failsafe.with(retryPolicy) 
    .onRetry(r -> LOG.debug("retrying...")) 
    .withFallback(e -> LOG.debug("do something else...")) 
    .run(() -> someCode()); 

Es ist ungefähr so ​​einfach und ausdrucksvoll, wie Sie für Ihren Anwendungsfall zu erhalten.

0

Ähnlich wie bei einigen der vorgeschlagenen Ansätze könnten Sie eine Klasse erstellen, die die Wiederholungsfunktion von dem restlichen Code trennt. Die Klasse unter dem tut genau das und ermöglicht es Ihnen auch einen Eingang zu nehmen und etwas Ergebnis zurück:

public class Retrier<T, R> { 
    private static final int DEFAULT_RETRY_COUNT = 3; 
    private int retryCount; 
    private Function<T, R> retriable; 
    private T input; 

    public Retrier(T input, Function<T, R> retriable) { 
     this(input, retriable, DEFAULT_RETRY_COUNT); 
    } 

    public Retrier(T input, Function<T, R> retriable, int retryCount) { 
     this.retryCount = retryCount; 
     this.retriable = retriable; 
     this.input = input; 
    } 

    public R execute() { 
     int count = 0; 
     while(true) { 
      try { 
       return retriable.apply(input); 
      } 
      catch (Exception e) { 
       if (++count >= retryCount) { 
        throw e; 
       } 
      } 
     } 
    } 
} 

Und dann kann man es wie dieses Beispiel verwenden

new Retrier<String, String>("test", input -> {/* proceess the input and return the result*/).execute(); 

Und wo immer Ihr Code eine Ausnahme auslöst, wird es erneut versucht.