14

コピー アンド ペースト プログラミングは良くない考えだと一般に認められていますが、2 つの関数またはコード ブロックがあり、いくつかの点で異なる必要があり、それらを一般化するのが非常に厄介な状況を処理する最善の方法は何でしょうか?

いくつかの小さな違いを除いて、コードが実質的に同じであるが、それらの小さな違いが、パラメーターやテンプレート メソッドなどを追加することによって簡単に除外できない場合はどうなるでしょうか?

より一般的に言えば、わずかなコピー アンド ペースト コーディングが本当に正当化されると認めるような状況に遭遇したことがありますか。

4

14 に答える 14

28

関数についてこの質問をする

「この小さな要件が変更された場合、それを満たすために両方の機能を変更する必要がありますか?」

于 2008-12-30T23:44:23.417 に答える
14

もちろん許されることもあります。そのため、人々はスニペット ファイルを保持しています。しかし、コードを頻繁にカット アンド ペーストする場合、または数行以上のコードを使用する場合は、サブルーチンにすることを検討する必要があります。なんで?何かを変更しなければならない可能性が高く、このように変更する必要があるのは 1 回だけです。

中間のケースは、マクロが利用可能な場合に使用することです。

于 2008-12-30T23:44:19.410 に答える
12

3 か所以上でコードを使用しない限り、抽象化は効果がないため、コピー アンド ペーストは 1 回 (コードの複製は最大 2 回に制限) されると人々が言うのを聞いたことがあります。() 私自身、必要に応じてすぐにリファクタリングすることを良い習慣にするようにしています。

于 2008-12-31T00:59:24.500 に答える
6

はい、まさにあなたの言うとおりです。マイナーだが因数分解が困難なバリエーション。状況が本当に求めているものである場合は、自分をむち打ちしないでください。

于 2008-12-30T23:43:12.610 に答える
5

Re カット アンド パストは常に許容されます。

はい。セグメントがわずかに異なり、使い捨てシステム (非常に短い期間存在し、メンテナンスを必要としないシステム) を行っている場合。それ以外の場合は、通常、共通点を抽出することをお勧めします。

似ているが完全には似ていないセグメントについて:

違いがデータにある場合は、関数を抽出し、データの違いをパラメーターとして使用してリファクタリングします (パラメーターとして渡すデータが多すぎる場合は、それらをオブジェクトまたは構造にグループ化することを検討してください)。関数内の一部のプロセスに違いがある場合は、コマンド パターンまたは抽象テンプレートを使用してリファクタリングします。これらの設計パターンを使用してもリファクタリングが難しい場合は、関数が多くの責任を単独で処理しようとしている可能性があります。

たとえば、diff#1 と diff#2 の 2 つのセグメントが異なるコード セグメントがあるとします。そして、diff#1 では、diff1A または diff1B を持つことができ、diff#2 では、diff2A と diff2B を持つことができます。

diff1A と diff2A が常に一緒であり、diff1B と diff2B が常に一緒である場合、diff1A と diff2A を 1 つのコマンド クラスまたは 1 つの抽象テンプレート実装に含めることができ、diff1B と diff2B を別のクラスに含めることができます。

ただし、複数の組み合わせ (つまり diff1A & diff2A、diff1A & diff2B、diff1B & diff2A、diff1B & diff2B ) がある場合は、機能を再考する必要があるかもしれません。

Re SQL ステートメント:

ロジック (if-else、ループ) を使用して SQL を動的に構築すると、読みやすさが犠牲になります。しかし、すべての SQL バリエーションを作成するのは維持が困難です。そこで途中で出会い、SQL セグメントを使用します。共通点を SQL セグメントとして抽出し、それらの SQL セグメントを定数として使用してすべての SQL バリエーションを作成します。

例えば:

private static final String EMPLOYEE_COLUMNS = " id, fName, lName, status";

private static final String EMPLOYEE_TABLE = " employee";

private static final String EMPLOYEE_HAS_ACTIVE_STATUS = " employee";

private static final String GET_EMPLOYEE_BY_STATUS =
  " select" + EMPLOYEE_COLUMNS + " from" + EMPLOYEE_TABLE + " where" + EMPLOYEE_HAS_ACTIVE_STATUS;

private static final String GET_EMPLOYEE_BY_SOMETHING_ELSE =
  " select" + EMPLOYEE_COLUMNS + " from" + EMPLOYEE_TABLE + " where" + SOMETHING_ELSE;
于 2008-12-31T00:55:59.937 に答える
4

マーティンファウラーが示唆するように、

一度やってください、結構です。

二度やると、においがし始めます。

それを3回行い、リファクタリングする時間です。


編集:コメントへの回答として、アドバイスの起源はドン・ロバーツです:

3回のストライキで、リファクタリングします。

Martin Fowlerは、リファクタリングの第2章、「三つのルール」(58ページ)のセクションでそのことを説明しています。

于 2008-12-31T02:52:52.227 に答える
3

私の会社のコード ベースには、高度な共通性を持つ一連の約 10 個ほどの毛むくじゃらの SQL ステートメントがあります。すべてのステートメントには、共通のコア、または少なくとも 1 つまたは 2 つの単語だけが異なるコアがあります。次に、10 個のステートメントを 3 つまたは 4 つのグループにグループ化して、コアに共通の付属物を追加します。ここでも、各付属物でおそらく 1 つか 2 つの単語が異なります。いずれにせよ、10 個の SQL ステートメントをベン図のセットと考えてください。

これらのステートメントは、重複を避けるようにコーディングすることを選択しました。そのため、ステートメントを構築するための関数 (技術的には Java メソッド) があります。共通コアの違いの 1 つまたは 2 つを説明するいくつかのパラメーターが必要です。次に、付属物を構築するためのファンクターが必要です。これはもちろん、マイナーな違いについてはより多くのパラメーターでパラメーター化され、より多くの付属物についてはより多くのファンクターなどでパラメーター化されます。

このコードは、SQL がまったく繰り返されないという点で優れています。SQL の句を変更する必要がある場合は、それを 1 か所だけ変更するだけで、それに応じて 10 個の SQL ステートメントすべてが変更されます。

しかし、人間は読みにくいコードです。特定のケースで実行される SQL を把握する唯一の方法は、完全にアセンブルされた後にデバッガーを使用して SQL を出力することです。そして、句を生成する特定の関数が全体像にどのように適合するかを理解するのは厄介です。

これを書いて以来、SQL クエリを 10 回カット アンド ペーストしたほうがよかったのではないかとよく考えていました。もちろん、これを行った場合、SQL への変更は 10 か所で発生する必要があるかもしれませんが、コメントは更新する 10 か所を指摘するのに役立ちます。

SQL を理解しやすく、すべてを 1 か所にまとめることの利点は、SQL をカット アンド ペーストすることの欠点をおそらく上回るでしょう。

于 2008-12-31T00:03:36.017 に答える
2
  1. 良いコードは再利用可能なコードです。
  2. 車輪を再発明しないでください。
  3. 例が存在するのには理由があります: 学習を助け、理想的にはより良いコーディングを行うためです。

コピーして貼り付ける必要がありますか?誰も気にしない!重要なのは、なぜコピーして貼り付けているのかということです。ここで誰かを哲学的にしようとしているわけではありませんが、これについて実際に考えてみましょう。

怠惰からですか?「何とか、私は以前にこれをやったことがあります...私はいくつかの変数名を変更しているだけです..完了しました。」

コピーして貼り付ける前に既に適切なコードであった場合は問題ありません。そうしないと、怠惰からくだらないコードを永続化させてしまい、将来的にお尻を噛むことになります.

わからないからですか?「くそー..その関数がどのように機能するかわかりませんが、私のコードで機能するかどうか..」そうかもしれません! これにより、締め切りが午前 9 時で、午前 4 時ごろの時計を赤目で見つめているとストレスを感じる瞬間に、時間を節約できます。

このコードに戻ったときに理解できますか? コメントしても?いいえ、そうではありません。何千行ものコードを書いた後で、そのコードが何を行っているかを理解していない場合、数週間後、数か月後に戻ってくるとどうやって理解できるでしょうか? それ以外のすべての誘惑にもかかわらず、それを学ぼうとします。入力すると、メモリにコミットするのに役立ちます。入力する行ごとに、その行が何をしているのか、その機能の全体的な目的にどのように貢献しているかを自問してください。徹底的に学ばなくても、後で戻ってきたときに、少なくとも認識できる可能性はあります。

それで - コードをコピーして貼り付けますか?自分がしていることの意味を意識していれば問題ありません。さもないと?やらないでください。また、コピーして貼り付けるサードパーティ コードのライセンスのコピーがあることを確認してください。当たり前のように思えますが、そうでない人が多いことに驚かれることでしょう。

于 2009-01-02T12:07:09.413 に答える
2

絶対に絶対に..

:)

問題のコードを投稿すると、見た目よりも簡単であることがわかります

于 2008-12-30T23:44:16.163 に答える
2

それがそれを行う唯一の方法である場合は、それを選択してください。多くの場合 (言語によって異なります)、オプションの引数を使用して同じ関数に小さな変更を加えることができます。

最近、PHP スクリプトに add() 関数と edit() 関数がありました。どちらも実質的に同じことを行いましたが、edit() 関数は INSERT クエリではなく UPDATE クエリを実行しました。私はちょうど次のようなことをしました

function add($title, $content, $edit = false)
{
    # ...
    $sql = edit ? "UPDATE ..." : "INSERT ...";
    mysql_unbuffered_query($sql);
}

うまくいきましたが、コピー/貼り付けが必要な場合もあります。それを防ぐために、奇妙で複雑なパスを使用しないでください。

于 2008-12-30T23:53:16.880 に答える
1

ペストのように切り貼りは避けます。いとこのクローンと変更よりもさらに悪いです。あなたのような状況に直面した場合、私はいつでもマクロ プロセッサまたは他のスクリプトを使用してさまざまなバリエーションを生成する準備ができています。私の経験では、単一の真実が非常に重要です。

残念ながら、C マクロ プロセッサは、改行、式、ステートメント、および引数の面倒な引用要件のため、この目的にはあまり適していません。私は書くのが嫌いです

#define RETPOS(E) do { if ((E) > 0) then return; } while(0)

しかし、その引用は必要です。ツールチェーンに別のアイテムを追加せず、ビルド プロセスや Makefile を変更する必要がないため、C プリプロセッサには欠陥がありますが、よく使用します。

于 2008-12-30T23:49:14.390 に答える
0

最善の方法 (一般的な関数に変換するか、マクロを使用する以外) は、コメントを入れることです。コードのコピー元とコピー先、共通点、相違点、およびその理由をコメントすると...それなら大丈夫です。

于 2008-12-30T23:56:55.733 に答える
0

機能がほとんど同じであるが、さまざまなシナリオで微調整が必​​要であることがわかった場合、問題は設計にあります。フラグやコピー&ペーストの代わりにポリモーフィズムとコンポジションを使用します。

于 2008-12-30T23:57:59.477 に答える
0

これは確かに主観的であるため、これが主観的であるとタグ付けされてうれしいです! これは非常にあいまいな例ですが、複製されたコードが十分にある場合、それらのセクションを抽象化し、異なる部分を別々に保つことができると思います。コピー アンド ペーストを行わないことのポイントは、保守が難しく壊れやすいコードにならないようにすることです。

于 2008-12-30T23:46:03.307 に答える