2016-11-23 3 views
3

注:ループ内のロジックステートメントを避けたいという印象があります。これは、反復が予測可能な動作をするループをコンパイラがどのように最適化できるかによって部分的に述べられたと思います。私がほとんど確信している間に私はこれを以前に聞いたことがあり、長い間、私はこれを大会と考えていました。悲しいことに私は良い参考文献を見つけることができませんでした。しかし、これが本当であれば、 "DRY"原則(自分自身を繰り返さないでください)のためにいくつかの相反するケースがあります。ループのロジックは?または、むしろ2つの別々のほぼ同一のループですか?

PRETEXT:この例で使用したようにかなり大きなデータセット、たとえば多次元配列があると仮定します。さらに、すべてのエントリをトラバースして、要素のすべてまたは一部に対して何らかの操作を行う必要があること、およびその操作の1つまたは複数を実行することを選択できる必要があると仮定します。これは、コードの90%〜99%が2つのメソッドを作成することを要求しますが、演算子またはメソッド呼び出しのみが異なります。これがC++だったのであれば、ループ関数への関数ポインタを提供したいと思っていましたが、これも避けたいのか分かりません。

質問:ロジックステートメントを使用し、1つのループまたはむしろ2つのほぼ同じメソッドを使用することをお勧めしますか?

例:

// This method is provided for completeness of the example 
// and to provide some clue as to what boolean parameter and logic statement 
// I could alternatively have implemented within the loop method instead of external to it. 
public int[][] addOrSubtractArrays(int[][] a, int[][] b, boolean useAdd){ 
    if(a == null || b == null || a.length != b.length || a.length < 1 || a[0].length != b.length) 
     return null; 
    return useAdd ? add(a, b) : subtract(a, b); 
} 

private int[][] add(int[][] a, int[][] b){ 
    int h = a.length; 
    int w = a[0].length; 
    int[][] c = new int[h][w]; 
    for(int y = 0; y < h; y++){ 
     for(int x = 0; x < w; x++){ 
      c[y][x] = a[y][x] + b[y][x]; 
     } 
    } 
    return c; 
} 

private int[][] subtract(int[][] a, int[][] b){ 
    int h = a.length; 
    int w = a[0].length; 
    int[][] c = new int[h][w]; 
    for(int y = 0; y < h; y++){ 
     for(int x = 0; x < w; x++){ 
      c[y][x] = a[y][x] - b[y][x]; 
     } 
    } 
    return c; 
} 

例2:私は "双子" メソッドソリューションがどのように見えるか冗長化を示すために、いくつかの例を提供してきました(?明白な)代替

private int[][] addOrSubtract(int[][] a, int[][] b, boolean useAdd){ 
    if(a == null || b == null || a.length != b.length || a.length < 1 || a[0].length != b.length) 
     return null; 
    int h = a.length; 
    int w = a[0].length; 
    int[][] c = new int[h][w]; 
    for(int y = 0; y < h; y++){ 
     for(int x = 0; x < w; x++){ 
      if(useAdd) 
       c[y][x] = a[y][x] + b[y][x]; 
      else 
       c[y][x] = a[y][x] - b[y][x]; 
     } 
    } 
    return c; 
} 

私は過度にしています(ほぼ)重複するコードを避けるために、ループ構造全体を保持するいくつかの一般的なメソッドを作成するように誘惑されました。しかし、 "私が聞いたこと"が合理的な文脈を持っていれば、これは私の知る限りでは避けるべきです。

+0

あなたの質問に答えてください、私は2つの機能を持つ最初の例を使用します。少しのコードを繰り返してもかまいませんが、後でそれらの関数を使用するときにコードを読みやすくしています。ブール値が何を意味するかを調べる必要はありません。 – Mats391

答えて

1

addOrSubtract内のロジックが悪いと持っていません。乗算など、他の算術演算はどうでしょうか? multiplyOrDivideを公開することができますが、選択肢がaddOrMultiplyである必要がある場合はどうなりますか?うまく拡張できません。あなたが持っている必要がありますどのような

は、クライアントが実行するためにどのような操作を指定することを可能にする方法である:

public int[][] calculate(int[][] first, int[][] second, Operation operation) { 
    if(firstArray == null || secondArray == null || firstArray.length != secondArray.length || firstArray.length < 1 || firstArray[0].length != secondArray.length) 
     throw new IllegalArgumentException("Arrays can't be null and must be of equal length."); 

    int height = firstArray.length; 
    int width = firstArray[0].length; 
    int[][] result = new int[height][width]; 
    for(int y = 0; y < height; y++){ 
     for(int x = 0; x < width; x++){ 
      result[y][x] = operation.performOn(firstArray[y][x], secondArray[y][x]); 
     } 
    } 
} 

必要に応じてあなたは今、新しい操作を追加することができます。

enum Operation { 
    ADD { 
     @Override 
     public void performOn(int firstValue, int secondValue) { 
      return firstValue + secondValue; 
     } 
    }, 
    SUBTRACT { 
     //... 
    }; 

    public abstract int performOn(int firstValue, int secondValue); 
} 

あなたがオーバーライド感じる場合このファッションはスケーリングをあまりにも冗長にしますが、ロジックをコールバック関数に委譲することで戦略パターンを利用できます。

enum Operation { //could/should implement IOperation 
    ADD((a, b) -> a + b), 
    SUBTRACT((a, b) -> a - b); 

    private IOperation operation; 

    Operation(IOperation operation) { 
     this.operation = operation; 
    } 

    public final int performOn(int firstValue, int secondValue) { 
     return operation.performOn(firstValue, secondValue); 
    } 
} 

interface IOperation { 
    int performOn(int firstValue, int secondValue); 
} 
以下に示すよう

クライアントが今、あなたの関数を使用することができます。

calculate(firstArray, secondArray, Operation.ADD); 

私はむしろ使用BiFunctionより新しい機能インタフェースを作成することを選んだ理由は、オートボクシングを避けるためです。パフォーマンスはあなたの心配であると思われます。オートボクシングはパフォーマンスに劇的な影響を与えます。特に、これを強く実行する場合は特にそうです。大きなサイズの配列からでも、小さな時間枠内で連続してaddOrSubtractを呼び出す必要があっても、落とし穴を避けるのが最善です。

IllegalArgumentExceptionは、プログラムが説明的なメッセージで「吹き飛ばす」ことを可能にします。 nullが返されました。これは、この方法を使用する人はヌルチェック(乱雑なコードの臭い、billion dollar mistake)を実行する必要がありますか、説明的なメッセージを持たないNullPointerExceptionが発生する可能性があることを意味します。

+0

私はIOperationを一度も使用していませんが、使用している構文はラムダ式に関係していますか?戻り値nullは実際のコードで行ったことではなく、例を複雑にすることを避けるためのものでした。 – Chexxor

+1

はい、機能的なインターフェイスです。インターフェイスが指定されている場所でラムダ式を作成できます。私は 'IOperation'を定義していますので、JDKではそれを見つけることはできませんが、独自のインターフェースを定義することができます。独自のインターフェースを作成したくない場合は、' BiFunction'を使用してください。 'BiFunction'を使うと、オートボックスのためにあなたの状況ではかなり高価になる可能性があります。それでは、それは事前最適化です。もしあなたが本当に 'BiFunction'を使うことの影響を心配しているならば、違いをベンチマークするべきです。 –

2

あなたが言ったように、これがC++の場合、関数ポインタを渡します。さて、あなたは、Java 8を使用している場合も、同様の何かがあるのです - BiFunction<Integer, Integer, Integer>

あなたの単一の方法は次のようになります。

private int[][] addOrSubtract(int[][] a, int[][] b, boolean useAdd){ 
    BiFunction<Integer, Integer, Integer> func = useAdd ? 
     ((a, b) -> a + b) : ((a, b) -> a - b); 
    if(a == null || b == null || a.length != b.length || a.length < 1 || a[0].length != b.length) 
     return null; 
    int h = a.length; 
    int w = a[0].length; 
    int[][] c = new int[h][w]; 
    for(int y = 0; y < h; y++){ 
     for(int x = 0; x < w; x++){ 
      c[y][x] = func.apply(a[y][x], b[y][x]); 
     } 
    } 
    return c; 
} 

をあなたは、Java 8を使用していない場合は、その後、私はあなただけのいずれかを使用することができます推測しますあなたの2つのソリューションのうち私は彼らに何か間違っているとは思わない。早めの最適化はすべての悪の根源だということを覚えておいてください。

+0

こんにちは掃除機、これは答えに無関係かもしれませんが、あなたは早期最適化を説明できますか?私はそれをgoogled、それは引用符ですが、それはまだ正確に何かを得ることができません:)ありがとう! – Yazan

+1

基本的には、他のすべてが実行される前に意識的に最適化を行わないことを意味します。私はちょうどあなたのプロジェクトを最初に行うOPを警告しています、これについては後で心配してください。 @Yazan – Sweeper

+0

okkki私はそれを得ました:)あなたのプロジェクトがまだ完了していない間に、最適化に圧倒されないでください。私にはうまくなります:) – Yazan

1

ループ内のロジックがない良いですかあれば私は他のデザインを検討することは常に良いことだ、 しかしを答えを持っていないので、これは、あなたの質問の適用範囲外である可能性があります。このような は、それが繰り返されるコードを持っていない、それはループの

public int[][] addOrSubtractArrays(int[][] a, int[][] b, boolean useAdd){ 
    if(a == null || b == null || a.length != b.length || a.length < 1 || a[0].length != b.length) 
     return null; 
    int h = a.length; 
    int w = a[0].length; 
    int[][] c = new int[h][w]; 
    for(int y = 0; y < h; y++){ 
     for(int x = 0; x < w; x++){ 
      c[y][x] = getResult(a[y][x] , b[y][x], useAdd); 
     } 
    } 

    return c; 
} 

private int getResult(int elementA, int elementB, boolean useAdd){ 
    return useAdd ? (elementA+elementB) : (elementA-elementB); 
} 
+0

ロジック部分を別の方法でアウトソーシングすることで、コンパイラはループを同じ程度に最適化できますか?あるいは、ループの中の関数呼び出しがあまり最適化されていないコードを作るでしょうか?その質問は私の問題の本質の一部です。 – Chexxor

+1

@Chexxor私はこれがまっすぐだとは思わない、あるケースではコンパイラの動作に基づいて結論づけることができますが、このケースはforループ本体にもっと多くのものがあれば、より複雑なロジック...あなたができることは、上記のすべてのメソッドをクラスに入れてコンパイルし、 '.class'ファイルをコンパイル解除するためのツールを使用し、コンパイラによってコードが変更されたかどうかを確認します。それは非常に効率的ですが、必要です〜しかし〜最適化することはできませんまたは単にそれは顕著な違いを作りません(そして、それはループの別のロジックとの違いを生み出すことができますか?) – Yazan

+0

@ Yazan私は彼が話していると信じていますランタイムコンパイルの最適化について([JITコンパイラ](http://stackoverflow.com/questions/95635/what-does-a-just-in-time-jit-compiler-do)から)。バイトコードを読み取っても実行時の最適化は決まらない。 Javaをバイトコードにコンパイルする 'javac'と、バイトコードをネイティブコードにコンパイルする(または単にバイトコードを解釈する)' jit'という2つのコンパイラがあることに留意してください。 –

関連する問題