44 votes

Manière élégante de passer plusieurs arguments à une fonction

J'ai une fonction qui ressemble à ceci:

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

À mon avis, c'est moche. Quelle est la bonne façon de gérer ce problème? Devrais-je créer quelques vecteurs avec différents types de données (int, string et bool) et les transmettre en tant qu'arguments à cette fonction?

80voto

Quentin Points 3904

Si tous ces paramètres sont liés de manière significative, regroupez-les dans une structure.

44voto

Ralph Tandetzky Points 291

Les mettre dans un struct

Créer une structure

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

et de mettre tous les paramètres de là. Vous pouvez réellement fournir des valeurs par défaut pour l'initialisation de l' struct , ainsi que par la mise en œuvre d'un constructeur par défaut ou, en C++11, en fournissant de l'initialisation par défaut de chacun des membres. Vous pouvez alors modifier les valeurs qui ne sont pas censés être en défaut. Cette sélection de la cueillette de non-paramètres par défaut n'est pas possible pour un appel de fonction avec beaucoup de paramètres en C++.

Rendre l'interface agréable pour l'appelant

Pourtant, l'utilisation est un peu moche, puisque vous devez créer un nom temporaire de l'objet, puis modifiez les valeurs qui ne devraient pas l'être par défaut et ensuite passer de l'objet à la fonction:

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

Si vous appelez cette fonction dans plusieurs endroits différents, il est préférable d'éviter cette uglyness en fournissant une mutation des fonctions membres qui peuvent être enchaînés:

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

Puis l'appel de code peut écrire

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

sans ci-dessus uglyness. Cela évite que le nom de l'objet qui est utilisée une fois seulement.

23voto

Alessandro Teruzzi Points 1860

Personnellement, je ne crois pas que le déplacement de tous les arguments dans un struct sera beaucoup mieux. Il suffit de déplacer la saleté sous le tapis. Quand vous allez à traiter avec la création de la structure, vous avez le même problème.

La question est de savoir combien réutilisables cette structure sera? Si vous vous retrouvez avec un 18 les paramètres pour un appel de fonction de quelque chose, c'est pas tout à fait raison dans votre conception. Après une analyse plus poussée, vous pourriez découvrir que ces paramètres peuvent être regroupés en différentes classes et les classes peuvent être cumulées pour un objet unique, qui sera l'entrée de votre fonction. Vous pouvez aussi préférer les classes de structure dans le but de protéger vos données.

MODIFIER

Je vais vous donner un petit exemple pour décrire de façon, plusieurs classes sont mieux que ce qu'on monolithique struct. Nous allons commencer à compter les tests qui ont besoin d'écrire pour couvrir la fonction ci-dessus. Il y a 18 paramètres en entrée (3 boolean). Donc, nous allons avoir besoin d'au moins 15 tests pour valider la saisie (en supposant que les valeurs ne sont pas reliés).

Le nombre total de tests est impossible à calculer sans la mise en œuvre, mais nous pouvons avoir une idée de l'ampleur. Laissez prendre la limite inférieure de tous les commentaires peuvent être traiter comme boolean le nombre de combinaisons possibles de 2^18, de sorte autour de 262000 tests.

Maintenant, ce qui se passerait si nous avons divisé l'entrée dans plusieurs objets?

Tout d'abord, le code pour valider l'entrée est déplacé en dehors de la fonction dans le corps de chaque objet (et il peut être réutilisé).

Mais plus important encore, le nombre de tests va s'effondrer, disons que dans le groupe de quatre (4,4,4 et 4 params par objet) le nombre total de tests est à seulement:

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

La cinquième attributs est due à la permutation des objets eux-mêmes.

Alors, qu'est-ce que des coûts plus exigeant? Écrire des milliers de tests ou de quelques autres classes?

Évidemment, ce n'est qu'une grossière simplification, toutefois, il sera sous-jacente au cœur du problème. Un désordre de l'interface n'est pas qu'une question de style ou un inconvénient pour le développeur, c'est un vrai obstacle pour produire du code de qualité.

C'est la leçon la plus importante que j'ai jamais appris dans ma carrière en tant que développeur professionnel: "Grandes classes et de la graisse interfaces sont du mal". C'est juste mon heuristique version du principe de responsabilité unique (j'ai remarqué que la SRP peut être difficile à obtenir de droit, ce qu'il semble raisonnable d'être seule responsabilité, il peut être pas tout à fait la même après une heure de codage, donc j'ai utilisé une règle heuristique pour m'aider à revaulate mon choix de départ).

14voto

PovilasB Points 370

Ou vous pouvez utiliser une interface fluide . Cela ressemblerait à ceci:

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

Ceci est applicable si vous avez des valeurs par défaut pour vos paramètres spécifiés ou s'il est autorisé à avoir un script partiellement construit.

10voto

izb Points 12736

Ignorant la possibilité ou l'opportunité d'un changement de fonction ou d'un programme, d'une certaine façon à réduire le nombre de paramètres...

J'ai vu des normes de codage qui spécifient la façon dont les longues listes de paramètres doit être mis en forme, pour les cas où la refactorisation est pas possible. Un exemple est l'utilisation de double indentations et un paramètre par ligne (Pas pour toutes les fonctions - seulement pour ceux qui ont plusieurs lignes de paramètres).

E. g.

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

Le point ici est de créer une mise en page cohérente et look à toutes les fonctions avec un grand nombre de paramètres.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X