2016-06-29 1 views
4

私のコードでは、いくつかの外部インターフェイスに接続しようとしているセクションがあり、失敗した場合は一定回数再試行します。コードは機能しますが、やや醜いです。私はこれがより洗練された方法でいくつかの派手なJava8の機能を使って行うことができるかどうか疑問に思っていますか?Java 8の機能を使用して再試行コードブロックを簡略化する方法

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

このコードでは何が醜いですか?再試行するには、ループ(または再帰)する必要があります。 (関連:http://stackoverflow.com/questions/34740091/apply-retries-in-a-rxjavaおよびhttp://stackoverflow.com/questions/30989558/java-8-retry-a-method-until-a -comondition-is-intervals-fulfiled-in-intervals) – Tunaki

+0

ええ、私はここではあまりにも扱いにくいかもしれませんが、これはより機能的な、あるいは宣言的な方法で行うことができるかどうかは不思議です。読み込み可能... – Moonlit

+1

醜いのは、特定のタスクのコードを再試行/失敗管理ロジックと競合させることです。これは、リトライロジックを場所全体に複製することを意味します。カットアンドペーストによって、リトライ動作を後で一貫して調整することは事実上不可能です。 –

答えて

12

リトライロジックを分けることができます。あなたは、いくつかの補助的な足場必要があります:今

interface ThrowingTask { 
    void run() throws ExecutionException; 
} 

を、次のように記述:

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; 
     } 
    } 
} 

さて、あなたは再試行ロジックを使用してタスクのロジックを融合することなく、再試行して物事を実行することができます

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

これは、リトライ時に呼び出されるラムダを受け入れるか、リトライ回数を返すかなどを調整することができます。ただし、ゲームでは、runWithRetriesのようなメソッドを記述します。どのような振る舞いが必要なのかを抽象化していますので、一度リトライループを書いておき、必要な場所で実際の振る舞いを記入するだけです。

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

それはあなたがあなたのユースケースのために得ることができると同じくらいシンプルかつ表現力豊かです:Failsafeを使用して

4

まあ、私の意見では、より機能的なアプローチは、あなたのことを持つ。8それでも:(

あなたはまだそれを提供better-monads libraryを使用することができます残念ながら、JDKで私たちのためにそこではないTryモナドを使用するようになりますこのようないくつかの実装を思い付くことができます。

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

長い話を短く、この機能にtryAttemptのと失敗した場合にだけチェーン・コールは、次のCALをrecoverWithしようlのtryAttempt。基礎となるスロー障害が発生した場合には、値を返す結果のクライアントコードが成功した場合に.get()の直接呼び出し(で展開することができTry<T>を取得するために起こっていると

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

:クライアントコードは次のように見えるように起こっています例外)またはライブラリのドキュメントに記載されている他のメソッドを使用します。

希望します。

UPDATE:

これはfilterfindFirstlimitを使用して機能的な方法でも行うことができ、任意の外部ライブラリなし:

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

クライアントコードは同じまま。また、式times回は評価されませんが、最初の正常な試行では停止しますのでご注意ください。

+0

2番目のソリューションでは、最初のソリューションのIntStream.range(0、times).mapToObj(i - > catchingSupplier)を使用して、catchingSupplierストリームを生成できますか? – srborlongan

+1

@ srborlonganはいそうです。私はちょうど0とIntStream(IMOを理解するのにはあまり役に立ちません)の言及が本当に好きではなく、制限方法を好むという意味で、可能な限り最小限の2番目のソリューションを保ちたいと思っていました。 – tkachuko

0

提案されているアプローチのいくつかと同様に、リトライの機能を残りのコードと区別するクラスを作成できます。その下のクラスはまさにそれを行い、また、あなたが入力を取り、いくつかの結果を返すことができます:

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; 
       } 
      } 
     } 
    } 
} 

をそしてあなたは、この例のようにそれを使用することができます

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

そして、コードが例外をスローする場所はどこでも再試行されます。

関連する問題