一般的に、私はこれに多く遭遇します。私の同僚の中には、たとえコードの重複があったとしても、非常にシンプルで読みやすいクラスを好む人もいますが、私はコードの重複を避けるために全力を尽くしています。ベストプラクティスは何ですか? 私はJavaだけで働いています。
15 に答える
私は常にコードの重複のないソリューションを好みます。より複雑なアーキテクチャを最初は理解するのが難しい場合でも、メンテナンスのメリットは学習曲線を上回ります。
どちらも良い目標ですが、可読性は、保守可能なコードベースを持つための絶対的な最初の要件であると感じています。コードの重複を完全に排除するよりも、シンプルで読みやすく、保守しやすいものを常に好みます。
ベスト プラクティス:コードが短い場合は、2 回複製しますが、それ以上複製しないでください。
そのため、非常によく似たコード スニペットが 3 つの異なる場所にコピー/貼り付けされている場合は、リファクタリングを検討してください。
リファクタリングは、コードをより複雑にすることを自動的に意味するわけではないことに注意してください。次の点を考慮してください。
class IntStack
{
public int value;
public IntStack next;
}
class StringStack
{
public String value;
public StringStack next;
}
class PersonStack
{
public Person value;
pubilc PersonStack Next;
}
新しいデータ型のスタックが必要になるたびに、新しいクラスを作成する必要があります。コードの複製は正常に機能しますが、新しいメソッド、おそらく新しいスタックを返す「プッシュ」メソッドを追加したいとしましょう。さて、あなたはそれを無数の異なる場所に追加することを余儀なくされています. または、汎用オブジェクト スタックを使用することもできますが、そうするとタイプ セーフが失われます。ジェネリックはアーキテクチャを簡素化します:
class Stack<T>
{
public T value;
public Stack<T> next;
}
涼しい!
さて、この例はどうですか:
class Logger
{
int logtype;
public Logger(int logtype) { ... }
public void Log(string text)
{
if (logtype == FILE) { ... }
elseif (logtype == DATABASE) { ... }
elseif (logtype == CONSOLE) { ... }
}
public void Clear()
{
if (logtype == FILE) { ... }
elseif (logtype == DATABASE) { ... }
elseif (logtype == CONSOLE) { ... }
}
public void Truncate(int messagesToTruncate)
{
if (logtype == FILE) { ... }
elseif (logtype == DATABASE) { ... }
elseif (logtype == CONSOLE) { ... }
}
}
メソッドを追加するたびに、使用しているロガーの種類を確認する必要があります。痛みがあり、バグが発生しやすい。通常は、インターフェース (おそらく Log、Clear、および Truncate メソッドを使用) を取り出して、3 つのクラス (FileLogger、DatabaseLogger、ConsoleLogger) を作成します。
より多くのクラス = より多くのアーキテクチャ。これは、長期的に維持するのが簡単ですか、それとも難しいですか? この例では、コードの保守が容易になったと思いますが、YMMV.
複雑なメタプログラミング (Java ではそれほど問題ではない) やリフレクションの過度の使用によってコードの重複を防ぐ極端なケースがあり、そのような少数のケースでは、重複を許可することをお勧めします。これはまれです。あなたではないかなり熟練した開発者がコードを理解できる限り、重複を排除したいと思います。
私は、チームに 1 人か 2 人の熟練した開発者と多数の初心者が含まれている状況に出くわしました。初心者は、一目で理解できないコーディング手法の使用を防ごうとします。これには抵抗しなければなりません。
コードの重複を避ける主な理由は保守性です。コードのセグメントが複数の場所にある場合は、更新するときにどこでも変更することを忘れないでください。1 つのインスタンスを変更し忘れると、すぐには気付かない大きな問題が発生する可能性があります。
「ジェネリック」があなたの質問にどのように関連しているかわかりません。CollectionOfFooとCollectionOfBarを表すために別々のクラスを用意するのは些細なことですが、明らかに間違っているので、それはあなたが求めているものではありません。
おそらく、各視点の例を提供する必要がありますが、それでも主観的であるために閉鎖される可能性があります。
コードの重複を避けることは常に良いことです。防ぐ必要があるのは、時期尚早の一般化です。
似たようなコードをいくつか入手したら、すぐに急いで入る必要はありません。良いモデルを開発するまで、腰を下ろしても大丈夫です。
特に、物事がもう少し複雑でオープンエンドの場合は、一般化する必要のあるもののいくつかの例を挙げても問題ありません。1つまたは2つだけではなく、3〜5以上の例がある場合は、一般化する方が簡単な場合があります。
これは裁判の呼びかけです。ほとんどのプログラマーはコードを複製しすぎており、それが熱心な開発者の間で、複製を根絶することは絶対に良いことであるという態度につながっていると思いますが、そうではありません。コードを読みやすくすることは最優先事項であり、通常、コードの重複をなくすことは読みやすさにとって良いことですが、常にそうであるとは限りません。
また、商業的に価値のあるコードを、なじみのない言語機能を学習する目的で使用する場所として使用することもありません。その目的のために個別の学習プロジェクトを作成します。ジェネリックやその他の機能に夢中になりすぎて発生したバグを修正するために、勤務時間外に呼び出されることは望ましくありません。
いいえ、どちらの状況も受け入れられません。ジェネリックを使用して記述しますが、必要なだけ複雑にします。
コードを複製しないでください。バグを二重に修正し、機能強化を二重に追加し、コメントを二重に書き、テストを二重に書かなければなりません。作成するコードのすべての行は、そのコードベースで作業している限り、背負わなければならない小さな負担です。あなたの負担を最小限に抑えます。
さまざまな要因によって異なります。
- どのくらいのコードが複製されていますか? 正当な理由があれば、同じ 5 行が 2 回表示されても問題ありません。オーバー アーキテクト コードは避けてください。長期的には保守性が低下する可能性があります。これは、次にコードに取り組む人が、アーキテクチャの微妙な部分をすべて理解できず、形が崩れてしまう可能性があるためです。
- 同じコードのコピーはいくつありますか? 2 は悪くありませんが、10 (10 進数) はあまり良くありません。
- コードが重複しているのはなぜですか?すべての要件が構築されると、重複ではなく、多少似ていることが判明した多くの「重複」に遭遇しました。
だから私の答えは多分...
ジェネリクスが読みにくい主な理由は、経験の浅いプログラマーにとって馴染みがないためです。これは、明快さを際立たせるために、ネーミングとドキュメントの選択に細心の注意を払う必要があることを意味します。
このようなコア クラスに適切な名前を付けることが非常に重要です。設計者は、名前を選択する前に、これについて同僚と徹底的に話し合うことをお勧めします。
通常、コードを複製する唯一の理由は、言語の弱いテンプレート/マクロ/ジェネリック システムを克服するため、またはさまざまな型を処理するさまざまな低レベル関数名が存在する最適化のためです。
あなたが作業していないと指摘したC++では、テンプレートシステムにより、オーバーヘッドのない型と関数の生成が可能になり、特定の型パラメーターに対して生成されたコードを「カスタマイズ」するための特殊化が可能になります。Java にはそのようなものは何もなく、C++ 内で利用可能な一般的および特定の選択回避の両方を排除しています。
Common Lisp では、マクロとコンパイラ マクロを使用して、C++ テンプレートに似たコードを生成できます。この場合、基本的なケースを特定の型に合わせてカスタマイズし、さまざまなコード展開を生成できます。
Java でジェネリック コードに反対するのは、数値型を使用する内部ループだけです。汎用コードの数値型のボックス化およびボックス化解除のコストを支払うことは、そのような場合には容認できません。ただし、コンパイラまたは HotSpot がボックス化を忘れるほど巧妙であるとプロファイラーが確信できる場合を除きます。
読みやすさに関しては、コードをモデルにして、同僚が立ち向かわなければならない課題にします。それを読むのに苦労している人々を教育することを申し出てください。欠陥がないか確認するように彼らに勧めてください。それらが見つかった場合は、唯一無二のバージョンを修正するだけでよいという利点を示すことができます。
リレーショナル データベースでの正規化と比較してください。多くの場所ではなく、ある種の機能やデータが存在する 1 つの場所が必要になるでしょう。保守性とコードについて推論する能力の点で大きな違いがあります。
答えを出す前に、サンプルコードを見て、質問が実際に何であるかを確認したいと思います。
それを待っている間、コードが最も健全な OO 原則 (各クラスが 1 つのことのみを実行し、1 つのことのみを実行するなど) で構築されている場合、コードの重複は発生しないはずです。確かに、抽象化などに夢中になり、役に立たないクラスの山が大量に作成される可能性がありますが、それはここでは問題ではないと思います。
素晴らしい質問です。一般的な答えはあなたの状況に合わないかもしれません。あなたの選択を決定する多くの要因があります
- 現在のコード品質
- 製品/プロジェクトの段階、初期/成長/成熟
- 開発者の経験とスキル
- プロジェクト/製品の市場投入までの時間
これらは、選択を決定する重要な要素の一部です。私の経験では、重複したビジネス ロジック (中間層) コードは適切な方法ではありませんが、プレゼンテーション レイヤーには重複したコードが含まれる可能性があることを認識しています。
これを書いているとき、「 Dos Slow Growth Equal Death 」という記事を思い出しました。高品質で重複のないコードを書くには時間がかかるかもしれませんが、それがビジネスのボトルネックになることはありません。