質問

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

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

しかし、私が何か誤解をしているのではないか、この重複にはもともと理由があったのではないかという奇妙な予感がします。

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

役に立ちましたか?

解決

私が最初にプログラミングを始めたとき、

、私はそのようなエレガントな作品を書くために自分自身を非常に誇りに思っていた...私は小ぎれい20-30ライン機能に包ま同様の機能の束を持っていたアプリを書きましたコード。

の後まもなく、クライアントは再び、再度、再度、再度、再度...(多くのより多くの回)私のエレガントなコードをハック、非常に困難になった後、非常に特殊なケースでは、プロセスを変更しました、バギー、&ハイメンテナンスの混乱ます。

私は非常によく似た何かをするように頼まれたとき、

一年後、私は意図的DRY無視することにしました。私は基本的なプロセスを一緒に入れて、すべての重複コードを生成しました。重複したコードは、文書化され、私は、コードを生成するために使用するテンプレートを保存しました。クライアントは、特定の条件の変更を要求した場合(等、もしx == y ^ Z + Bその後1 + 2 == 3.42)、それはケーキの一部でした。維持&変更することが信じられないほど簡単だった。

振り返ってみると、私はおそらく、関数ポインタと述語を持つこれらの問題の多くを解決している可能性がありますが、私は一度に持っていた知識を使って、私はまだ、この特定のケースを信じて、これが最良の決断だった。

他のヒント

これについてよく読むのは、 大規模な C++ ソフトウェア設計 ジョン・ラコス著。

彼は、コードの重複がプロジェクトに役立つ場合もあれば、妨げになる場合もあるという点で、多くの優れた点を持っています。

最も重要な点は、重複または重複コードを削除するかどうかを決定するときに次のことを尋ねることです。

将来このメソッドが変更された場合、複製されたメソッドの動作を変更する必要がありますか、それとも現状のままにする必要がありますか?

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

結局のところ、重要なのはメンテナンスに関するものであり、きれいなソースに関するものではありません。

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

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

まだ私の口の中で悪趣味を残しています。

経験が浅いことに加えて、コードの重複が発生する可能性がある理由は次のとおりです。

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

私たちのほとんどは現実の世界で働いており、実際の制約により、コードの良さについて考えるのではなく、現実の問題にすぐに取り組む必要があります。したがって、コピー&ペーストして次に進みます。私の場合、後でコードがさらに数回重複していることに気付いた場合は、もう少し時間をかけてすべてのインスタンスを 1 つに収束する必要があるというサインです。

言語の制約により、コードの一般化が不可能/「美しく」ない

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

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

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

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


後で追加...

ループ展開

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

あなたは一つの部分で将来の変更が意図せずに他の部分を変更しないことを確認するためにそうすることをお勧めします。たとえば考える

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()変更の副作用の原因となります、プログラマが認識しないかもしれない副作用。 そう、「コードの重複」のこの種のプログラムで将来の変更のこの種に対する安全機構として機能することができます。

ドメインごとの共通点は何もありません。

時には、メソッドとクラスが、実装ワイズたくさん似て見えます。これらのケースでは、それは未来が同じにならない何かにこれらの実装を分岐するわけではないことをより頻繁に変更するように、コードの重複を行うことが多い方が良いでしょう。

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

例(擬似コード)

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)のうちリファクタリング可能性があります - しかし、言語に依存することは、(特に継承して)難しいかもしれません。体のほとんどは、各プロパティのと同じであるので、それは、コードの重複であるが、共通部分をリファクタリングするのは難しいことができます。

要するに - リファクタリング方法が要因より複雑であれば、私はそれが「悪」であるが、コードの重複といいと思う(と悪のままになります)。

私はこれはから生じる見ることができる唯一の「有効」なものは、コードのこれらの行が異なっていたとき、その後の編集を通じて同じものに収束しています。私はこれが私の前に起こる持っていたが、どれもあまりにも頻繁にしました。

これは、新しい機能にコードのこの共通セグメントを考慮する時が来たとき、もちろん、です。

それは私が重複したコードを正当化する任意の合理的な方法を考えることはできません、と述べました。それは悪い理由を見ています。

1位の変化が複数の場所の変更を必要とするので、

これは悪いです。これはバグの可能性と、時間を増加させています。それを加味することで、あなたは、単一の作業場所にコードを維持します。結局のところ、あなたがプログラムを書くとき、あなたがそれを2回書いていない、なぜ機能は何が違うのだろうか?

この種のコードの重複(多数の行が何度も重複)の場合は、次のように言います。

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

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

それに対して私が見た中での最良の解決策:開発者に採用されたら、古いアプリケーションのメンテナンスから始めてもらいます。そうすることで、この種のことはよくないことを開発者に教えることになります...そして彼らはそうするだろう 理解する なぜ、それが最も重要な部分です。

コードを複数の関数に分割したり、コードを正しい方法で再利用したり、これらすべてには経験が必要になることがよくあります。あるいは、適切な人材を雇用していません ;-)

私は、いくつかの特別なケースでは、このように重複したコードを使用するコード内で発生した低レベルJMP文を避けるためでしょう、あなたのプログラミング、グラフィックスを行うために使用される前に長い時間が(それがラベルにジャンプを回避することにより、パフォーマンスを改善します/関数)。これは、「インライン化」擬似を最適化し、実行する方法でした。

ただし、この場合には、私があわや、彼らはそれをやっていた理由はないと思う。

別のタスクが2つの場所で同じ動作を繰り返す、偶然に類似している場合は、必ずしも重複ではありません。一箇所の変更で行動した場合、それは彼らが他の場所で変更する必要がありそうなのですか?そして、これはあなたが避けるか、または離れてリファクタリングする必要があり、重複です。

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

私はコードの重複のために多くの良い理由を知っているのではなく、より最初のリファクタリングに足でジャンプしていない、それだけではなく、そのあなたに大きなコードベースを変更することよりも、あなたが実際に変更コードのこれらのビットをリファクタリングするために、おそらく良いでしょうまだ完全には理解していない。

は、どちらかが未経験だったおよび/またはハード時間に押された原作者のようですね。最も経験豊富なプログラマーの束一緒に後で少なく、メンテナンスがありますので、再利用されているもの - 。怠惰のフォーム

コピーしたコードは、ビットリファクタリングが必要になる場合があり、いくつかのグローバルデータにアクセスする場合、任意の副作用がある場合は、

あなたがチェックすべき唯一のものはあります。

編集のバックコンパイラが原因コンパイラでは、いくつかのバグにそれが起こる可能性さえcrappier安っぽいとオプティマイザた日の1月には、バグを回避するために、このようなトリックを行う必要がありました。そのようなたぶん、その何か?古い何歳?

大規模プロジェクト(GBほどの大規模なコードベースを持つもの)には、の失うの既存のAPIにはかなり可能です。これは、典型的に不十分なドキュメント、または元のコードの位置を特定するためのプログラマのできないことによるものです。従ってコードを複製する。

は、怠惰、または貧弱な審査実務に沸きます。

EDITます:

1つの追加の可能性は、道に沿って除去されたこれらの方法で追加のコードがあったことです。

ファイルにリビジョン履歴を見たことがありますか?

すべての答えは右に見えますが、私は別の可能性があると思います。 たぶん、あなたは私の「インライン化コード」連想させると言う事などのパフォーマンスの考慮事項があります。それはそれらを呼び出すためにインライン関数には常に高速です。 たぶん、あなたは見てのコードは、最初に前処理された?

それはソースコードジェネレータによって生成されたときに、

Iは重複コードで問題はありません。

私たちはピクセル操作コードしたコードを複製するために私たちを強制ことが判明何か。我々は非常に大きな画像を扱うと、関数呼び出しのオーバーヘッドは、当社のピクセル単位の時間の30%のオーダーまで食べていました。

ピクセル操作コードの複製コードの複雑さを犠牲にして私たちの20%より速い画像トラバーサルを与えました。

これは明らかに非常にまれなケースであり、そして最終的には(300ライン機能は現在1200本)で有意に我々のソースを肥大化します。

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

リファクタリング容赦なくのデザインパターンを参照してください。

オリジナルのプログラマがいずれかの期限や怠惰を満たすために急いでいました。コードをリファクタリングし、改善すること自由に感じています。

scroll top