128 votes

Quelle est la meilleure façon de remanier une méthode qui a trop de paramètres (6+) ?

Il m'arrive de rencontrer des méthodes comportant un nombre inconfortable de paramètres. Le plus souvent, il s'agit de constructeurs. Il me semble qu'il devrait y avoir une meilleure solution, mais je ne la vois pas.

return new Shniz(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)

J'ai pensé à utiliser des structs pour représenter la liste des paramètres, mais cela semble simplement déplacer le problème d'un endroit à l'autre, et créer un autre type dans le processus.

ShnizArgs args = new ShnizArgs(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
return new Shniz(args);

Cela ne semble donc pas être une amélioration. Alors quelle est la meilleure approche ?

0 votes

Vous avez dit "structure". Ce terme a différentes connotations dans différents langages de programmation. Que voulez-vous dire ?

2 votes

Si vous cherchez un langage particulier pour désambiguïser, allez-y avec C#. Mais en fait, il s'agit d'un simple sac de propriétés. Il a différents noms de propriétés avec différents types. Il peut être défini comme une classe, une table de hachage, une structure ou autre.

0 votes

Cet article a de bonnes idées sur le sujet. Il est spécifique à Javascript, mais les principes peuvent être réappliqués à d'autres langages.

124voto

Jay Bazuzi Points 20462

Je vais supposer que vous voulez dire C# . Certains de ces éléments s'appliquent également à d'autres langues.

Vous avez plusieurs possibilités :

passer du constructeur aux régleurs de propriétés . Cela peut rendre le code plus lisible, car il est évident pour le lecteur de savoir quelle valeur correspond à quel paramètre. La syntaxe de l'initialisateur d'objet rend cela agréable. Elle est également simple à mettre en œuvre, car vous pouvez simplement utiliser des propriétés générées automatiquement et ne pas écrire les constructeurs.

class C
{
    public string S { get; set; }
    public int I { get; set; }
}

new C { S = "hi", I = 3 };

Cependant, vous perdez l'immuabilité et vous perdez la possibilité de vous assurer que les valeurs requises sont définies avant d'utiliser l'objet au moment de la compilation.

Modèle de construction .

Pensez à la relation entre string y StringBuilder . Vous pouvez l'obtenir pour vos propres classes. J'aime l'implémenter comme une classe imbriquée, donc la classe C a une classe connexe C.Builder . J'aime aussi une interface fluide sur le constructeur. Si elle est bien faite, vous pouvez obtenir une syntaxe comme celle-ci :

C c = new C.Builder()
    .SetX(4)    // SetX is the fluent equivalent to a property setter
    .SetY("hello")
    .ToC();     // ToC is the builder pattern analog to ToString()

// Modify without breaking immutability
c = c.ToBuilder().SetX(2).ToC();

// Still useful to have a traditional ctor:
c = new C(1, "...");

// And object initializer syntax is still available:
c = new C.Builder { X = 4, Y = "boing" }.ToC();

J'ai un script PowerShell qui me permet de générer le code constructeur pour faire tout cela, où l'entrée ressemble à :

class C {
    field I X
    field string Y
}

Ainsi, je peux générer au moment de la compilation. partial me permettent d'étendre à la fois la classe principale et le constructeur sans modifier le code généré.

Refactoring "Introduce Parameter Object" (introduction d'un objet paramètre) . Voir le Catalogue de remaniement . L'idée est que vous prenez certains des paramètres que vous passez et les mettez dans un nouveau type, puis passez une instance de ce type à la place. Si vous faites cela sans réfléchir, vous vous retrouverez au point de départ :

new C(a, b, c, d);

devient

new C(new D(a, b, c, d));

Cependant, cette approche a le plus grand potentiel pour avoir un impact positif sur votre code. Donc, continuez en suivant ces étapes :

  1. Cherchez sous-ensembles de paramètres qui ont un sens ensemble. Regrouper tous les paramètres d'une fonction sans réfléchir ne vous apporte pas grand-chose ; l'objectif est d'avoir des regroupements qui ont un sens. Vous saurez que vous avez bien fait lorsque le nom du nouveau type sera évident.

  2. Recherchez d'autres endroits où ces valeurs sont utilisées ensemble, et utilisez le nouveau type là aussi. Il y a de fortes chances pour que, lorsque vous avez trouvé un bon nouveau type pour un ensemble de valeurs que vous utilisez déjà partout, ce nouveau type ait également un sens dans tous ces endroits.

  3. Recherchez les fonctionnalités qui se trouvent dans le code existant, mais qui appartiennent au nouveau type.

Par exemple, vous voyez peut-être un code qui ressemble à ceci :

bool SpeedIsAcceptable(int minSpeed, int maxSpeed, int currentSpeed)
{
    return currentSpeed >= minSpeed & currentSpeed < maxSpeed;
}

Vous pourriez prendre le minSpeed y maxSpeed et les mettre dans un nouveau type :

class SpeedRange
{
   public int Min;
   public int Max;
}

bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed)
{
    return currentSpeed >= sr.Min & currentSpeed < sr.Max;
}

C'est mieux, mais pour vraiment tirer parti du nouveau type, déplacez les comparaisons dans le nouveau type :

class SpeedRange
{
   public int Min;
   public int Max;

   bool Contains(int speed)
   {
       return speed >= min & speed < Max;
   }
}

bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed)
{
    return sr.Contains(currentSpeed);
}

Y maintenant nous arrivons à quelque chose : la mise en œuvre de SpeedIsAcceptable() dit maintenant ce que vous voulez dire, et vous avez une classe utile et réutilisable. (La prochaine étape évidente est de faire SpeedRange à Range<Speed> .)

Comme vous pouvez le constater, Introduire un paramètre-objet était un bon début, mais sa véritable valeur est qu'il nous a permis de découvrir un type utile qui manquait à notre modèle.

5 votes

Je vous suggère d'essayer d'abord "Introduire un objet paramètre", et de ne recourir aux autres options que si vous ne trouvez pas un bon objet paramètre à créer.

4 votes

Excellente réponse. si vous aviez mentionné l'explication du refactoring avant les sucres syntaxiques de c#, cette réponse aurait été mieux votée IMHO.

14 votes

Ooh ! +1 pour "Vous saurez que vous avez raison quand le nom du nouveau type sera évident."

105voto

Matthew Brubaker Points 1801

Le mieux serait de trouver des moyens de regrouper les arguments. Cela suppose, et ne fonctionne vraiment que si, vous vous retrouvez avec plusieurs "groupements" d'arguments.

Par exemple, si vous transmettez les spécifications d'un rectangle, vous pouvez transmettre x, y, largeur et hauteur ou simplement un objet rectangle contenant x, y, largeur et hauteur.

Recherchez ce genre de choses lors de la refactorisation afin de la nettoyer quelque peu. Si les arguments ne peuvent vraiment pas être combinés, commencez à chercher si vous avez une violation du principe de responsabilité unique.

6 votes

Bonne idée mais mauvais exemple ; le constructeur du Rectangle devrait avoir 4 arguments. Cela aurait plus de sens si la méthode attendait deux ensembles de coordonnées/dimensions du rectangle. Vous pourriez alors passer 2 rectangles au lieu de x1, x2, y1, y2...

3 votes

C'est juste. Comme je l'ai dit, cela n'a vraiment de sens que si vous vous retrouvez avec plusieurs groupements logiques.

28 votes

+1 : Pour la responsabilité unique, c'est l'un des rares commentaires dans toutes les réponses qui aborde vraiment le vrai problème. Un objet a vraiment besoin de 7 valeurs indépendantes pour former son identité.

20voto

kdgregory Points 21849

S'il s'agit d'un constructeur, en particulier s'il existe plusieurs variantes surchargées, vous devriez vous tourner vers le modèle Builder :

Foo foo = new Foo()
          .configBar(anything)
          .configBaz(something, somethingElse)
          // and so on

S'il s'agit d'une méthode normale, vous devez réfléchir aux relations entre les valeurs transmises, et peut-être créer un objet de transfert.

0 votes

Excellente réponse. Peut-être même plus pertinente que la réponse "mettre les paramètres dans une classe" que tout le monde (y compris moi) a donnée.

1 votes

C'est probablement une mauvaise idée de rendre votre classe mutable, juste pour éviter de passer trop de paramètres au constructeur.

0 votes

@outlaw - si la mutabilité est une préoccupation, vous pouvez facilement mettre en œuvre la sémantique "run once". Cependant, un grand nombre de paramètres ctor indique souvent un besoin de configuration (ou, comme d'autres l'ont noté, une classe qui essaie de faire trop de choses). (suite)

11voto

Wouter Lievens Points 1787

La réponse classique à cette question consiste à utiliser une classe pour encapsuler une partie, voire la totalité, des paramètres. En théorie, c'est très bien, mais je suis le genre de personne qui crée des classes pour les concepts qui ont une signification dans le domaine, donc il n'est pas toujours facile d'appliquer ce conseil.

Par exemple, au lieu de :

driver.connect(host, user, pass)

Vous pourriez utiliser

config = new Configuration()
config.setHost(host)
config.setUser(user)
config.setPass(pass)
driver.connect(config)

YMMV

5 votes

Je préférerais certainement le premier morceau de code. Je suis d'accord, il y a une certaine limite, au-delà de laquelle le nombre de paramètres devient laid, mais à mon goût, 3 serait acceptable.

10voto

Youssef Points 452

Ceci est cité dans le livre de Fowler et Beck : "Refactoring"

Liste longue des paramètres

Dans nos premiers jours de programmation, on nous a appris à passer en paramètre tout ce qui est nécessaire à l'activité de l'entreprise. une routine. C'était compréhensible car l'alternative était les données globales, et les données globales sont maléfiques et généralement douloureuses. mal et généralement pénible. Les objets changent cette situation car si vous n'avez pas quelque chose dont vous avez besoin, vous pouvez toujours demander à un autre objet de l'obtenir pour vous. Ainsi, avec les objets, vous n'avez pas à Ainsi, avec les objets, vous ne transmettez pas tout ce dont la méthode a besoin, mais vous en transmettez suffisamment pour que la méthode puisse accéder à tout ce dont elle a besoin. tout ce dont elle a besoin. Une grande partie de ce dont une méthode a besoin est disponible dans la classe hôte de la méthode. Dans programmes orientés objet, les listes de paramètres ont tendance à être beaucoup plus petites que dans les programmes traditionnels. traditionnels. C'est une bonne chose car les longues listes de paramètres sont difficiles à comprendre, parce qu'elles deviennent incohérentes et difficiles à comprendre. incohérentes et difficiles à utiliser, et parce que vous les modifiez constamment lorsque vous avez besoin de plus de données. La plupart des modifications sont supprimées par le passage d'objets, car il est beaucoup plus probable que vous n'ayez à effectuer que quelques modifications. de n'avoir besoin que de quelques requêtes pour obtenir un nouvel élément de données. Utilisez l'option Remplacer un paramètre par une méthode lorsque vous pouvez obtenir les données d'un paramètre en faisant une demande à un objet que vous connaissez déjà. en faisant une demande à un objet que vous connaissez déjà. Cet objet peut être un champ ou un autre paramètre. un autre paramètre. Utilisez l'option Préserver tout l'objet pour prendre un ensemble de données glanées dans un objet et les replacer dans l'objet. objet et le remplacer par l'objet lui-même. Si vous avez plusieurs éléments de données sans objet logique logique, utilisez Introduire un objet de paramètre. Il existe une exception importante à ces modifications. C'est lorsque vous faites explicitement ne voulez pas créer de dépendance entre l'objet appelé et l'objet plus grand. Dans ce cas Dans ces cas, le déballage des données et leur envoi en tant que paramètres est raisonnable, mais faites attention à la douleur impliqués. Si la liste des paramètres est trop longue ou change trop souvent, vous devez repenser votre structure de dépendance. structure de dépendance.

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