20

現在、非常に古い C++ プロジェクトをレビューしていて、コードの重複がたくさんあります。

たとえば、それぞれが 10 行の同一のコードを保持する 5 つの MFC メッセージ ハンドラーを持つクラスがあります。または、非常に具体的な文字列変換のための 5 行のスニペットがあちこちにあります。このような場合、コードの重複を減らすことはまったく問題になりません。

でも、何かを誤解しているのかもしれないし、元々この重複には理由があったのかもしれないという不思議な感覚があります。

コードを複製する正当な理由は何ですか?

4

20 に答える 20

26

これについては、John Lakos による大規模な C++ ソフトウェア設計が参考になります。

彼はコードの複製について多くの良い点を持っており、それがプロジェクトを助けたり妨げたりするかもしれません。

最も重要なポイントは、重複または重複コードを削除することを決定する際に尋ねることです。

このメソッドが将来変更された場合、複製されたメソッドの動作を変更したいですか、それともそのままにしておく必要がありますか?

結局のところ、メソッドには (ビジネス) ロジックが含まれており、呼び出し元ごとにロジックを変更したい場合もあれば、そうでない場合もあります。状況によります。

結局のところ、きれいなソースではなく、メンテナンスがすべてです。

于 2009-09-01T08:17:03.207 に答える
16

怠惰、それが私が考えることができる唯一の理由です。

もっと深刻なことに。私が考えることができる唯一の正当な理由は、製品サイクルの最後の変更です。これらはより精査される傾向があり、最小の変更が成功率が最も高くなる傾向があります。その限られた状況では、小さな変更をリファクタリングするよりも、コードの重複を変更する方が簡単です。

それでも口の中に嫌な味が残ります。

于 2009-09-01T05:31:58.157 に答える
14

私が最初にプログラミングを始めたとき、私は 20 ~ 30 行のきちんとした小さな関数にまとめた、似たような機能がたくさんあるアプリを書きました... 私は、このようなエレガントなコードを書いたことをとても誇りに思っていました。

その直後、クライアントは非常に特殊なケースでプロセスを変更し、それから何度も何度も何度も何度も .... (何度も何度も) 私のエレガントなコードは非常に難しく、ハック的で、バグのある、 &高メンテナンス混乱。

1 年後、非常に似たようなことを依頼されたとき、私は意図的に DRY を無視することにしました。基本的なプロセスをまとめ、すべての重複コードを生成しました。重複したコードは文書化されており、コードの生成に使用したテンプレートを保存しました。クライアントが特定の条件変更を要求したとき (たとえば、x == y^z + b then 1+2 == 3.42)、それは簡単なことでした。メンテナンスと変更は信じられないほど簡単でした。

振り返ってみると、これらの問題の多くは関数ポインターと述語を使用して解決できたはずですが、当時の知識を使用して、この特定のケースではこれが最善の決定だったと今でも信じています。

于 2009-09-01T12:49:50.967 に答える
14

経験が浅いだけでなく、コードの重複が表示される理由があります。

適切にリファクタリングする時間がない

私たちのほとんどは、実際の制約により、コードの良さを考えるのではなく、実際の問題に迅速に移行することを強いられる現実の世界で働いています。ですから、コピー&ペーストして先に進みます。私の場合、後でコードがさらに数回複製されていることがわかった場合は、そのコードにもう少し時間をかけて、すべてのインスタンスを 1 つに収束させる必要があるという兆候です。

言語の制約により、コードの一般化が不可能/「きれい」ではない

関数の奥深くに、同じ複製コードのインスタンスごとに大きく異なるいくつかのステートメントがあるとしましょう。例: ビデオのサムネイルの 2 次元配列を描画する関数があり、各サムネイル位置の計算が埋め込まれています。ヒット テストを計算する (クリック位置からサムネイル インデックスを計算する) ために、同じコードを使用していますが、ペイントはしていません。

一般化があるかどうかはまったくわかりません

最初にコードを複製し、後でそれがどのように進化するかを観察します。私たちはソフトウェアを書いているので、すべてが「ソフト」で変更可能であるため、ソフトウェアへの「できるだけ遅い」変更を許可できます。

また何か思い出したら追記します。


後で追加...

ループ展開

Einstein と Hawking が組み合わされたようにコンパイラがスマートになる前は、高速化のためにループまたはインライン コードをアンロールする必要がありました。ループのアンロールにより、コードが複製され、おそらく数パーセント高速になりますが、コンパイラはとにかくそれを行いませんでした。

于 2009-09-01T06:51:57.730 に答える
12

ある部分の将来の変更が意図せずに他の部分を変更しないようにするために、そうすることが必要な場合があります。たとえば、考えてみましょう

Do_A_Policy()
{
  printf("%d",1);
  printf("%d",2);
}

Do_B_Policy()
{
  printf("%d",1);
  printf("%d",2);
}

次のような関数を使用して、「コードの重複」を防ぐことができます。

first_policy()
{
printf("%d",1);
printf("%d",2);
}

Do_A_Policy()
{
first_policy()
}

Do_B_Policy()
{
first_policy()
}

ただし、他のプログラマーが Do_A_Policy() を変更したいと考え、first_policy() を変更することでそうするリスクがあり、Do_B_Policy() を変更することによる副作用 (プログラマーが認識していない可能性がある) を引き起こす可能性があります。したがって、この種の「コードの重複」は、この種の将来のプログラムの変更に対する安全メカニズムとして機能します。

于 2009-09-01T05:50:05.657 に答える
6

ドメインに関しては共通点がないメソッドとクラスの場合もありますが、実装に関しては非常によく似ています。これらの場合、将来の変更がより頻繁に行われるため、コードの複製を行う方が良いことがよくあります。これにより、これらの実装が同じではないものに分岐することはありません。

于 2009-09-01T06:00:31.970 に答える
4

私が考えることができる正当な理由: 重複を避けるためにコードがより複雑になる場合。基本的には、いくつかの方法でほぼ同じことを行う場所ですが、まったく同じではありません。もちろん、変更が必要なさまざまなメンバーへのポインターを含む特別なパラメーターをリファクタリングして追加できます。しかし、リファクタリングされた新しいメソッドは複雑になりすぎる可能性があります。

例 (疑似コード):

procedure setPropertyStart(adress, mode, value)
begin
  d:=getObject(adress)
  case mode do
  begin
    single: 
       d.setStart(SingleMode, value);
    delta:
       //do some calculations
       d.setStart(DeltaSingle, calculatedValue);
   ...
end;

procedure setPropertyStop(adress, mode, value)
begin
  d:=getObject(adress)
  case mode do
  begin
    single: 
       d.setStop(SingleMode, value);
    delta:
       //do some calculations
       d.setStop(DeltaSingle, calculatedValue);
   ...
end;

メソッド呼び出し (setXXX) をリファクタリングすることもできますが、言語によっては難しい場合があります (特に継承の場合)。本体の大部分は各プロパティで同じであるため、コードの重複ですが、共通部分をリファクタリングするのは難しい場合があります。

要するに、リファクタリングされた方法がより複雑な要因である場合、それは「悪」ですが(そして悪のままです)、コードの複製を使用します。

于 2009-09-01T07:08:44.250 に答える
3

そのようなコードの重複 (多くの行が何度も重複している) については、次のように言います。

  • どちらかの怠惰(アプリケーションの他の部分に影響を与える可能性があることを心配することなく、あちこちにコードを貼り付けるだけです。新しい関数を作成して 2 つの場所で使用すると、何らかの影響があると思います)
  • または良い習慣を知らない(コードの再利用、異なる関数/メソッドでの異なるタスクの分離)

しかし、私が一般的に見たものから、おそらく最初の解決策です:-(

それに対して私が見た最良の解決策は、開発者が雇われたときに、古いアプリケーションを維持することから始めてもらうことです.最も重要な部分。

コードをいくつかの関数に分割し、コードを正しい方法で再利用すること、そして多くの場合、すべて経験が必要です -- または、適切な人材を雇っていない ;-)

于 2009-09-01T05:36:06.107 に答える
3

これが原因であると私が見ることができる唯一の「有効な」ことは、それらのコード行が異なっていて、その後の編集によって同じものに収束した場合です。私は以前にこれに遭遇したことがありますが、それほど頻繁ではありません。

もちろん、これは、コードのこの共通部分を新しい機能に分解するときです。

とはいえ、重複したコードを正当化する合理的な方法は思いつきません。ダメな理由を見てください。

1 か所を変更するには複数の場所を変更する必要があるため、これは悪いことです。これは時間の増加であり、バグの可能性があります。それを除外することにより、コードを単一の作業場所に維持します。結局のところ、プログラムを書くときは 2 回書きません。なぜ関数が違うのでしょうか?

于 2009-09-01T05:34:20.943 に答える
2

コードが重複する正当な理由はよくわかりませんが、最初からリファクタリングに飛びつくよりも、変更していない大きなコードベースを変更するよりも、実際に変更するコードのビットのみをリファクタリングする方がよいでしょう。それでも完全に理解します。

于 2009-09-01T12:54:07.597 に答える
2

昔、私がグラフィックスプログラミングを行っていたとき、いくつかの特別なケースでは、コードで生成された低レベルの JMP ステートメントを回避するために、このように重複したコードを使用していました (ラベル/関数へのジャンプを回避することでパフォーマンスが向上します)。 . これは、疑似「インライン化」を最適化して実行する方法でした。

ただ、今回の場合は、それでやっていたとは思えませんね。

于 2009-09-01T05:48:22.430 に答える
2

異なるタスクがたまたま似ている場合、2 つの場所で同じアクションを繰り返すことは必ずしも重複ではありません。ある場所での行動が変化した場合、他の場所でも同様に変化する可能性はありますか? 次に、これは回避またはリファクタリングする必要がある重複です。

また、ロジックが重複している場合でも、重複を減らすためのコストが高すぎる場合があります。これは、コードの重複だけではない場合に特に発生する可能性があります。たとえば、さまざまな場所 (DB テーブル定義、C++ クラス、テキストベースの入力) で繰り返される特定のフィールドを持つデータのレコードがある場合、これを減らす通常の方法は複製はコード生成で行われます。これにより、ソリューションが複雑になります。ほとんどの場合、この複雑さは報われますが、報われない場合もあります。それはあなたのトレードオフです。

于 2009-09-01T06:26:41.603 に答える
1

コードの複製を余儀なくされたのは、ピクセル操作コードでした。非常に大きな画像を処理し、関数呼び出しのオーバーヘッドがピクセルあたりの時間の30%程度を消費していました。

ピクセル操作コードを複製すると、コードが複雑になる代わりに、画像の走査が20%速くなります。

これは明らかに非常にまれなケースであり、最終的にはソースを大幅に肥大化させました(300行の関数は1200行になりました)。

于 2009-09-09T04:03:40.090 に答える
1

元の作者は経験が浅かったか、時間に追われていたようです。ほとんどの経験豊富なプログラマーは、再利用されるものを束ねます。これは、後のメンテナンスが少なくなるためです。これは一種の怠惰です。

チェックする必要がある唯一のことは、副作用があるかどうかです。コピーされたコードが一部のグローバル データにアクセスする場合は、少しリファクタリングが必要になる場合があります。

編集:コンパイラが駄目で、オプティマイザがさらに駄目だった時代には、コンパイラのバグが原因で、バグを回避するためにそのようなトリックをしなければならないことがありました. たぶんそのようなものですか?歳は何歳ですか?

于 2009-09-01T05:48:40.103 に答える
1

大規模なプロジェクト (コードベースが 1 GB にも及ぶプロジェクト) では、既存の APIが失われる可能性が非常に高くなります。これは通常、ドキュメントが不十分であるか、プログラマーが元のコードを見つけられないことが原因です。したがって、コードが重複しています。

つまるところ、怠惰、または不十分なレビュー慣行に帰着します。

編集:

1 つの追加の可能性は、途中で削除されたこれらのメソッドに追加のコードが含まれている可能性があることです。

ファイルの改訂履歴を確認しましたか?

于 2009-09-01T06:32:33.027 に答える
1

すべての答えは正しいように見えますが、別の可能性があると思います。あなたの言うことは「インラインコード」を思い出させるので、パフォーマンスに関する考慮事項があるかもしれません。それらを呼び出す関数をインライン化する方が常に高速です。おそらく、あなたが見ているコードは最初に前処理されていますか?

于 2009-09-01T08:09:26.763 に答える
1

ソース コード ジェネレーターで生成されたコードの重複は問題ありません。

于 2009-09-01T09:44:24.513 に答える
0

「戦略パターン」があるので、コードを複製する正当な理由はありません。コードの 1 行も複製する必要はありません。それ以外はすべて大失敗です。

于 2010-07-06T08:32:17.160 に答える
0

コードの重複に正当な理由はありません。

容赦なくリファクタリングする設計パターンを参照してください。

元のプログラマーは、締め切りに間に合わせるために急いでいたか、怠け者でした。自由にリファクタリングしてコードを改善してください。

于 2009-09-01T05:34:46.123 に答える
0

私の謙虚な意見では、コードを複製する場所はありません。たとえば、このウィキペディアの記事を見てください

または、Larry Wall の引用を参照してみましょう。

「怠惰、短気、傲慢というプログラマーの三大美徳を伸ばすことを奨励します。」

コードの重複が「怠惰」とは何の関係もないことは明らかです。ハハ;)

于 2009-11-02T14:24:58.773 に答える