web-dev-qa-db-ja.com

多数のスイッチケースに基づく長いメソッドのリファクタリング

Javaをバックエンド開発言語として使用しています。

1年前、Enums値に基づいてスイッチケースを使用するメソッドを記述しました。 enumメンバーを継続的に追加し、メソッドのケースを追加しているため、メソッドは非常に大きく成長しています。現在、約100の列挙型フィールドと対応する数のスイッチケースがあります。

 class AClass{

   enum option{ o1, o2, o3...on}

   Value method(Option o){

      switch(o){
        case o1:
          value = deriveValue(p1,p2,p3);
        case o2:
          value = deriveValue(p2,p3,p4);
        .
        .
        .
        case on:
          value = deriveValue(p1,p2,p3);
      }
   } 
 }

したがって、ビジネス要件が発生するたびに、列挙型と対応するスイッチケースを追加します。今後、同じロジックを追加し続けると、メソッドが長くなりすぎて管理できなくなったように見えます。

きれいにするために、同じ場合のクラスを作成することにより、switch caseをpolymorphismに置き換えることを考えましたが、やはりn個のクラスを作成することになります。

小さくてシンプルで扱いやすいソリューションを探しています。

-----------------更新---------------------

提案したように、さらに詳しく説明すると、Enum値はクライアントが値を必要とするフィールド名です。したがって、クライアントが新しいフィールドの値を必要とする場合、対応するスイッチケースを追加することにより、フィールドを列挙型で追加し、新しく追加されたフィールドの値をフェッチする方法の対応する定義を追加します。

スイッチの場合、一般的な再利用可能なメソッドがあります。 deriveValue()(与えられた例を参照してください)に、新しく追加されたフィールドの値を取得するために必要なパラメーターを渡します。

16
Free Coder

何を達成しようとしていますか?あなたが持っているコード構造を理解して維持するのに苦労していますか、それともメソッドを短くすべきだと言っている「良い習慣」の抽象的な考えに従っているだけですか?

私には、あなたが持っているソリューションが、与えられた要件に対して非常に適切であるかのように見えます。 1行のコードで実行できる数百の同様の計算が本当にある場合、単一の小さなメソッドで数百のサブクラスを作成することは、それらすべての並列ルールを1か所に単にリストするよりも必ずしも良いとは限りません。 (長いメソッドの問題は、それらが繰り返し行われ、一貫性がなくなり、混乱することです。しかし、処理する必要がある多くの並列ケースをリストする場合、長いリストは完全に問題ありません。)

31
Kilian Foth

テーブルを作成できます。最初の列はスイッチの値になり、他の列はderiveValueメソッドで渡される引数になります。例えば

table OPTIONS
OPTION -  ARGUMENT1 -  ARGUMENT2 -  ARGUMENT3
o1          p1          p2          p3
o2          p2          p3          p4
on          p1          p2          p3

次に、上記の表を使用して、オプションに基づいて適切な関数呼び出しを生成する1つの一般的な関数を記述できます。

18
DesignerAnalyst

列挙型で抽象メソッドを使用して、コードの局所性を向上させることができます(読みやすさとメンテナンスを向上させるために、ここではパフォーマンスを話しません)。

class AClass{
    enum Option{
        o1 {
            @Override
            Value deriveValue() {
                return deriveValue(p1, p2, p3);
            }
        }, ... on {
            @Override
            Value deriveValue() {
                return deriveValue(p1, p2, p3);
            }
        };
        abstract Value deriveValue();
    }

    Value method(Option o){
        return o.deriveValue();
    }
}
11
cadrian

システムにこのようなチェックを実行する単一の関数が常に1つしかなく、外部拡張のために開く必要がまったくない場合に、ポリモーフィックソリューションを投げるこのような状況がある場合は、注目に値します実際に、毎日の保守コストが実際には減少したのではなく増加したことに気付かされるかもしれません。

ここでは、オープン拡張の必要性を除いて、ポリモーフィックと条件文の実際的な違いは、より集中化された不安定なコードとより分散化された安定したコードのどちらを好むかということになるでしょう。

より分散された安定したコードは、バージョン管理の足踏みが少なくなる場合、実際には有利な場合があります。ここでより集中化された不安定なコードは、少し単純なケースごとに新しいクラスを定義することに関連するボイラープレートを回避した結果としてのみ、短くなる傾向があります。

現在、約100の列挙型フィールドと対応する数のスイッチケースがあります。したがって、ビジネス要件が発生するたびに、列挙型と対応するスイッチケースを追加します。

これを回避できれば、これは私にとって問題のように思えます。非常に多くの列挙型フィールドを伴うことができる種類の分類はありませんか?あなたが与えたような通常の例では、それほど意味のある分類は見つかりません。それが単なる類推であるといいのですが。

これらの列挙型フィールドを整理するために、意味のある分類が見つかれば、複数の関数を使用できる可能性があります。各関数(まだかなり粗い)は範囲のケースを処理でき、範囲に基づいて呼び出す関数を外部関数がチェックします。

それは、ある程度集中化されたシンプルなソリューションを維持するために何らかの分類を見つけることができれば、シンプルでそれほど煩わしくないソリューションですが、すでに処理したオプションの範囲に対して安定した関数を残します。また、処理するオプションの範囲が少ないほど、個別に変更する理由が少なくなるため、関連する関数実装の不安定性も軽減されます。

100のオプションすべてにサブタイプが含まれていない可能性があるルックアップテーブルソリューションを使用することもできます。これにより、1つの集中化された場所を変更できますが、おそらくより好ましい構文を使用できます。 p1p2、...、pnの性質によって異なります。たとえば、それらが同種の場合、配列を使用して、LUTへのインデックスをモデル化するオプション列挙フィールドを使用して、インデックスの順序に基づいてLUTを作成できる場合があります。例(疑似コード):

lut[o1] = {0, 1, 2};
lut[o2] = {1, 2, 3};
...
lut[on] = {0, 1, 2};

Value method(Option o){
    int i1 = lut[o][0];
    int i2 = lut[o][1];
    int i3 = lut[o][2];
    deriveValue(p[i1], p[i2], p[i3]);
}

これはアーキテクチャ的にはあまり機能しません(ただし、[lut]エントリを外部から追加できるようにすると、拡張用にオープンになります)が、構文を優先する場合があります。これは、ソリューションがリモートのアナロジーではなく、実際に引数の順序が異なるオプションに基づいてderiveValueを呼び出す場合に適用されます(この場合、実際には少なくとも3つ以上のパラメーターがあると思います非常に多くのオプションが必要です)。

1
user204677

大きな数を扱うために使用される一般的な戦略を適用することができます-構造。おそらく、均一に処理されるオプションの多さは、階層が少なすぎることを示しているのでしょうか?おそらく、ケースは一緒に属しているように見えるグループ(I/O、操作、計算など)にきちんと分類されますか? (多分そうではありません-無視してください;-))。

これは、階層的な列挙システムで反映される可能性があります。 1つの列挙型はグループを特徴付け、次の列挙型は特定の操作です。

グループは個別にコーディングでき、それぞれがより管理しやすくなります。それらを別のソースファイル/クラスに置くと、無関係な場所での編集エラーが発生しにくくなり、コンパイルが高速化され、依存関係が(システムライブラリや他のカスタムクラスから)減少するはずです。

もちろん、このアプローチは多態性の設計思想としても考えられます。一般的なオプションクラスから派生したグループクラスから派生した特定のオプション/アクションクラスがあり、共通コードを階層の上位にバンドルする機会があります。

クラスに変更しても役に立ちません。明快さ、堅牢性、コンパクトさ、保守性、パフォーマンスの点で、列挙型とswitchステートメントよりも優れています。あなたが自問しなければならないのは、型を扱うかどうかです。列挙型を頻繁かつ継続的に追加していることを考えると、型ではなくデータを扱っているように見えます。単一のクラスと複数のインスタンスが必要になる場合があります。これは、DesignerAnalystがテーブル提案で示唆するものです。オブジェクトインスタンスの入力データをハードコードするか、テキストファイルで入力を平手打ちすることができます。大きな疑問が残ります。これらの列挙値は本当に何ですか?彼らはどのように分類しますか?

1
Martin Maat

必要なときにクラスを無視できるように明確に定義された構造の一部である場合、クラスを追加しても問題はありません。それらをモジュールに配置するだけで、変更する必要がある場合にのみモジュールにアクセスできます。

IMHO、各オプションは値を計算する方法を表すため、OOでは、各意見は、deriveValue()の特定の実装を持つオブジェクトで表す必要があります。つまり、Javaでは、各オブジェクトに実装を定義する特定のクラスが必要です。したがって、コードは次のようになります。

オプションパッケージ:

public interface Option {
     Value deriveValue(? p1, ? p2, ? p3);
}

public FirstOption implements Option {
    Value deriveValue(? p1, ? p2, ? p3){ ... } 
}

他のパッケージで;

class AClass{
   private Option o;

   Value method(){ o.deriveValue(p1,p2,p3); }
}

指定する動作が異なる場合は、大きなスイッチではなく、1つの呼び出しでそれぞれを表すことができます。警告:新しい動作を追加するたびに、各オプションクラスを開いて新しいメソッドを追加する必要があります。

0
mgoeminne