web-dev-qa-db-ja.com

知らないうちにコードが重複しないようにするにはどうすればよいですか?

かなり大きなコードベースで作業しています。何百ものクラス、多数の異なるファイル、多くの機能が、新しいコピーをプルダウンするのに15分以上かかります。

このような大きなコードベースの大きな問題は、かなりの数のユーティリティメソッドがあり、同じことを行うか、可能な場合にこれらのユーティリティメソッドを使用しないコードがあることです。また、ユーティリティメソッドは1つのクラスにすべて含まれているわけではありません(巨大な混乱状態になるためです)。

私はコードベースは初心者ですが、何年もコードベースに取り組んでいるチームリーダーが同じ問題を抱えているようです。それは多くのコードと作業の重複につながり、そのため、何かが壊れると、通常は基本的に同じコードの4つのコピーで壊れます

このパターンをどのように抑制できますか?ほとんどの大規模プロジェクトと同様に、すべてのコードがドキュメント化されているわけではありませんが(一部はそうです)、すべてのコードがドキュメント化されているわけではありません。しかし、基本的には、この点で品質の改善に取り組み、将来コードの重複が少なくなり、ユーティリティ関数などが見つけやすくなれば、それは本当に素晴らしいことです。

また、ユーティリティ関数は通常、一部の静的ヘルパークラス、単一オブジェクトで機能する一部の非静的ヘルパークラス、または主に「支援」するクラスの静的メソッドです。

ユーティリティ関数を拡張メソッドとして追加する実験を1回行いました(クラスの内部は必要なく、非常に特定のシナリオでのみ必要でした)。これは、プライマリクラスなどが雑然とするのを防ぐ効果がありましたが、あなたがすでにそれを知っていなければ、それは実際にはもう発見できません。

33
Earlz

簡単な答えは、コードの重複を実際に防ぐことはできないということです。ただし、次の2つのステップに分かれる困難な継続的で反復的な増分プロセスによって「修正」することができます。

ステップ1。レガシーコードのテストの作成を開始します(テストフレームワークを使用することが望ましい)。

ステップ2。テストから学んだことを使用して、複製されたコードをリライト/リファクタリングします

静的分析ツールを使用して、重複したコードを検出できます。C#には、これを行うためのツールがたくさんあります:

このようなツールは、同様のことを行うコード内のポイントを見つけるのに役立ちます。彼らが本当にそうであるかどうかを決定するためのテストを書き続ける。同じテストを使用して、重複するコードを使いやすくします。この「リファクタリング」は複数の方法で実行でき、このリストを使用して正しいリストを決定できます。

さらに、Michael C. Feathersによるこのトピックに関する本全体も掲載されています レガシーコードを効果的に使用する 。それはあなたがコードをより良いものに変えるために取ることができる異なる戦略を深く掘り下げます。彼は「レガシーコード変更アルゴリズム」を持っており、上記の2つのステップのプロセスからそれほど離れていません。

  1. 変化点を特定する
  2. テストポイントを見つける
  3. 依存関係を壊す
  4. テストを書く
  5. 変更を加えてリファクタリングする

ブラウンフィールド開発、つまり変更が必要なレガシーコードを扱っている場合は、この本を読むのに適しています。

この場合

OPの場合、テスト不可能なコードは、いくつかの形式をとる「ユーティリティメソッドとトリック」のハニーポットが原因であると想像できます。

  • 静的メソッド
  • 静的リソースの使用
  • シングルトンクラス
  • マジック値

これらには何の問題もないことに注意してください。一方、通常、それらを維持および変更することは困難です。 .NETの拡張メソッドは静的メソッドですが、テストも比較的簡単です。

ただし、リファクタリングを行う前に、それについてチームと話し合ってください。先に進む前に、それらを同じページに置く必要があります。これは、何かをリファクタリングしていると、可能性が高くなり、マージの競合が発生するためです。したがって、何かをやり直す前に、それを調査し、完了するまでしばらくの間、注意してこれらのコードポイントで作業するようにチームに指示してください。

OPはコードに対してnewであるため、何かを行う前に、他に行うべきことがいくつかあります。

  • 時間をかけてコードベースから学習します。つまり、「すべて」を破り、「すべて」をテストし、元に戻します。
  • コミットする前に、チームの誰かにコードを確認するよう依頼してください。 ;-)

幸運を!

30
Spoike

別の角度から問題を確認することもできます。問題はコードの重複であると考えるのではなく、問題の原因がコードの再利用に関するポリシーの欠如にあるかどうかを検討します。

私は最近、本 再利用可能なコンポーネントを使用したソフトウェアエンジニアリング を読みましたが、実際には、組織レベルでコードの再利用性を促進する方法に関する一連の非常に興味深いアイデアがあります。

この本の作者であるJohannes Sametingerは、コードの再利用に対する一連の障壁について説明しています。例えば:

概念と技術

  • 再利用可能なソフトウェアを見つけるのが難しい:ソフトウェアは、見つけられない限り再利用できません。リポジトリにコンポーネントに関する十分な情報がない場合、またはコンポーネントの分類が不十分な場合、再利用は起こりません。
  • 見つかったソフトウェアの非再利用性:既存のソフトウェアに簡単にアクセスしても、ソフトウェアの再利用が増えるとは限りません。意図せずに、ソフトウェアが他の人が再利用できるように記述されていることはほとんどありません。他の誰かのソフトウェアを変更および適応することは、必要な機能を最初からプログラミングするよりもさらに高価になる可能性があります。
  • 再利用に適さないレガシーコンポーネント:再利用のために設計および開発されていない限り、コンポーネントの再利用は困難または不可能です。さまざまなレガシーソフトウェアシステムから既存のコンポーネントを収集し、それらを新しい開発に再利用するだけでは、体系的な再利用には不十分です。リエンジニアリングは、再利用可能なコンポーネントの抽出に役立ちますが、かなりの労力がかかる場合があります。
  • オブジェクト指向テクノロジー:オブジェクト指向テクノロジーはソフトウェアの再利用にプラスの影響を与えると広く信じられています。残念ながら、誤って、多くの人は再利用がこのテクノロジーに依存している、またはソフトウェア指向の再利用にはオブジェクト指向テクノロジーの採用で十分であると考えています。
  • Modification:コンポーネントは必ずしも私たちが望むようになっているとは限りません。変更が必要な場合は、コンポーネントへの影響とその以前の検証結果を特定できるはずです。
  • ガベージの再利用:再利用可能なコンポーネントを特定の品質レベルに認定すると、起こり得る欠陥を最小限に抑えることができます。不十分な品質管理は、再利用の主要な障害の1つです。必要な関数がコンポーネントによって提供される関数と一致するかどうかを判断するいくつかの手段が必要です。

その他の基本的な技術的な問題には、

  • 再利用可能なコンポーネントの構成について合意する。
  • コンポーネントの機能と使用方法を理解する。
  • 再利用可能なコンポーネントをデザインの残りの部分にインターフェースする方法を理解する。
  • 再利用可能なコンポーネントを設計して、制御された方法で容易に適応および変更できるようにします。
  • プログラマーが必要なものを見つけて使用できるようにリポジトリーを編成する。

著者によれば、組織の成熟度に応じて、さまざまなレベルの再利用性が発生します。

  • アプリケーショングループ間のアドホックな再利用:再利用への明示的なコミットメントがない場合、再利用はせいぜい非公式で無計画な方法で発生する可能性があります。再利用のほとんどは、プロジェクト内で行われます。これは、コードの清掃にもつながり、コードの重複につながります。
  • アプリケーショングループ間でのリポジトリベースの再利用:コンポーネントリポジトリが使用され、さまざまなアプリケーショングループからアクセスできる場合、状況はわずかに改善されます。ただし、コンポーネントをリポジトリーに配置するための明示的なメカニズムは存在せず、リポジトリー内のコンポーネントの品質に責任を負う人はいません。これは多くの問題を引き起こし、ソフトウェアの再利用を妨げることがあります。
  • コンポーネントグループでの集中的な再利用:このシナリオでは、コンポーネントグループがリポジトリを明示的に担当します。グループは、リポジトリに格納するコンポーネントを決定し、これらのコンポーネントの品質と必要なドキュメントの可用性を確保し、特定の再利用シナリオで適切なコンポーネントを取得するのに役立ちます。アプリケーショングループは、各アプリケーショングループの一種の下請け業者として機能するコンポーネントグループから分離されています。コンポーネントグループの目的は、冗長性を最小限に抑えることです。一部のモデルでは、このグループのメンバーは特定のプロジェクトで作業することもできます。プロジェクトの立ち上げ時に、彼らの知識は再利用を促進するために貴重であり、特定のプロジェクトへの関与のおかげで、彼らはリポジトリーに含める可能性のある候補を特定できます。
  • ドメインベースの再利用:コンポーネントグループの特殊化は、ドメインベースの再利用に相当します。各ドメイングループは、そのドメイン内のコンポーネントを担当します。ネットワークコンポーネント、ユーザーインターフェイスコンポーネント、データベースコンポーネント。

したがって、他の回答で提示されたすべての提案に加えて、再利用プログラムの設計、管理の関与、ドメイン分析による再利用可能なコンポーネントの識別を担当するコンポーネントグループの形成、および他の開発者が簡単にできる再利用可能なコンポーネントのリポジトリの定義を行うことができます。問題を解決するためのクッキングされたソリューションをクエリして探します。

2
edalorzo

2つの可能な解決策があります。

防止-可能な限り適切なドキュメントを用意してください。すべての機能を適切に文書化し、文書全体を検索しやすくします。また、コードを書くときは、どこにコードを置くべきかを明確にし、どこを見ればよいかを明確にします。 「ユーティリティ」コードの量を制限することは、これの重要なポイントの1つです。 「ユーティリティクラスを作ろう」と聞くたびに、明らかに問題なので、髪が上がり、血液が凍ってしまいます。いくつかの機能がすでに存在するときはいつでも、人々にコードベースを知ってもらうように迅速かつ簡単な方法を常に提供します。

ソリューション-防止に失敗した場合、問題のあるコードを迅速かつ効率的に解決できるはずです。開発プロセスでは、重複コードをすばやく修正できる必要があります。ユニットテストは、コードを壊すことを恐れずにコードを効率的に変更できるため、これに最適です。したがって、2つの類似したコードを見つけた場合、それらを関数またはクラスに抽象化することは、少しのリファクタリングで簡単になるはずです。

個人的には防げないと思います。試してみると、既存の機能を見つけるのが難しくなります。

1
Euphoric

この種の問題が一般的な解決策を持っているとは思いません。開発者が既存のコードを検索する十分な意欲がある場合、重複したコードは作成されません。また、開発者は必要に応じてその場で問題を修正できます。

言語がC/C++の場合、リンクの柔軟性のため、複製はより簡単になります(事前の情報なしでextern関数を呼び出すことができます)。 Javaまたは.NETの場合、ヘルパークラスやユーティリティコンポーネントを考案する必要があるかもしれません。

私は通常、重複した部分から大きなエラーが発生した場合にのみ、既存のコードの重複削除を開始します。

0
9dan

私は実際に最も人気のない意見をここに反映し、Gangnusと共存させて、コードの複製が常に有害であるとは限らず、時にはそれほど悪ではないかもしれないことを提案します。

たとえば、次のオプションを使用できる場合:

A)安定した(変更されていない)小さな画像ライブラリ十分にテストされた。これは、内積やlerpsやクランプなどのベクトル演算の数十行の数学的コードを複製しますが、完全に何からも切り離されています。そうでなければ、ほんの一瞬でビルドします。

B)上記の数十行のコードを回避するために叙事詩の数学ライブラリに依存する不安定な(急速に変化する)画像ライブラリ。数学ライブラリは不安定であり、常に新しい更新と変更を受信するため、画像ライブラリも完全に変更されていない場合も同様に再構築されます。全体をクリーンビルドするには15分かかります。

...そして、明らかに、ほとんどの人にとって、Aは、実際には、マイナーなコードの重複のために、望ましいことです。私が行う必要がある重要な強調点は、十分にテストされたの部分です。明らかに、そもそも機能しないコードを複製することより悪いことは何もありません。その時点で、それはバグを複製しています。

しかし、考慮すべき結合と安定性もあり、ここでの適度な重複は、パッケージの安定性(不変の性質)も向上させる分離メカニズムとして機能します。

したがって、私の提案は実際には、テストにもっと焦点を当て、非常に安定していて(不変で、将来変更する理由をいくつか見つけるなど)、信頼できる外部ソースへの依存関係がある場合は、それを信頼できるものにすることです。非常に安定しており、コードベース内のすべての形式の複製を打ち抜こうとしています。大規模なチーム環境では、後者は非現実的な目標になる傾向があり、コードベースにある不安定なコードの結合と量を増やす可能性があることは言うまでもありません。

0
user204677

これは、多くのプログラマーによって処理された大きなプロジェクトの典型的な問題であり、時々多くの仲間からのプレッシャーの下で貢献しています。クラスのコピーを作成し、それをその特定のクラスに適合させることは非常に魅力的です。ただし、元のクラスで問題が見つかった場合は、その忘れられている子孫でも解決する必要があります。

これに対する解決策があり、Java 6.で導入されたGenericsと呼ばれます。これは、テンプレートと呼ばれるC++に相当します。正確なクラスがGenerics内でまだ知られていないコードクラス。Java Genericsを確認してください。大量のドキュメントが見つかります。

良い方法は、特定のバグのために修正する必要がある最初のコードを書き直すことにより、多くの場所でコピー/貼り付けされているように見えるコードを書き直すことです。 Genericsを使用するように書き換え、非常に厳密なテストコードを記述します。

Genericクラスのすべてのメソッドが呼び出されていることを確認してください。コードカバレッジツールを導入することもできます。汎用コードは複数の場所で使用されるため、完全にコードカバレッジにする必要があります。

また、Genericコードと組み合わせて使用​​される最初の指定されたクラスに対して、JUnitなどを使用してテストコードを記述します。

上記のすべてのコードが機能し、完全にテストされたら、2番目(ほとんどの場合)にコピーされたバージョンの汎用コードの使用を開始します。指定されたクラスに固有のコード行がいくつかあることがわかります。これらのコード行は、Generic基本クラスを使用する派生クラスで実装する必要がある抽象保護メソッドで呼び出すことができます。

はい、それは退屈な仕事ですが、進むにつれて、同様のクラスを取り除き、それを非常に非常にクリーンで、よく書かれ、はるかに保守しやすいものに置き換えることがますます良くなります。

私は同様の状況で、ジェネリッククラスで、最終的にはほぼ同じであるが、一定期間にわたってさまざまなプログラマーによってコピーおよび貼り付けされた6または7のほぼ同じクラスを置き換えました。

そして、はい、私はコードの自動テストに非常に賛成です。最初はもっと費用がかかりますが、全体的に大幅な時間の節約になります。また、ジェネリックコードのコードカバレッジ全体を少なくとも80%および100%に達成するようにしてください。

これがお役に立てば幸いです。

0