30個のパラメーターを受け取るメソッドがあります。パラメーターを受け取って1つのクラスに入れたので、1つのパラメーター(クラス)をメソッドに渡すことができました。すべてのパラメーターが含まれている場合でも、すべてのパラメーターをカプセル化するオブジェクトを渡すことは、リファクタリングの場合は完全に問題ありませんか?.
それはいい考えです。これは通常、たとえばWCFでデータコントラクトが行われる方法です。
このモデルの利点の1つは、新しいパラメーターを追加する場合、クラスのコンシューマーがパラメーターを追加するためだけに変更する必要がないことです。
David Heffernanが述べているように、それはコードを自己文書化するのに役立ちます:
FrobRequest frobRequest = new FrobRequest
{
FrobTarget = "Joe",
Url = new Uri("http://example.com"),
Count = 42,
};
FrobResult frobResult = Frob(frobRequest);
ここでの他の回答は、クラスのインスタンスを渡す方が30個のパラメーターを渡すよりも良いことを正しく指摘していますが、多数のパラメーターが根本的な問題の症状である可能性があることに注意してください。
たとえば、静的メソッドは多くの場合、パラメータの数が増えるので、それらはずっとインスタンスメソッドである必要があり、そのクラスのインスタンスでより簡単に維持できる多くの情報を渡しています。
または、より高い抽象化レベルのオブジェクトにパラメーターをグループ化する方法を探します。無関係なパラメータの束を単一のクラスにダンプすることは、最後の手段のIMOです。
これに関するいくつかのアイデアについては パラメータが多すぎますか? を参照してください。
それは良いスタートです。しかし、その新しいクラスを取得したので、コードを裏返しにすることを検討してください。そのクラスをパラメーターとして取るメソッドを新しいクラスに移動します(もちろん、元のクラスのインスタンスをパラメーターとして渡します)。これで、クラスに単独で大きなメソッドができました。これを小さくして、管理しやすく、テストしやすいメソッドに分けるのは簡単です。これらのメソッドの一部は元のクラスに戻る場合がありますが、かなりの量がおそらく新しいクラスに残ります。 パラメータオブジェクトの導入 から メソッドをメソッドオブジェクトに置き換える に移動しました。
30個のパラメーターを持つメソッドがあることは、メソッドが長すぎて複雑すぎることを示すかなり強い兆候です。デバッグが難しい、テストが難しい。だから、あなたはそれについて何かをするべきです、そして、パラメータオブジェクトの導入は始めるのに良い場所です。
パラメータオブジェクトへのリファクタリング自体は悪い考えではありませんが、他の場所から提供された30個のデータを必要とするクラスがコードの匂いの可能性があるという問題を隠すために使用しないでください。パラメータオブジェクトのリファクタリングの導入は、おそらく、その手順の終わりではなく、より広範なリファクタリングプロセスの途中のステップと見なす必要があります。
それが実際に対処しない懸念の1つは、Feature Envyの懸念です。パラメータオブジェクトを渡されるクラスが別のクラスのデータに非常に関心を持っているという事実は、そのデータを操作するメソッドを、データが存在する場所に移動する必要があることを示していませんか?一緒に属しているメソッドとデータのクラスターを識別し、それらをクラスにグループ化することは、カプセル化を強化し、コードをより柔軟にすることにより、本当に優れています。
動作とデータを別々のユニットに分割する操作を数回繰り返した後、膨大な数の依存関係を持つクラスがなくなったことに気付くはずです。これにより、コードがよりしなやかになるため、常により良い最終結果になります。
これは優れたアイデアであり、問題に対する非常に一般的な解決策です。 2つまたは3つを超えるパラメーターを持つメソッドは、指数関数的に理解が難しくなります。
これらすべてを1つのクラスにカプセル化すると、コードがより明確になります。プロパティには名前があるため、次のような自己文書化コードを記述できます。
params.height = 42;
params.width = 666;
obj.doSomething(params);
当然のことながら、多くのパラメーターがある場合、位置の識別に基づく代替案は単に恐ろしいものです。
さらに別の利点は、すべての呼び出しサイトで変更を強制することなく、インターフェイスコントラクトにパラメーターを追加できることです。ただし、これは思ったほど簡単ではありません。異なる呼び出しサイトが新しいパラメーターに異なる値を必要とする場合、パラメーターベースのアプローチよりもそれらを突き止めるのは困難です。パラメーターベースのアプローチでは、新しいパラメーターを追加すると、各呼び出しサイトで変更が強制されて新しいパラメーターが提供され、コンパイラーにそれらすべてを見つける作業を実行させることができます。
Martin Fowlerはこれを Introduce Parameter Object と呼んでいますRefactoring。その引用があれば、それを悪い考えと呼ぶ人はほとんどいないでしょう。
30のパラメータは混乱です。私は、プロパティを持つクラスを持つことは、よりきれいだと思います。同じカテゴリに当てはまるパラメータのグループに対して、複数の「パラメータクラス」を作成することもできます。
たぶんC#4.0のオプションで名前付きのパラメータはこれの良い代替品ですか?
とにかく、あなたが説明している方法は、プログラムの振る舞いを抽象化するのにも役立ちます。たとえば、インターフェイスに標準のSaveImage(ImageSaveParameters saveParams)
- functionを1つ含めることができます。ここで、ImageSaveParameters
もインターフェイスであり、イメージ形式に応じて追加のパラメーターを持つことができます。たとえば、JpegSaveParameters
にはQuality
-プロパティがあり、PngSaveParameters
にはBitDepth
-プロパティがあります。
これがPaint.NETのsave save-dialogが実行する方法であり、実際の例です。
リファクタリングを行うかどうかにかかわらず、 Plain Old Data クラスを使用することは合理的です。なぜそうではないと思ったのか、私は興味があります。
前に述べたように:これは正しい手順ですが、次のことも考慮してください。
クラスの代わりに構造体を使用することも検討できます。
しかし、あなたがやろうとしていることは非常に一般的で素晴らしいアイデアです!
ここには非常に多くの素晴らしい答えがあります。 2セント追加したいのですが。
パラメータオブジェクトから始めるのがよいでしょう。しかし、できることは他にもあります。以下を検討してください(Rubyの例):
/ 1 /すべてのパラメーターを単にグループ化する代わりに、パラメーターの意味のあるグループ化ができるかどうかを確認します。複数のパラメーターオブジェクトが必要になる場合があります。
def display_line(startPoint, endPoint, option1, option2)
になるかもしれない
def display_line(line, display_options)
/ 2 /パラメータオブジェクトのプロパティ数は、元のパラメータ数よりも少ない場合があります。
def double_click?(cursor_location1, control1, cursor_location2, control2)
になるかもしれない
def double_click?(first_click_info, second_click_info)
# MouseClickInfo being the parameter object type
# having cursor_location and control_at_click as properties
このような使用法は、これらのパラメーターオブジェクトに意味のある動作を追加する可能性を発見するのに役立ちます。あなたは彼らが彼らの最初の データクラスのにおい をあなたの快適さにより早く振り払うのを見つけるでしょう。 :-)