web-dev-qa-db-ja.com

メソッドに多くの引数を渡すためのベストプラクティス?

時折、多くの引数を受け取るメソッドを書く必要があります。例えば:

public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
{
}

この種の問題が発生すると、引数をマップにカプセル化することがよくあります。

Map<Object,Object> params = new HashMap<Object,Object>();
params.put("objA",ObjA) ;

......

public void doSomething(Map<Object,Object> params)
{
 // extracting params 
 Object objA = (Object)params.get("objA");
 ......
 }

これは良い方法ではありません。paramsをマップにカプセル化することは効率の無駄です。良いことは、クリーンな署名であり、他のパラメーターを最小限の変更で簡単に追加できることです。この種の問題のベストプラクティスは何ですか?

80
Sawyer

Effective Java 、Chapter 7(Methods)、Item 40(Design method signatures入念に設計)では、Blochはこう書いています:

過度に長いパラメーターリストを短縮する方法は3つあります。

  • メソッドを複数のメソッドに分割し、それぞれがパラメーターのサブセットのみを必要とする
  • パラメーターのグループを保持するヘルパークラスを作成します(通常は静的メンバークラス)
  • builderのパターンをオブジェクトの構築からメソッドの呼び出しに適合させます。

詳細については、本を購入することをお勧めします。本当に価値があります。

120
JRL

魔法の文字列キーでマップを使用するのは悪い考えです。コンパイル時のチェックが失われ、必要なパラメーターが何であるかが本当にわかりません。それを補うために非常に完全なドキュメントを書く必要があります。コードを見ずに、これらの文字列が何であるかを数週間で覚えていますか?タイプミスをした場合はどうなりますか?間違ったタイプを使用しますか?コードを実行するまでわかりません。

代わりにモデルを使用してください。これらすべてのパラメーターのコンテナーになるクラスを作成します。そうすれば、Javaの型安全性を維持できます。そのオブジェクトを他のメソッドに渡したり、コレクションに入れたりすることもできます。

もちろん、パラメーターのセットが他の場所で使用されたり、渡されたりしない場合、専用のモデルは過剰である可能性があります。バランスを取る必要があるため、常識を使用してください。

68
tom

多くのオプションパラメータがある場合は、流れるようなAPIを作成できます。単一のメソッドをメソッドのチェーンで置き換えます

exportWithParams().datesBetween(date1,date2)
                  .format("xml")
                  .columns("id","name","phone")
                  .table("angry_robots")
                  .invoke();

静的インポートを使用すると、内部の流れるようなAPIを作成できます。

... .datesBetween(from(date1).to(date2)) ...
24
Ha.

「パラメーターオブジェクトの導入」と呼ばれます。複数の場所で同じパラメーターリストを渡す場合は、それらすべてを保持するクラスを作成してください。

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);

同じパラメーターリストをあまり頻繁に渡さない場合でも、その簡単なリファクタリングにより、コードの可読性が向上します。これはalways goodです。 3か月後にコードを見ると、バグの修正や機能の追加が必要なときに理解しやすくなります。

もちろんこれは一般的な哲学であり、詳細を提供していないため、詳細なアドバイスもできません。 :-)

12
dimitarvp

まず、メソッドをリファクタリングしようとします。多数のパラメーターを使用している場合は、いずれにしても長すぎる可能性があります。分解すると、コードが改善され、各メソッドのパラメーターの数が減る可能性があります。また、操作全体を独自のクラスにリファクタリングできる場合もあります。次に、同じパラメーターリストの同じ(またはスーパーセット)を使用している他のインスタンスを探します。複数のインスタンスがある場合、これらのプロパティが一緒に属していることを示す可能性があります。その場合、パラメーターを保持するクラスを作成して使用します。最後に、パラメーターの数がマップオブジェクトを作成してコードの可読性を向上させる価値があるかどうかを評価します。これは個人的な呼び出しだと思います-このソリューションにはそれぞれの方法で苦労があり、トレードオフのポイントはどこにあるかが異なります。 6つのパラメーターについては、おそらくそれを行いません。 10の場合、おそらく他の方法が最初に機能しなかった場合です。

10
tvanfosson

多くの場合、これはオブジェクトの構築時に問題になります。

その場合、builder object patternを使用します。パラメーターのリストが大きく、必ずしもすべてのパラメーターが必要なわけではない場合は、うまく機能します。

メソッド呼び出しに適応させることもできます。

また、読みやすさも大幅に向上します。

public class BigObject
{
  // public getters
  // private setters

  public static class Buider
  {
     private A f1;
     private B f2;
     private C f3;
     private D f4;
     private E f5;

     public Buider setField1(A f1) { this.f1 = f1; return this; }
     public Buider setField2(B f2) { this.f2 = f2; return this; }
     public Buider setField3(C f3) { this.f3 = f3; return this; }
     public Buider setField4(D f4) { this.f4 = f4; return this; }
     public Buider setField5(E f5) { this.f5 = f5; return this; }

    public BigObject build()
    {
      BigObject result = new BigObject();
      result.setField1(f1);
      result.setField2(f2);
      result.setField3(f3);
      result.setField4(f4);
      result.setField5(f5);
      return result;
    }
  }
}

// Usage:
BigObject boo = new BigObject.Builder()
  .setField1(/* whatever */)
  .setField2(/* whatever */)
  .setField3(/* whatever */)
  .setField4(/* whatever */)
  .setField5(/* whatever */)
  .build();

検証ロジックをBuilder set ..()およびbuild()メソッドに配置することもできます。

7
Bohdan

Parameter object と呼ばれるパターンがあります。

アイデアは、すべてのパラメーターの代わりに1つのオブジェクトを使用することです。後でパラメータを追加する必要がある場合でも、オブジェクトに追加するだけです。メソッドのインターフェースは同じままです。

6
Padmarag

そのデータを保持するクラスを作成できます。ただし、十分な意味を持つ必要がありますが、マップ(OMG)を使用するよりもはるかに優れている必要があります。

5

Code Complete *はいくつかのことを提案します:

  • 「ルーチンのパラメーターの数を約7に制限します。7は、人々が理解するための魔法の数字です」(p 108)。
  • 「入力-変更-出力の順序でパラメーターを配置する...複数のルーチンが同様のパラメーターを使用する場合、一貫した順序で同様のパラメーターを配置します」(p 105)。
  • ステータス変数またはエラー変数を最後に配置します。
  • tvanfosson のように、ルーチンが必要とする構造化変数(オブジェクト)の部分のみを渡します。ただし、関数で構造化変数のほとんどを使用している場合は、構造全体を渡すだけですが、これによりある程度結合が促進されることに注意してください。

*初版、私は更新する必要があることを知っています。また、OOPがより一般的になり始めたときに第2版が作成されてから、このアドバイスの一部が変更された可能性があります。

4
Justin Johnson

リファクタリングすることをお勧めします。これらのオブジェクトは、このメソッドに渡す必要があることを意味しますか?それらを単一のオブジェクトにカプセル化する必要がありますか?

2
GaryF

マップを使用することは、コールシグネチャを削除する簡単な方法ですが、別の問題があります。メソッドの本体内を見て、メソッドがそのMapで何を期待しているのか、キー名、値のタイプを確認する必要があります。

よりクリーンな方法は、オブジェクトBean内のすべてのパラメーターをグループ化することですが、それでも問題を完全に解決するわけではありません。

ここにあるのは設計上の問題です。メソッドに7つ以上のパラメーターがあると、それらが何を表し、どの順序を持​​っているかを思い出すのに問題が生じ始めます。ここから、間違ったパラメーターの順序でメソッドを呼び出すだけで、多くのバグを取得できます。

多くのパラメーターを送信するためのベストプラクティスではなく、アプリのより良い設計が必要です。

2
user159088

Beanクラスを作成し、すべてのパラメーター(セッターメソッド)を設定し、このBeanオブジェクトをメソッドに渡します。

1
Tony

これは、多くの場合、クラスに複数の責任があることを示しています(つまり、クラスが多すぎます)。

単一の責任原則 を参照してください

詳細については。

0
helpermethod
  • コードを見て、これらすべてのパラメーターが渡される理由を確認してください。メソッド自体をリファクタリングできる場合もあります。

  • マップを使用すると、メソッドが脆弱になります。メソッドを使用している人がパラメーター名のスペルを間違えたり、メソッドがUDTを予期している場所に文字列を投稿したりするとどうなりますか?

  • Transfer Object を定義します。少なくとも型チェックを提供します。メソッド内ではなく使用時に検証を実行することもできます。

0
Everyone

渡されるパラメーターが多すぎる場合は、メソッドをリファクタリングしてみてください。たぶん、想定外のことをたくさんやっているのかもしれません。そうでない場合は、パラメータを単一のクラスに置き換えてみてください。これにより、すべてを単一のクラスインスタンスにカプセル化し、パラメーターではなくインスタンスを渡すことができます。

0
azamsharp