72

コードの繰り返しが多いことは一般的に悪いことであり、これを最小限に抑えるのに役立つ設計パターンがあります。ただし、言語自体の制約により、避けられない場合もあります。から次の例を取り上げますjava.util.Arrays

/**
 * Assigns the specified long value to each element of the specified
 * range of the specified array of longs.  The range to be filled
 * extends from index <tt>fromIndex</tt>, inclusive, to index
 * <tt>toIndex</tt>, exclusive.  (If <tt>fromIndex==toIndex</tt>, the
 * range to be filled is empty.)
 *
 * @param a the array to be filled
 * @param fromIndex the index of the first element (inclusive) to be
 *        filled with the specified value
 * @param toIndex the index of the last element (exclusive) to be
 *        filled with the specified value
 * @param val the value to be stored in all elements of the array
 * @throws IllegalArgumentException if <tt>fromIndex &gt; toIndex</tt>
 * @throws ArrayIndexOutOfBoundsException if <tt>fromIndex &lt; 0</tt> or
 *         <tt>toIndex &gt; a.length</tt>
 */
public static void fill(long[] a, int fromIndex, int toIndex, long val) {
    rangeCheck(a.length, fromIndex, toIndex);
    for (int i=fromIndex; i<toIndex; i++)
        a[i] = val;
}

上記のスニペットは、ソース コードに 8 回表示されます。ドキュメンテーション/メソッド シグネチャのバリエーションはほとんどありませんが、メソッド本体はまったく同じint[]short[]ルート配列型、、、、、、、、およびchar[]byte[]boolean[]double[]float[]Object[]

反省(それ自体はまったく別の主題です)に頼らない限り、この繰り返しは避けられないと私は信じています。ユーティリティ クラスとして、このように反復的な Java コードが非常に集中することは非常に非典型的であることは理解していますが、ベスト プラクティスを使用しても反復は発生します。リファクタリングは、常に可能であるとは限らないため、常に機能するとは限りません (明らかなケースは、繰り返しがドキュメントにある場合です)。

明らかに、このソース コードを維持することは悪夢です。ドキュメンテーションのわずかなタイプミス、または実装のマイナーなバグは、何回繰り返しても乗算されます。実際、最良の例はたまたまこの正確なクラスを含んでいます:

Google Research ブログ - おまけ、おまけ - すべてを読む: ほぼすべてのバイナリ検索とマージソートが壊れている (Joshua Bloch、ソフトウェア エンジニア)

このバグは驚くほど微妙なもので、単純で単純なアルゴリズムと多くの人が考えていたもので発生しています。

    // int mid =(low + high) / 2; // the bug
    int mid = (low + high) >>> 1; // the fix

上記の行は、ソース コードで 11 回表示されます

だから私の質問は:

  • この種の反復的な Java コード/ドキュメントは、実際にはどのように処理されますか? それらはどのように開発、維持、およびテストされますか?
    • 「オリジナル」から始めて、できるだけ成熟させてから、必要に応じてコピーして貼り付け、間違いがなかったことを願っていますか?
    • 元のファイルで間違いを犯した場合は、コピーを削除して複製プロセス全体を繰り返すことに慣れていない限り、どこでも修正できますか?
    • そして、これと同じプロセスをテスト コードにも適用しますか?
  • Java は、この種の目的のために、使用が制限されたソース コードの前処理を行うことで利益を得るでしょうか?
    • おそらく Sun は、この種の反復的なライブラリ コードの作成、保守、文書化、およびテストを支援する独自のプリプロセッサを持っているのでしょうか?

コメントで別の例が要求されたので、Google Collections からこれを取得しました: com.google.common.base.Predicates行 276-310 ( AndPredicate) vs 行 312-346 ( OrPredicate)。

これら 2 つのクラスのソースは、次の点を除いて同一です。

  • AndPredicatevs OrPredicate(それぞれがそのクラスに 5 回出現)
  • "And("vs Or("(それぞれのtoString()方法で)
  • #andvs #or( @seeJavadoc コメント内)
  • truevs false(in apply;!は式の外に書き直すことができます)
  • -1 /* all bits on */0 /* all bits off */hashCode()
  • &=|=hashCode()
4

9 に答える 9

32

絶対にパフォーマンスが必要な人にとっては、ボックス化とボックス化解除、一般化されたコレクションなどは大したことではありません。

float と double の両方で機能するために同じ複合体が必要なパフォーマンス コンピューティングでも同じ問題が発生します (たとえば、Goldberd の論文すべてのコンピューター科学者が浮動小数点数について知っておくべきことで示されている方法のいくつかを挙げてください)。

同様の量のデータを扱う場合に、 TroveTIntIntHashMap実行が Java の周りを循環するのには理由があります。HashMap<Integer,Integer>

さて、Trove コレクションのソース コードはどのように書かれているのでしょうか?

もちろん、ソースコードのインストルメンテーションを使用して:)

コードジェネレーターを使用して繰り返しソースコードを作成する、より高いパフォーマンス (デフォルトの Java ライブラリーよりもはるかに高い) のための Java ライブラリーがいくつかあります。

私たちは皆、「ソースコードの計測」が悪であり、コード生成がくだらないことを知っていますが、それでも、自分が何をしているのかを本当に知っている人 (つまり、Trove のようなものを書くような人) はそうしています :)

価値のあるものとして、次のような重大な警告を含むソース コードを生成します。

/*
 * This .java source file has been auto-generated from the template xxxxx
 * 
 * DO NOT MODIFY THIS FILE FOR IT SHALL GET OVERWRITTEN
 * 
 */
于 2010-02-26T04:06:51.940 に答える
16

どうしてもコードを複製する必要がある場合は、与えた優れた例に従って、変更を加える必要があるときに簡単に見つけて修正できる1つの場所にすべてのコードをグループ化します。重複を文書化し、さらに重要なことに、重複の理由を文書化して、あなたの後に来るすべての人が両方を認識できるようにします。

于 2010-02-25T20:10:52.430 に答える
6

ウィキペディアからDon't Repeat Yourself (DRY) または Duplication is Evil (DIE)

コンテキストによっては、DRY 哲学を実施するために必要な労力は、データの個別のコピーを維持するための労力よりも大きい場合があります。他のコンテキストでは、複製された情報は不変であるか、DRY を必要としないほど厳密に管理されています。

そのような問題を防ぐための答えやテクニックはおそらくありません。

于 2010-02-25T20:20:25.673 に答える
4

Haskell のような派手なパンツ言語でさえ、反復的なコードがあります ( haskell とシリアライゼーションに関する私の記事を参照してください) 。

この問題には 3 つの選択肢があるようです。

  1. リフレクションを使用してパフォーマンスを低下させる
  2. お使いの言語に対応する Template Haskell や Caml4p などの前処理を使用して、厄介な問題に対処してください
  3. または、あなたの言語がそれをサポートしている場合、私の個人的なお気に入りの使用マクロ (scheme と lisp)

マクロは通常、ターゲットと同じ言語であるのに対し、前処理は別の言語であるため、マクロは前処理とは異なると考えています。

Lisp/Scheme マクロは、これらの問題の多くを解決すると思います。

于 2010-04-28T01:02:00.990 に答える
2

類似していると主張されている2つのコードフラグメントを考えると、ほとんどの言語には、コードフラグメントをモノリスに統合する抽象化を構築するための機能が制限されています。あなたの言語がそれを行うことができないときに抽象化するには、言語の外に出る必要があります:-{

最も一般的な「抽象化」メカニズムは、「マクロ本体」をインスタンス化しながら任意の計算を適用できる完全なマクロプロセッサです(Turing対応 のPostまたは文字列書き換えシステムを考えてみてください)。 M4GPMは典型的な例です。Cプリプロセッサはこれらの1つではありません。

このようなマクロプロセッサを使用している場合は、「抽象化」をマクロとして作成し、「抽象化された」ソーステキストに対してマクロプロセッサを実行して、コンパイルして実行する実際のソースコードを生成できます。

また、「コードジェネレーター」と呼ばれる、より限定されたバージョンのアイデアを使用することもできます。これらは通常チューリング対応ではありませんが、多くの場合、十分に機能します。それはあなたの「マクロインスタンス化」がどれほど洗練されている必要があるかに依存します。(人々がC ++テンプレートメカニズムに夢中になっている理由は、その醜さにもかかわらず、 Turingに対応しているため、人々はそれを使って本当に醜いが驚くべきコード生成タスクを実行できます)。ここでの別の答えは、明らかにもっと限定されているが、それでも非常に有用なカテゴリーにあるTroveに言及しています。

本当に一般的なマクロプロセッサ(M4など)はテキストだけを操作します。それはそれらを強力にしますが、プログラミング言語の構造をうまく処理しません。そして、コードを生成するだけでなく、生成された結果を最適化できるようなmcaroプロセッサでジェネラを書くのは本当に厄介です。私が遭遇するほとんどのコードジェネレーターは「この文字列をこの文字列テンプレートにプラグインする」ため、生成された結果の最適化を行うことはできません。任意のコードを生成して高パフォーマンスで起動したい場合は、Turingに対応しているが、生成されたコードの構造を理解しているため、コードを簡単に操作(最適化など)できるものが必要です。

このようなツールは、プログラム変換システムと呼ばれます。このようなツールは、コンパイラと同じようにソーステキストを解析し、分析/変換を実行して目的の効果を実現します。プログラムのソーステキストにマーカーを配置して(たとえば、構造化されたコメントや、それらを含む言語の注釈)、プログラム変換ツールに何をすべきかを指示できる場合は、それを使用して、そのような抽象化のインスタンス化、コード生成、および/またはコードの最適化。(Javaコンパイラにフックするという1つのポスターの提案は、このアイデアのバリエーションです)。一般的なpuprose変換システム(DMS Software Reengineering Tookitなど)を使用すると、基本的にすべての言語でこれを実行できます。

于 2010-03-06T16:30:28.053 に答える
2

SunはJavaSEライブラリコードについてこのように文書化する必要があり、おそらく他のサードパーティのライブラリライターも同様に文書化する必要があります。

ただし、このようなファイル全体に、社内でのみ使用されるコードでドキュメントをコピーして貼り付けるのは、まったく無駄だと思います。社内のJavaDocの見た目が悪くなるため、多くの人が反対することを私は知っています。ただし、トレードオフは、コードをよりクリーンにすることです。これは、私の意見では、より重要です。

于 2010-02-25T20:23:25.187 に答える
2

特に配列に関しては、Javaプリミティブ型はあなたを困惑させます。プリミティブ型を含むコードについて具体的に質問している場合は、それらを避けてください。ボックス型を使用する場合は、Object[]メソッドで十分です。

一般に、多くの単体テストが必要であり、リフレクションに頼る以外に何もする必要はありません。あなたが言ったように、それは完全に別の主題ですが、反省をあまり恐れないでください。最初にできるDRYestコードを記述し、次にそれをプロファイリングして、リフレクションパフォーマンスのヒットが、余分なコードを書き出して維持するのに十分なほど悪いかどうかを判断します。

于 2010-02-25T20:26:36.143 に答える
2

コード ジェネレーターを使用して、テンプレートを使用してコードのバリエーションを作成できます。その場合、Java ソースはジェネレーターの製品であり、実際のコードはテンプレートです。

于 2010-02-25T20:55:31.280 に答える
1

ジェネリックのおかげで、この種の繰り返しの多くを回避できるようになりました。型だけが変わる同じコードを書くとき、それらは天の恵みです。

残念ながら、ジェネリック配列はまだ十分にサポートされていないと思います。少なくとも現時点では、ジェネリックを利用できるコンテナーを使用してください。ポリモーフィズムは、この種のコードの重複を減らすための便利なツールでもあります。

絶対に複製しなければならないコードの処理方法に関する質問に答えるには... 簡単に検索できるコメントで各インスタンスにタグを付けます。C スタイルのマクロを追加する Java プリプロセッサがいくつかあります。私はnetbeansが持っていたことを覚えていると思います。

于 2010-02-25T20:08:29.923 に答える