web-dev-qa-db-ja.com

関数に複数の引数を渡すエレガントな方法

次のような関数があります。

bool generate_script (bool net, bool tv, bool phone,
                        std::string clientsID,
                        std::string password,
                        int index, std::string number, 
                        std::string Iport, std::string sernoID,
                        std::string VoiP_number, std::string  VoiP_pass,
                        std::string target, int slot, int port, 
                        int onu, int extra, std::string IP, std::string MAC);

私の意見では、見苦しいです。この問題を処理する適切な方法は何ですか?異なるデータ型(int、string、bool)でいくつかのベクトルを作成し、この関数への引数として渡す必要がありますか?

41

これらのパラメーターがすべて意味のある関係にある場合は、構造体にまとめてください。

77
Quentin

それらをstructに入れます

構造を作成する

struct GenerateScriptParams { /* ... */ };

すべてのパラメーターをそこに入れます。実際には、デフォルトコンストラクタを実装するか、C++ 11で個々のメンバーのデフォルト初期化を提供することにより、structの初期化にもデフォルト値を提供できます。その後、デフォルトとは想定されていない値を変更できます。 C++で多数のパラメーターを使用した関数呼び出しでは、このデフォルト以外のパラメーターの選択的な選択はできません。

呼び出し元にとってインターフェースを素敵にする

ただし、一時的な名前オブジェクトを作成し、デフォルトではない値を変更してからオブジェクトを関数に渡す必要があるため、使用方法は少し見苦しくなります。

GenerateScriptParams gsp;
gsp.net = true;
gsp.phone = false;
gps.extra = 10;
generate_script( gsp );

その関数をいくつかの異なる場所で呼び出す場合、チェーン化できるメンバー関数を変更することにより、このさを回避することは理にかなっています。

GenerateScriptParams & GenerateScriptParams::setNet  ( bool val );
GenerateScriptParams & GenerateScriptParams::setTV   ( bool val );
GenerateScriptParams & GenerateScriptParams::setPhone( bool val );
// ... //

その後、呼び出しコードは次のように書くことができます

generate_script( GenerateScriptParams()
    .setNet(true),
    .setPhone(false),
    .setExtra(10) );

上記のさなし。これにより、一度しか使用されない名前付きオブジェクトが回避されます。

44
Ralph Tandetzky

個人的には、1つの構造体ですべての引数を移動すると、コードがはるかに改善されるとは考えていません。汚れをカーペットの下に移動するだけです。構造体の作成に取り組むとき、あなたは同じ問題を抱えています。

問題は、この構造体がどれだけ再利用可能かということです。 1つの関数呼び出しで18個のパラメーターが必要になる場合は、設計では正しくありません。さらに分析すると、これらのパラメーターをいくつかの異なるクラスにグループ化し、それらのクラスを関数の入力となる単一のオブジェクトに集約できることがわかります。データを保護するために、構造化するクラスを好む場合もあります。

[〜#〜] edit [〜#〜]

いくつかのクラスが1つのモノリシック構造体よりも優れている理由を説明する小さな例を挙げます。上記の機能をカバーするために書く必要があるテストのカウントを始めましょう。入力として18個のパラメーターがあります(3つのブール値)。したがって、入力を検証するためだけに少なくとも15のテストが必要になります(値が相互接続されていない場合)。

テストの総数を実装なしで計算することは不可能ですが、その規模については把握できます。すべての入力をブール値として扱うことができる下限を取り、可能な組み合わせの数は2 ^ 18なので、262000テストです。

ここで、入力を複数のオブジェクトに分割するとどうなりますか?

まず、入力を検証するためのコードは、関数からすべての単一オブジェクトの本体に移動します(再利用できます)。

しかし、さらに重要なことは、テストの数が崩壊することです。たとえば、4つのグループ(オブジェクトごとに4,4,4および4パラメーター)でテストの総数は次のようになります。

2 ^ 4 + 2 ^ 4 + 2 ^ 4 + 2 ^ 4 + 2 ^ 4 = 80

5番目の属性は、オブジェクト自体の順列によるものです。

それでは、より多くのコストが必要なのは何ですか?何千ものテストを書くか、それともクラスをもう少し書きますか?

明らかに、これは大雑把な単純化ですが、問題の根底にあります。 乱雑なインターフェイスは、スタイルの問題や開発者にとって不便なだけでなく、品質の高いコードを生成することの真の障害です

これは、プロの開発者としてのキャリアで学んだ最も重要な教訓です:「大きなクラスと太いインターフェイスは悪です」。これは、単一責任原則の私のヒューリスティックバージョンです(SRPを正しくするのは難しい場合があることに気づきました。私の最初の選択を再評価するのを助けるルール)。

20

または、 fluent interface を使用できます。次のようになります。

script my_script(mandatory, parameters);
my_script.net(true).tv(false).phone(true);

これは、指定したパラメーターのデフォルト値がある場合、または部分的に構築されたスクリプトを使用できる場合に適用されます。

13
PovilasB

パラメーターの数を減らすために何らかの方法で関数またはプログラムを変更する可能性または望ましさを無視する...

リファクタリングが不可能な場合に、パラメータリストをフォーマットする期間を指定するコーディング標準を見てきました。そのような例の1つは、行ごとに二重インデントと1つのパラメーターを使用することです(すべての関数ではなく、複数行のパラメーターを持つ関数のみ)。

例えば。

bool generate_script (
        bool net,
        bool tv,
        bool phone,
        std::string clientsID,
        std::string password,
        int index,
        std::string number,
        std::string Iport,
        std::string sernoID,
        std::string VoiP_number,
        std::string  VoiP_pass,
        std::string target,
        int slot,
        int port,
        int onu,
        int extra,
        std::string IP,
        std::string MAC);

ここでのポイントは、一貫したレイアウトを作成し、多数のパラメーターを持つすべての関数を探すことです。

8
izb