J’ai une fonction qui ressemble à ceci:
bool generate_script (bool net, bool tv, bool phone, std::ssortingng clientsID, std::ssortingng password, int index, std::ssortingng number, std::ssortingng Iport, std::ssortingng sernoID, std::ssortingng VoiP_number, std::ssortingng VoiP_pass, std::ssortingng target, int slot, int port, int onu, int extra, std::ssortingng IP, std::ssortingng MAC);
À mon avis, il a l’air moche. Quelle est la manière appropriée de gérer ce problème? Dois-je créer peu de vecteurs avec différents types de données (int, ssortingng et bool) et les transmettre comme arguments à cette fonction?
Si tous ces parameters sont significativement liés, empaquetez-les dans une structure.
struct
Créer une structure
struct GenerateScriptParams { /* ... */ };
et y mettre tous les parameters. Vous pouvez également fournir des valeurs par défaut pour l’initialisation de la struct
en implémentant un constructeur par défaut ou, en C ++ 11, en fournissant une initialisation par défaut des membres individuels. Vous pouvez ensuite modifier les valeurs qui ne sont pas censées être définies par défaut. Cette sélection sélective de parameters autres que ceux par défaut n’est pas possible pour un appel de fonction avec beaucoup de parameters en C ++.
Cependant, l’usage est un peu moche, puisque vous devez créer un object de nom temporaire, puis modifier les valeurs qui ne doivent pas être définies par défaut, puis transmettre l’object à la fonction:
GenerateScriptParams gsp; gsp.net = true; gsp.phone = false; gps.extra = 10; generate_script( gsp );
Si vous appelez cette fonction à plusieurs endroits différents, il est judicieux d’éviter cette horreur en fournissant des fonctions de membre mutantes pouvant être chaînées:
GenerateScriptParams & GenerateScriptParams::setNet ( bool val ); GenerateScriptParams & GenerateScriptParams::setTV ( bool val ); GenerateScriptParams & GenerateScriptParams::setPhone( bool val ); // ... //
Alors le code d’appel peut écrire
generate_script( GenerateScriptParams() .setNet(true), .setPhone(false), .setExtra(10) );
sans la laideur ci-dessus. Cela évite l’object nommé qui n’est utilisé qu’une seule fois.
Personnellement, je ne crois pas que déplacer tous les arguments dans une structure rendrait le code bien meilleur. Vous déplacez simplement la saleté sous le tapis. Lorsque vous allez gérer la création de la structure, vous avez le même problème.
La question est de savoir combien cette structure sera réutilisable? Si vous vous retrouvez avec 18 parameters pour un appel de fonction, ce n’est pas tout à fait correct dans votre conception. Après une parsing plus approfondie, vous découvrirez que ces parameters peuvent être regroupés dans plusieurs classes différentes et que ces classes peuvent être agrégées en un seul object qui sera l’entrée de votre fonction. Vous pouvez également préférer les classes à struct afin de protéger vos données.
MODIFIER
Je vais vous donner un petit exemple pour décrire pourquoi plusieurs classes valent mieux qu’une structure monolithique. Commençons à compter les tests que vous devez écrire pour couvrir la fonction ci-dessus. Il y a 18 parameters en entrée (3 booléens). Nous allons donc avoir besoin d’au moins 15 tests uniquement pour valider l’entrée (en supposant que les valeurs ne sont pas interconnectées).
Le nombre total de tests est impossible à calculer sans la mise en œuvre, mais nous pouvons avoir une idée de l’ampleur. Soit prendre la limite inférieure toutes les entrées peuvent être traitées comme booléennes le nombre de combinaisons possibles est 2 ^ 18 soit environ 262000 tests .
Maintenant, que se passe-t-il si nous divisons l’entrée en plusieurs objects?
Tout d’abord, le code permettant de valider l’entrée est déplacé de la fonction vers le corps de chaque object (et peut être réutilisé).
Mais plus important encore, le nombre de tests va s’effondrer, disons en groupe de quatre (4,4,4 et 4 parameters par object), le nombre total de tests est seulement:
2 ^ 4 + 2 ^ 4 + 2 ^ 4 + 2 ^ 4 + 2 ^ 4 = 80
Le cinquième atsortingbut est dû à la permutation des objects avec eux-mêmes.
Alors, quoi de plus exigeant en termes de coûts? Ecrire des milliers de tests ou peu d’autres classes?
Évidemment, il s’agit d’une simplification grossière, mais elle sous-tendra le cœur du problème. Une interface de fouillis n’est pas seulement une question de style ou un inconvénient pour le développeur, c’est un véritable obstacle à la production d’un code de qualité .
C’est la leçon la plus importante que j’ai apprise dans ma carrière de développeur professionnel: “Les grosses classes et les grosses interfaces sont mauvaises”. C’est juste ma version heuristique du principe de la responsabilité unique (j’ai remarqué que le SRP peut être délicat pour bien faire les choses, ce qui semble raisonnable pour une responsabilité unique peut ne pas être tout à fait le même après une heure de codage). règle pour m’aider à revoir mes choix initiaux).
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 parameters spécifiés ou si un script partiellement construit est autorisé.
Ignorer la possibilité ou l’opportunité de modifier la fonction ou le programme de manière à réduire le nombre de parameters …
J’ai vu des normes de codage spécifiant la durée de formatage des listes de parameters pour les cas où le refactoring est impossible. Un exemple est l’utilisation de doubles indentations et d’un paramètre par ligne (pas pour toutes les fonctions – uniquement pour celles qui ont plusieurs lignes de parameters).
Par exemple
bool generate_script ( bool net, bool tv, bool phone, std::ssortingng clientsID, std::ssortingng password, int index, std::ssortingng number, std::ssortingng Iport, std::ssortingng sernoID, std::ssortingng VoiP_number, std::ssortingng VoiP_pass, std::ssortingng target, int slot, int port, int onu, int extra, std::ssortingng IP, std::ssortingng MAC);
Le point ici est de créer une mise en page cohérente et de rechercher toutes les fonctions avec un grand nombre de parameters.
Un peu tard, mais comme personne ne l’a encore fait, je voudrais souligner un aspect évident du problème: pour moi, une fonction qui prend tant d’arguments est susceptible de faire beaucoup de calculs, alors envisagez la possibilité de la décomposer en fonctions plus petites dans un premier temps.
Cela devrait vous aider à structurer vos données.