web-dev-qa-db-ja.com

複雑なコードを追加して重複コードを削除する

いくつかのクラスがあり、それらはすべてジェネリックベースクラスから継承しています。基本クラスには、Tタイプのいくつかのオブジェクトのコレクションが含まれています。

各子クラスはオブジェクトのコレクションから補間された値を計算できる必要がありますが、子クラスは異なるタイプを使用するため、計算はクラスごとにわずかに異なります。

これまでのところ、コードをクラス間でコピーして貼り付け、それぞれに小さな変更を加えました。しかし今、私は重複したコードを削除し、それを私の基本クラスの1つの一般的な補間メソッドで置き換えようとしています。しかし、それは非常に困難であることが証明されており、私が考えたすべてのソリューションは非常に複雑に見えます。

DRYの原則はこの種の状況ではあまり適用されませんが、それは冒とくのように聞こえます。コードの重複を取り除こうとすると、どれほど複雑になりすぎますか?

編集:

私が思いつくことができる最良の解決策は次のようなものです:

基本クラス:

protected T GetInterpolated(int frame)
{
    var index = SortedFrames.BinarySearch(frame);
    if (index >= 0)
        return Data[index];

    index = ~index;

    if (index == 0)
        return Data[index];
    if (index >= Data.Count)
        return Data[Data.Count - 1];

    return GetInterpolatedItem(frame, Data[index - 1], Data[index]);
}

protected abstract T GetInterpolatedItem(int frame, T lower, T upper);

子クラスA:

public IGpsCoordinate GetInterpolatedCoord(int frame)
{
    ReadData();
    return GetInterpolated(frame);
}

protected override IGpsCoordinate GetInterpolatedItem(int frame, IGpsCoordinate lower, IGpsCoordinate upper)
{
    double ratio = GetInterpolationRatio(frame, lower.Frame, upper.Frame);

    var x = GetInterpolatedValue(lower.X, upper.X, ratio);
    var y = GetInterpolatedValue(lower.Y, upper.Y, ratio);
    var z = GetInterpolatedValue(lower.Z, upper.Z, ratio);

    return new GpsCoordinate(frame, x, y, z);
}

子クラスB:

public double GetMph(int frame)
{
    ReadData();
    return GetInterpolated(frame).MilesPerHour;
}

protected override ISpeed GetInterpolatedItem(int frame, ISpeed lower, ISpeed upper)
{
    var ratio = GetInterpolationRatio(frame, lower.Frame, upper.Frame);
    var mph = GetInterpolatedValue(lower.MilesPerHour, upper.MilesPerHour, ratio);
    return new Speed(frame, mph);
}
24
Phil

ある意味で、あなたは最後の段落のその発言であなた自身の質問に答えました:

DRYの原則はこのような状況ではあまり当てはまりませんが、それは冒涜のように聞こえます。

問題を解決するために実際的ではない実践が見つかった場合は、その実践を信仰的に使用しないでください(Word冒とくはこれに対する警告の一種です)。ほとんどのプラクティスにはwhenswhysがあり、すべての可能なケースの99%をカバーしていても、1%は別のアプローチが必要になる場合があります。

具体的には、 [〜#〜] dry [〜#〜] に関して、いくつかのを複製する方が実際にはより良いこともありますが、単純なコードを1つの巨大な怪物よりも、それを見ると気分が悪くなります。

そうは言っても、これらのEdgeケースの存在は、ずさんなコピーと貼り付けのコーディングや、再利用可能なモジュールの完全な欠如の言い訳として使用すべきではありません。簡単に言えば、ある言語のある問題に対する一般的なコードと読みやすいコードの両方を書く方法がわからない場合、冗長性を持たせることはおそらく悪くないでしょう。コードを保守する必要がある人を考えてください。冗長性や難読化を使用すると、より簡単に生活できるでしょうか?

特定の例についてのより具体的なアドバイス。これらの計算は似ていますがまだ少し異なっていました。計算式を小さなサブ式に分解してから、わずかに異なるすべての計算でこれらのヘルパー関数を呼び出してサブ計算を実行することもできます。すべての計算が過度に一般化されたコードに依存している状況を回避し、ある程度の再利用が可能です。

30
Goran Jovic

子クラスはさまざまなタイプを使用し、計算はクラスごとに少し異なります。

まず第一に、クラス間でどの部分が変更され、どの部分が変更されていないかを最初に明確に特定します。それを特定したら、問題は解決されます。

リファクタリングを開始する前の最初の演習としてそれを行ってください。その後、他のすべてが自動的に配置されます。

17
java_mouse

2行以上のコードのほとんどすべての繰り返しは、何らかの方法で除外でき、ほとんどの場合は除外する必要があると思います。

ただし、このリファクタリングは、一部の言語では他の言語よりも簡単です。 LISP、Ruby、Python、Groovy、Javascript、Luaなどの言語では非常に簡単です。通常、テンプレートを使用するC++ではそれほど難しくありません。 Cでより苦痛なのは、唯一のツールがプリプロセッサマクロである可能性がある場合です。 Javaではしばしば苦痛であり、時には単純に不可能です。複数の組み込み数値型を処理するための汎用コードを記述しようとしています。

より表現力豊かな言語では、問題はありません。数行のコード以上のものをリファクタリングしてください。表現力の低い言語では、リファクタリングの痛みと繰り返されるコードの長さと安定性のバランスをとる必要があります。繰り返されるコードが長い場合、または頻繁に変更される可能性がある場合、結果のコードが多少読みにくくても、リファクタリングする傾向があります。

コードが短く、安定していて、リファクタリングが醜い場合にのみ、繰り返しコードを受け入れます。基本的に、Javaを作成しているのでない限り、ほとんどすべての重複を除外します。

コードを投稿していないか、使用している言語を示していないため、ケースに特定の推奨を与えることは不可能です。

8
kevin cline

基本クラスがアルゴリズムを実行する必要があると言いますが、アルゴリズムはサブクラスごとに異なるため、これは The Template Pattern の完全な候補のように聞こえます。

これにより、基本クラスがアルゴリズムを実行し、各サブクラスのバリエーションになると、実装するのはサブクラスの責任である抽象メソッドに委ねられます。たとえば、ASP.NETページがコードに依存してPage_Loadを実装する方法を考えてください。

5
Paul T Davies

各クラスの「1つの一般的な補間メソッド」のように聞こえるのは、やりすぎであり、より小さなメソッドにする必要があります。

計算がどれほど複雑であるかに応じて、計算の各「部分」をこのような仮想メソッドにすることができなかった理由

Public Class Fraction
{
     public virtual Decimal GetNumerator(params?)
     public virtual Decimal GetDenominator(params?)
     //Some concrete method to actually compute GetNumerator / GetDenominator
}

そして、計算のロジックでその「わずかな変動」を行う必要がある場合は、個々のピースをオーバーライドします。

(これは、小さなメソッドをオーバーライドしながら多くの機能を追加する方法の非常に小さくて役に立たない例です)

4
brian

私の意見では、ある意味では正しいDRYは長すぎる可能性があります。類似した2つのコードが非常に異なる方向に進化する可能性が高い場合は、最初は自分を繰り返さないようにします。

しかし、あなたはまた、そのような冒とく的な考えに警戒するのも当然です。あなたはそれを放っておくことを決定する前に、あなたのオプションをじっくりと考えてみてください。

たとえば、その繰り返しコードを基本クラスではなくユーティリティクラス/メソッドに配置した方がよいでしょうか? 継承よりも構成を優先する を参照してください。

3
pdr

DRYは従うべきガイドラインであり、破れないルールではありません。ある時点で、使用しているすべてのクラスでXレベルの継承とYテンプレートを使用する価値がないと判断する必要があります。このコードには繰り返しがないと言うだけです。質問する良い質問がいくつかあります。これらの類似のメソッドを抽出して1つとして実装するのに時間がかかり、変更の必要性が生じた場合、または変更が発生する可能性がある場合、それらすべてを検索することになります。最初にこれらのメソッドを抽出する私の作業を元に戻しますか?私は、追加の抽象化が、このコードがどこで、何をするのが難しいのかを理解し始めているところにいますか?

これらの質問のいずれかに「はい」と答えることができる場合、重複する可能性のあるコードを残す可能性が高いです

2
Ryathal

「なぜリファクタリングしなければならないのか」と自問自答する必要がありますか? 「類似しているが異なる」コードがある場合、1つのアルゴリズムに変更を加えると、その変更を他の場所にも反映させる必要があります。これは通常、災害のレシピであり、必ず他の誰かが1つの場所を見逃して、別のバグを招くでしょう。

この場合、アルゴリズムを1つの巨大なものにリファクタリングすると、アルゴリズムが複雑になりすぎて、将来のメンテナンスが難しくなります。したがって、一般的なものを賢明に除外できない場合は、次のように簡単です。

// this code is similar to class x function b

コメントで十分です。問題が解決しました。

0
Rocklan

機能が重複する1つの大きなメソッドと2つの小さなメソッドのどちらが良いかを判断する際、最初の50,000ドルの質問は、動作の重複部分が変更される可能性があるかどうか、および変更が小さなメソッドに等しく適用されるかどうかです。最初の質問への回答が「はい」で、2番目の質問への回答が「いいえ」の場合、その後、メソッドは個別のままにしておく必要があります。両方の質問に対する答えが「はい」の場合は、コードのすべてのバージョンが同期していることを確認するために何かを行う必要があります。多くの場合、これを行う最も簡単な方法は、バージョンを1つだけにすることです。

MicrosoftがDRYの原則に違反しているように見える場所がいくつかあります。たとえば、Microsoftは、メソッドが失敗を例外をスローするかどうかを示すパラメーターを受け入れるように明示的に推奨していません。 「失敗によって例外がスローされる」パラメータはメソッドの「一般的な使用法」APIでは見苦しいため、このようなパラメータは、Try/Doメソッドを他のTry/Doメソッドで構成する必要がある場合に非常に役立ちます。外部メソッドが想定されている場合失敗が発生したときに例外をスローする場合、失敗した内部メソッドの呼び出しは、外部メソッドが伝播できる例外をスローする必要があります。外部メソッドが例外をスローすることを想定していない場合、内部メソッドも例外ではありません。パラメータを使用してtry/doを区別する場合、外部メソッドはそれを内部メソッドに渡すことができます。それ以外の場合、外部メソッドが「try」として動作すると想定されている場合に、「try」メソッドを呼び出す必要があります。動作するはずのときに「do」メソッド「する」として。

0
supercat