2016-05-18 12 views
1

副作用が読み込まれたこの長いJavaコードのメソッドをより純粋なバージョンにリファクタリングするにはどうすればよいですか?オプションで純粋な関数で副作用を呼び出す

public Result nonPureMethod(String param1, String param2){ 
    this.current_status = "running"; 
    String s1 = step1(param1, param2); 
    this.logger.log("About to do step 2, this could take while"); 
    String s2 = step2(s1); 
    this.logger.log("Completed step 2"); 
    String s3 = step3(s2); 
    this.notifyOtherObject(s3); 
    if (this.UserPressedEmergencyStop){ this.current_status = "stopped"; return; } 
    String s4 = step4(s3); 
    this.current_status = "completed"; 
    this.saveFile(s4); 
    return new Result(s4); 
} 

これらの副作用はすべて実行する必要があります。しかし、時々私はこのようになりますこの方法、の「純粋な」バージョンを呼び出したい:

public static Result pureMethod(String param1, String param2){ 
    String s1 = step1(param1, param2); 
    String s2 = step2(s1); 
    String s3 = step3(s2); 
    String s4 = step4(s3); 
    return new Result(s4); 
} 

注:私は2つのメソッドを維持する必要はありません。可能であれば、私は1つ持っていたい。また、私はオプションでロギングのようないくつかの副作用を持つことができたいと思っていますが、他にはありません。このコードをリファクタリングする最良の方法は何ですか、私はそれを呼び出すことができ、オプションで副作用を持つことがあります。

私は現在Java 8を使用していますが、この問題はかなり一般的だと思います。私はこれまでに2つのアプローチを考えて問題を解決しました。まず、メソッドにブール値を渡すことができます: "runSideEffects"。 falseの場合は、副作用を実行するコードをスキップします。別のより柔軟なソリューションは、パラメータとして渡されるラムダ関数を要求し、副作用を呼び出す代わりにそれらを呼び出すことによって関数を変更することです。たとえば、 "void log(String msg)"のようなメソッドをパラメータとして渡すことができます。メソッドのプロダクション呼び出しは、メッセージをロガーに書き込む関数を渡すことができます。他の呼び出しは、log(msg)が呼び出されたときに効果的に何もしないメソッドを渡すことができます。これらのソリューションのどちらも素晴らしいとは思わないので、コミュニティに提案を求めています。

+3

のJava 8の新しいものが本当にこの方法であなたを助けるところ、私は表示されません。あなたが示唆しているのは、副作用と副作用=戦略の関係がない、大まかには(stragegyパターン)(https://en.wikipedia.org/wiki/Strategy_pattern)です。それはあなたがいつもやっていたことかもしれません(と私はそれがあなたのコードを改善するだろうと想像することはできません) – zapl

+1

2つの実装を持つことは間違いなく(@zaplによって提案されるように)行く方法です。しかし、ステータスを維持し、ファイルに保存する必要がありますか? – TriCore

+0

また、効果的なJava項目「Strive for Failure Atomicity」も読んでください。これは、一貫性のない状態になる可能性のある失敗を最小限に抑えるために、コードを整理することについていくつかのアドバイスがあります。 –

答えて

0

パス機能。関数が副作用をするようにする。関数の「純粋な」バージョンを呼び出す場合は、単に副作用関数をパラメーターとして渡すことはできません。

私は今、Githubのリポジトリなど、さまざまな言語で、これは利用可能な:https://github.com/daveroberts/sideeffects

package foo; 

import java.util.function.BiConsumer; 
import java.util.function.BooleanSupplier; 
import java.util.function.Consumer; 

public class SideEffects{ 
    public static void main(String args[]){ 
    System.out.println("Calling logic as a pure function"); 
    String result = logic("param1", "param2", null, null, null, null, null); 
    System.out.println("Result is "+result); 
    System.out.println(); 

    System.out.println("Calling logic as a regular function"); 
    result = logic("param1", "param2", 
     (level,msg)->{System.out.println("LOG ["+level+"]["+msg+"]");}, 
     (status)->{System.out.println("Current status set to: "+status); }, 
     (obj)->{System.out.println("Called notify message on object: "+obj.toString());}, 
     ()->{boolean dbLookupResult = false; return dbLookupResult;}, 
     (info)->{System.out.println("Info written to file [["+info+"]]");} 
     ); 
    System.out.println("Result is "+result); 
    } 

    public static String logic(String param1, String param2, 
     BiConsumer<String, String> log, 
     Consumer<String> setStatus, 
     Consumer<Object> notify, 
     BooleanSupplier eStop, 
     Consumer<String> saveFile){ 
    if (setStatus != null){ setStatus.accept("running"); } 
    String s1 = param1+"::"+param2; 
    if (log != null){ log.accept("INFO", "About to do Step 2, this could take awhile"); } 
    String s2 = s1+"::step2"; 
    if (log != null){ log.accept("INFO", "Completed step 2"); } 
    String s3 = s2+"::step3"; 
    if (notify != null) { notify.accept("randomobjectnotify"); } 
    if (eStop != null && eStop.getAsBoolean()){ 
    if (setStatus != null){ setStatus.accept("stopped"); } 
    return "stoppedresult"; 
    } 
    String s4 = s3+"::step4"; 
    if (setStatus != null){ setStatus.accept("completed"); } 
    if (saveFile!= null){ saveFile.accept("Logic completed for params "+param1+"::"+param2); } 
    return s4; 
    } 
} 
-1

私は別にそれは可能ですが、少しは何とかweridそう `

public int Steps(int param1,int param2){ 
//whatever you want your first step to do make result into a variable 
int param3 = param1-param2; 

//Same with step 2 ,3 and so on 
int param4 = param3*param1; 

}` 
+0

またはintだけでなくすべてのオブジェクトが必要です。 – Azamp19

+0

私はこの答えにはあまり追いついていません。私はあなたがparam 2からparam 1を減算することによって何を意味するのか、そしてparam 1によって何倍のparam 3を行うのかは分かりません。これらのステップはここではあいまいですが、多くのコード行で構成されています。私が示したかったことは、これらの行の間に状態を変更する行が他にもありますが、メソッドを呼び出すときに時々そうするのは嫌です。 – Dave

+1

わかりませんが、これはどのように質問と関係がありますか。 – TriCore

-1

のような結果の変数を使用して1に入れて、あなたではなく、それは手順を行うことができます信じています。

public class MethodPipeline<T, I, R> { 
    private final MethodPipeline<T, ?, I> prev; 
    private final int kind; 
    private final Function<? extends I, ? extends R> f; 
    private final Runnable r; 
    private final Consumer<? extends R> c; 
    private MethodPipeline(Function<? extends I, ? extends R> l, MethodPipeline<? extends T, ?, ? extends I> prev) { 
     kind = 0; 
     f = l; 
     r = null; 
     c = null; 
     this.prev = prev; 
    } 
    private MethodPipeline(Runnable l, MethodPipeline<? extends T, ?, ? extends I> prev) { 
     kind = 1; 
     f = null; 
     r = l; 
     c = null; 
     this.prev = prev; 
    } 
    private MethodPipeline(Consumer<? extends R> l, MethodPipeline<? extends T, ?, ? extends I> prev) { 
     kind = 2; 
     f = null; 
     r = null; 
     c = l; 
     this.prev = prev; 
    } 
    //...various public consructor 
    public <R1> MethodPipeline<T, R, R1> then(Function<? extends R, ? extends R1> convertor) { 
     return new MethodPipeline<>(convertor, this); 
    } 
    public MethodPipeline<T, I, R> sideEffect(Runnable sideEffect) { 
     return new MethodPipeline<>(sideEffect, this); 
    } 
    public MethodPipeline<T, I, R> sideEffect(Consumer<? extnds R> sideEffect) { 
     return new MethodPipeline<>(sideEffect, this); 
    } 
    public R run(T param, boolean sideEffect) { 
     I v = prev.run(param); 
     switch (kind) { 
     case 0: 
      return f.apply(v); 
     case 1: 
      if (sideEffect) 
       r.run(); 
      return v; 
     case 2: 
      if (sideEffect) 
       c.accept(v); 
      return v; 
     } 
    } 
} 

j.u.streamと同様に、パイプラインを設計しました。 runは型の安全のために再帰的です。注意して使用してください。パイプラインに作業をあまり入れすぎないでください。 StackOverFlowExceptionが発生する可能性があります。

PS:ウェブで書かれています。未検証。一度はコンパイルされません。自己責任。束縛された型変数は、いくつかのリファクタリングが必要な場合があります。

1

1つのオプションは、各ステップのために、空のtemplate methodでクラスにメソッドを抽出し、非純粋なバージョンのためにそれを上書きすることです:

class Method { 
    void beforeStart() {}; 
    void afterStep1(String result) {}; 
    void afterStep2(String result) {}; 
    void afterStep3(String result) {}; 
    void afterStep4(String result) {}; 

    final Result execute(String param1, String param2) { 
     beforeStart(); 
     String s1 = step1(param1, param2); 
     afterStep1(s1); 
     String s2 = step2(s1); 
     afterStep2(s2); 
     String s3 = step3(s2); 
     afterStep3(s3); 
     String s4 = step4(s3); 
     afterStep4(s4); 
     return new Result(s4); 
    } 
} 

次にあなたが上書き一の以上のサブクラスを定義することができます副作用を挿入する方法を提供しました。

2

私は偉大な解決策として、これを広告するが、あなたの状況の問題を議論するための方法として、より多くのではないよ。ここに印象的何

@SafeVarargs 
public static Result pureMethod(
    String param1, String param2, Consumer<String>... optionalSteps) { 
    if(optionalSteps.length>0) optionalSteps[0].accept(param1); 
    String s1 = step1(param1, param2); 
    if(optionalSteps.length>1) optionalSteps[1].accept(s1); 
    String s2 = step2(s1); 
    if(optionalSteps.length>2) optionalSteps[2].accept(s2); 
    String s3 = step3(s2); 
    if(optionalSteps.length>3) optionalSteps[3].accept(s3); 
    String s4 = step4(s3); 
    if(optionalSteps.length>4) optionalSteps[4].accept(s4); 
    return new Result(s4); 
} 
public Result nonPureMethod(String param1, String param2) { 
    return pureMethod(param1, param2, 
     arg -> this.current_status = "running", 
     arg -> this.logger.log("About to do step 2, this could take while"), 
     arg -> this.logger.log("Completed step 2"), 
     s3 -> { this.notifyOtherObject(s3); 
      if (this.UserPressedEmergencyStop) { 
       this.current_status = "stopped"; 
       throw new RuntimeException("stopped"); 
      } 
     }, 
     s4 -> { this.current_status = "completed"; this.saveFile(s4); }); 
} 

は、これらのオプションのアクションが共通しているのか少しです。上記のコードスニペットでは、指定された引数を使用しないアクションや、最初のアクションでは2つのパラメータのいずれかが選択されたものを受け入れます。代わりにBiConsumerを使用し、他のすべてのアクションに未使用のパラメータを指定する必要があります。しかし、ここで最悪の犯罪者は、4番目のアクションでの例外による終了です。きれいな解決策は、booleanを返す関数型を使用して続行するかどうかを判断することですが、すべてのアクションがbooleanを返すよう強制します。arg -> this.current_status = "running"のような簡単なラムダ式をarg -> { this.current_status = "running"; return true; }などに変更してください。

サイドノートでは、どのログフレームワークを使用しているのかわかりませんが、通常はロギングはすでに副作用フリーモードに切り替えることができます設定オプション。

あなたの行動を分類し、さまざまなパラメータを作成すると効果的でしょうか? Loggerと、状態更新子と、早期終了述部とを含む。

public static Result pureMethod(String param1, String param2, 
     Logger logger, ObjIntConsumer<String> statusUpdater, IntPredicate cont) { 
    statusUpdater.accept(null, 0); 
    String s1 = step1(param1, param2); 
    statusUpdater.accept(s1, 1); 
    if(!cont.test(1)) return null; 
    logger.log("About to do step 2, this could take while"); 
    String s2 = step2(s1); 
    statusUpdater.accept(s2, 2); 
    if(!cont.test(2)) return null; 
    logger.log("Completed step 2"); 
    String s3 = step3(s2); 
    statusUpdater.accept(s3, 3); 
    if(!cont.test(3)) return null; 
    String s4 = step4(s3); 
    statusUpdater.accept(s4, 4); 
    return new Result(s4); 
} 
public static Result pureMethod(String param1, String param2) { 
    Logger logger=Logger.getAnonymousLogger(); 
    logger.setLevel(Level.OFF); 
    return pureMethod(param1, param2, logger, (s,i)->{}, i->true); 
} 
public Result nonPureMethod(String param1, String param2) { 
    return pureMethod(param1, param2, this.logger, 
     (s,i)-> { switch (i) { 
      case 0: this.current_status = "running"; break; 
      case 3: this.notifyOtherObject(s); break; 
      case 4: this.current_status = "completed"; this.saveFile(s); break; 
     }}, i -> { 
      if(i==3 && this.UserPressedEmergencyStop) { 
       this.current_status = "stopped"; 
       return false; 
      } 
      else return true; 
     }); 
} 

が、それはまだいくつかの方法でnonPureMethodのユースケースにタイトです...パラメータとして

関連する問題