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


90

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

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をマップにカプセル化すると、完全に効率が低下します。良い点は、クリーンなシグネチャであり、変更が最も少ない他のパラメータを簡単に追加できることです。この種の問題のベストプラクティスは何ですか?

回答:


137

効果的なJavaの、第7章(方法)、項目40(設計法は慎重にシグネチャ)、ブロッホは書いています:

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

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

詳細については、本を購入することをお勧めします。


「過度に長いパラメータ」とは何ですか?メソッドのパラメーターが多すぎるといつ言えるでしょうか。特定の数または範囲はありますか?
Red M

2
@RedM私は常に、3つまたは4つを超えるパラメーターは「過度に長い」と考えていました
jtate

1
@jtateは個人的な選択ですか、それとも公式のドキュメントに従っていますか?
レッドM

1
@RedM個人設定:)
jtate 2017年

2
効果的なJavaの第3版では、これは第8章(メソッド)、項目51
GarethOwen、

71

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

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

もちろん、パラメータのセットが他の場所で使用されていないか、渡されていない場合、専用モデルはやり過ぎになる可能性があります。打たれるべきバランスがあるので、常識を使ってください。


24

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

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

静的インポートを使用すると、内部Fluent APIを作成できます。

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

2
オプションではなく、すべてのパラメーターが必要な場合はどうなりますか?
emeraldhieu 2011

1
この方法で、デフォルトのパラメータを設定することもできます。また、ビルダーパターンは流暢なインターフェイスに関連しています。これが本当に答えになるはずだと思います。長いコンストラクターをオプションの小さな初期化メソッドに分解することは別として。
Ehtesh Choudhury、2012

13

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

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

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

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


ガベージコレクションは問題になりますか?
rupinderjeet 16

呼び出し元の関数でパラメーターオブジェクトをローカルスコープにしたり、変更したりしない場合はそうではありません。そのような状況では、ほとんどの場合、データは収集され、そのメモリはかなり迅速に再利用されます。
dimitarvp 2016

イモ、あなたもXXXParameter param = new XXXParameter();利用できる必要があります、それから使用しXXXParameter.setObjA(objA)ます; etc ...
satibel 16

10

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


8

これは、オブジェクトを作成するときに問題になることがよくあります。

その場合は、ビルダーオブジェクトパターンを使用します。パラメーターの大きなリストがあり、必ずしもすべてが必要なわけではない場合は、うまく機能します。

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

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

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();

検証ロジックをビルダーのset ..()およびbuild()メソッドに入れることもできます。


あなたの分野の多くがそうであるならば、あなたは何を推薦しますfinalか?これが私がヘルパー関数を書くのを妨げている主なものです。フィールドをプライベートにして、そのクラスのコードでフィールドを誤って変更しないようにすることができると思いますが、もっとエレガントなものを望んでいます。
ragerdl

7

パラメータオブジェクトと呼ばれるパターンがあります

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


5

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


メソッドパラメータを保持するクラスを作成する必要はないと思います。
Sawyer

同じパラメーターを渡すインスタンスが複数ある場合にのみ、クラスを作成します。これは、パラメータが関連していて、おそらく一緒に属していることを示します。単一のメソッドのクラスを作成する場合、治療法はおそらく病気よりも悪いです。
tvanfosson 2010年

はい-関連するパラメーターをDTOまたは値オブジェクトに移動できます。複数のパラメーターの一部はオプションですか?つまり、メインメソッドはこれらの追加パラメーターでオーバーロードされていますか?そのような場合-私はそれが許容できると感じています。
JoseK 2010年

私が言ったことはそれが十分に意味のあるものでなければならないということです。
ヨハネスルドルフ

4

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

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

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


2

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


はい、そうすべきです。たとえば、大規模な検索フォームには、無関係な制約が多く、ページ分割の必要があります。currentPageNumber、searchCriteria、pageSize ...を渡す必要があります
Sawyer

2

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

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

ここにあるのはデザインの問題です。メソッドに7つ以上のパラメーターを指定すると、それらが表すものとその順序を思い出すときに問題が発生し始めます。ここからは、間違ったパラメーターの順序でメソッドを呼び出すだけで、多くのバグが発生します。

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


1

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


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

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

  • 転送オブジェクトを定義します。少なくとも型チェックが提供されます。メソッド内ではなく、使用時に検証を実行することもできます。



0

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


0

以前のやり方に固執すると思います。あなたの例のパラメータの数は多くありませんが、代替案ははるかに恐ろしいです。

  1. マップ-あなたが言及した効率性の問題がありますが、ここでより大きな問題は次のとおりです。

    • 呼び出し元は、何
      も参照せずに何を送信するかわからない...どのキーと
      値が使用されているかを正確に示すjavadocsはありますか?実行する場合(これはすばらしい)、多くのパラメーターを持つことも問題ではありません。
    • 異なる引数タイプを受け入れることは非常に困難になります。入力パラメーターを単一のタイプに制限するか、Map <String、Object>を使用してすべての値をキャストできます。ほとんどの場合、どちらのオプションも恐ろしいものです。
  2. ラッパーオブジェクト-これは、最初にラッパーオブジェクトを埋める必要があるため、問題を解決するだけです。メソッドに直接ではなく、パラメーターオブジェクトのコンストラクターに対して行います。問題の移動が適切かどうかを判断することは、そのオブジェクトの再利用に依存します。例えば:

それを使用しないでしょう:最初の呼び出しで1回だけ使用されるので、1行を処理するための多くの追加コード...?

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    SomeObject i = obj2.callAnotherMethod(a, b, c, h);
    FinalResult j = obj3.callAFinalMethod(c, e, f, h, i);
}

使用できる:ここでは、もう少し多くのことができます。まず、3つのメソッド呼び出しのパラメーターを因数分解できます。それ自体で他の2行も実行できます...つまり、ある意味で状態変数になります...

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    e = h.resultOfSomeTransformation();
    SomeObject i = obj2.callAnotherMethod(a, b, c, d, e, f, g);
    f = i.somethingElse();
    FinalResult j = obj3.callAFinalMethod(a, b, c, d, e, f, g, h, i);
}
  1. ビルダーパターン-これは私の見解ではアンチパターンです。最も望ましいエラー処理メカニズムは、後ではなく早期に検出することです。しかし、ビルダーパターンでは、欠落した呼び出し(プログラマーはそれを含めることを考えていませんでした)の必須パラメーターがコンパイル時から実行時に移動されます。もちろん、プログラマーが意図的にnullなどをスロットに挿入した場合、それは実行時になりますが、それでもエラーを早期にキャッチすることは、呼び出すメソッドのパラメーター名を調べることを拒否するプログラマーに対応することのはるかに大きな利点です。多数のオプションパラメータを処理する場合にのみ適切であると私は思います。それでも、メリットはせいぜいわずかです。私はビルダーの「パターン」に強く反対しています。

他に考慮すべきことを忘れているのは、これらすべてにおけるIDEの役割です。メソッドにパラメーターがある場合、IDEはほとんどのコードを生成し、赤い線は何を提供/設定する必要があるかを思い出させます。オプション3を使用する場合...これは完全に失われます。それを正しくするかどうかはプログラマ次第であり、コーディングとコンパイル時間の合図はありません...プログラマはそれをテストして調べる必要があります。

さらに、オプション2と3を不必要に広範囲に採用すると、生成される重複コードが大量に発生するため、メンテナンスの面で長期的にマイナスの影響があります。コードの数が多いほど、維持する必要のあるコードが多くなり、維持に多くの時間と費用がかかります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.