Meilleure pratique pour transmettre de nombreux arguments à la méthode?

91

Parfois, nous devons écrire des méthodes qui reçoivent de nombreux arguments, par exemple:

public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
{
}

Quand je rencontre ce genre de problème, j'encapsule souvent des arguments dans une carte.

Map<Object,Object> params = new HashMap<Object,Object>();
params.put("objA",ObjA) ;

......

public void doSomething(Map<Object,Object> params)
{
 // extracting params 
 Object objA = (Object)params.get("objA");
 ......
 }

Ce n'est pas une bonne pratique, encapsuler des paramètres dans une carte est totalement un gaspillage d'efficacité. La bonne chose est, la signature propre, facile d'ajouter d'autres paramètres avec le moins de modifications. quelle est la meilleure pratique pour ce genre de problème?

Scieur
la source

Réponses:

138

Dans Effective Java , Chapter 7 (Methods), Item 40 (Design method signatures soigneusement), Bloch écrit:

Il existe trois techniques pour raccourcir des listes de paramètres trop longues:

  • diviser la méthode en plusieurs méthodes, chacune ne nécessitant qu'un sous-ensemble des paramètres
  • créer des classes d'assistance pour contenir un groupe de paramètres (généralement des classes membres statiques)
  • adaptez le modèle Builder de la construction de l'objet à l'appel de méthode.

Pour plus de détails, je vous encourage à acheter le livre, ça vaut vraiment le coup.

JRL
la source
Que sont les "paramètres trop longs"? Quand pouvons-nous dire qu'une méthode a trop de paramètres? Y a-t-il un nombre spécifique ou une plage?
Red M
2
@RedM J'ai toujours considéré que plus de 3 ou 4 paramètres étaient "trop ​​longs"
jtate
1
@jtate est-ce un choix personnel ou suivez-vous un doc officiel?
Red M
1
@RedM personal preference :)
jtate
2
Avec la troisième édition de Effective Java,
voici le
71

Utiliser une carte avec des touches de chaîne magiques est une mauvaise idée. Vous perdez toute vérification du temps de compilation et les paramètres requis ne sont vraiment pas clairs. Vous auriez besoin d'écrire une documentation très complète pour compenser cela. Vous souviendrez-vous dans quelques semaines de ce que sont ces chaînes sans regarder le code? Et si vous faisiez une faute de frappe? Utilisez le mauvais type? Vous ne le saurez pas tant que vous n'aurez pas exécuté le code.

Utilisez plutôt un modèle. Créez une classe qui sera un conteneur pour tous ces paramètres. De cette façon, vous gardez la sécurité de type de Java. Vous pouvez également transmettre cet objet à d'autres méthodes, le placer dans des collections, etc.

Bien sûr, si l'ensemble de paramètres n'est pas utilisé ailleurs ou transmis, un modèle dédié peut être excessif. Il y a un équilibre à trouver, alors faites preuve de bon sens.

à M
la source
24

Si vous avez de nombreux paramètres facultatifs, vous pouvez créer une API fluide: remplacez la méthode unique par la chaîne de méthodes

exportWithParams().datesBetween(date1,date2)
                  .format("xml")
                  .columns("id","name","phone")
                  .table("angry_robots")
                  .invoke();

À l'aide de l'importation statique, vous pouvez créer des API internes fluides:

... .datesBetween(from(date1).to(date2)) ...
Ha.
la source
2
Et si tous les paramètres sont obligatoires et non facultatifs?
emeraldhieu
1
Vous pouvez également avoir des paramètres par défaut de cette façon. En outre, le modèle de générateur est lié aux interfaces fluides. Cela devrait vraiment être la réponse, je pense. En plus de décomposer un long constructeur en méthodes d'initialisation plus petites qui sont facultatives.
Ehtesh Choudhury
13

Il s'appelle "Introduce Parameter Object". Si vous vous trouvez en train de passer la même liste de paramètres à plusieurs endroits, créez simplement une classe qui les contient tous.

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);

Même si vous ne passez pas si souvent la même liste de paramètres, cette refactorisation facile améliorera toujours la lisibilité de votre code, ce qui est toujours bon. Si vous regardez votre code 3 mois plus tard, il sera plus facile à comprendre lorsque vous devez corriger un bogue ou ajouter une fonctionnalité.

C'est une philosophie générale bien sûr, et comme vous n'avez pas fourni de détails, je ne peux pas non plus vous donner de conseils plus détaillés. :-)

dimitarvp
la source
le ramassage des ordures sera-t-il un problème?
rupinderjeet
Pas si vous faites de l'objet paramètre une portée locale dans la fonction appelante, et si vous ne le mutez pas. Il sera probablement collecté et sa mémoire réutilisée assez rapidement dans de telles circonstances.
dimitarvp
Imo, vous devriez également avoir un XXXParameter param = new XXXParameter();disponible, puis utiliser XXXParameter.setObjA(objA); etc ...
satibel
10

Tout d'abord, j'essaierais de refactoriser la méthode. S'il utilise autant de paramètres, il peut être trop long de toute façon. Le décomposer améliorerait à la fois le code et réduirait potentiellement le nombre de paramètres de chaque méthode. Vous pourrez peut-être également refactoriser l'opération entière dans sa propre classe. Deuxièmement, je rechercherais d'autres cas où j'utilise le même (ou un sur-ensemble) de la même liste de paramètres. Si vous avez plusieurs instances, cela signifie probablement que ces propriétés appartiennent ensemble. Dans ce cas, créez une classe pour contenir les paramètres et utilisez-la. Enfin, j'évaluerais si le nombre de paramètres vaut la peine de créer un objet de carte pour améliorer la lisibilité du code. Je pense que c'est un appel personnel - il y a de la douleur dans chaque sens avec cette solution et le point de compromis peut différer. Pour six paramètres, je ne le ferais probablement pas. Pour 10, je le ferais probablement (si aucune des autres méthodes ne fonctionnait en premier).

Tvanfosson
la source
8

C'est souvent un problème lors de la construction d'objets.

Dans ce cas, utilisez un modèle d'objet de générateur , cela fonctionne bien si vous avez une grande liste de paramètres et que vous n'avez pas toujours besoin de tous.

Vous pouvez également l'adapter à l'appel de méthode.

Cela augmente également beaucoup la lisibilité.

public class BigObject
{
  // public getters
  // private setters

  public static class Buider
  {
     private A f1;
     private B f2;
     private C f3;
     private D f4;
     private E f5;

     public Buider setField1(A f1) { this.f1 = f1; return this; }
     public Buider setField2(B f2) { this.f2 = f2; return this; }
     public Buider setField3(C f3) { this.f3 = f3; return this; }
     public Buider setField4(D f4) { this.f4 = f4; return this; }
     public Buider setField5(E f5) { this.f5 = f5; return this; }

    public BigObject build()
    {
      BigObject result = new BigObject();
      result.setField1(f1);
      result.setField2(f2);
      result.setField3(f3);
      result.setField4(f4);
      result.setField5(f5);
      return result;
    }
  }
}

// Usage:
BigObject boo = new BigObject.Builder()
  .setField1(/* whatever */)
  .setField2(/* whatever */)
  .setField3(/* whatever */)
  .setField4(/* whatever */)
  .setField5(/* whatever */)
  .build();

Vous pouvez également insérer une logique de vérification dans les méthodes Builder set .. () et build ().

Bohdan
la source
Que recommanderiez-vous si plusieurs de vos champs le sont final? C'est la principale chose qui m'empêche d'écrire des fonctions d'aide. Je suppose que je peux rendre les champs privés et m'assurer de ne pas les modifier de manière incorrecte dans le code de cette classe, mais j'espère quelque chose de plus élégant.
ragerdl
7

Il existe un modèle appelé objet Parameter .

L'idée est d'utiliser un objet à la place de tous les paramètres. Maintenant, même si vous devez ajouter des paramètres plus tard, il vous suffit de les ajouter à l'objet. L'interface de la méthode reste la même.

Padmarag
la source
5

Vous pouvez créer une classe pour contenir ces données. Doit être suffisamment significatif, mais bien mieux que d'utiliser une carte (OMG).

Johannes Rudolph
la source
Je ne pense pas qu'il soit nécessaire de créer une classe pour contenir un paramètre de méthode.
Sawyer
Je ne créerais la classe que s'il y avait plusieurs instances de passage des mêmes paramètres. Cela signifierait que les paramètres sont liés et appartiennent probablement ensemble de toute façon. Si vous créez une classe pour une seule méthode, le remède est probablement pire que la maladie.
tvanfosson
Oui - vous pouvez déplacer les paramètres associés dans un DTO ou un objet de valeur. Certains des paramètres multiples sont-ils facultatifs, c'est-à-dire que la méthode principale est surchargée avec ces paramètres supplémentaires? Dans de tels cas, je pense que c'est acceptable.
JoseK
C'est ce que je voulais dire en disant doit être suffisamment significatif.
Johannes Rudolph
4

Code Complete * suggère plusieurs choses:

  • "Limitez le nombre de paramètres d'une routine à environ sept. Sept est un nombre magique pour la compréhension des gens" (p 108).
  • "Mettre les paramètres dans l'ordre entrée-modification-sortie ... Si plusieurs routines utilisent des paramètres similaires, mettez les paramètres similaires dans un ordre cohérent" (p 105).
  • Mettez les variables d'état ou d'erreur en dernier.
  • Comme l'a mentionné tvanfosson , ne transmettez que les parties de variables structurées (objets) dont la routine a besoin. Cela dit, si vous utilisez la plupart des variables structurées dans la fonction, passez simplement la structure entière, mais sachez que cela favorise le couplage dans une certaine mesure.

* Première édition, je sais que je devrais mettre à jour. De plus, il est probable que certains de ces conseils aient changé depuis la rédaction de la deuxième édition, lorsque la POO commençait à devenir plus populaire.

Justin Johnson
la source
2

La bonne pratique serait de refactoriser. Qu'en est-il de ces objets signifie qu'ils doivent être passés à cette méthode? Devraient-ils être encapsulés dans un seul objet?

GaryF
la source
oui ils devraient. Par exemple, un formulaire de recherche volumineux comporte de nombreuses contraintes et besoins de pagination non liés. vous devez passer currentPageNumber, searchCriteria, pageSize ...
Sawyer
2

L'utilisation d'une carte est un moyen simple de nettoyer la signature d'appel, mais vous avez un autre problème. Vous devez regarder à l'intérieur du corps de la méthode pour voir ce que la méthode attend dans cette Map, quels sont les noms de clés ou quels types les valeurs ont.

Une manière plus propre serait de regrouper tous les paramètres dans un bean objet, mais cela ne résout toujours pas complètement le problème.

Ce que vous avez ici est un problème de conception. Avec plus de 7 paramètres pour une méthode, vous commencerez à avoir des problèmes pour vous souvenir de ce qu'ils représentent et de leur ordre. De là, vous obtiendrez de nombreux bogues simplement en appelant la méthode dans le mauvais ordre des paramètres.

Vous avez besoin d'une meilleure conception de l'application et non d'une meilleure pratique pour envoyer de nombreux paramètres.


la source
1

Créez une classe bean, définissez tous les paramètres (méthode setter) et passez cet objet bean à la méthode.

Tony
la source
1
  • Regardez votre code et voyez pourquoi tous ces paramètres sont passés. Parfois, il est possible de refactoriser la méthode elle-même.

  • L'utilisation d'une carte rend votre méthode vulnérable. Que se passe-t-il si quelqu'un qui utilise votre méthode épelle mal un nom de paramètre ou publie une chaîne là où votre méthode attend un UDT?

  • Définissez un objet de transfert . Cela vous fournira au moins une vérification de type; il peut même être possible pour vous d'effectuer une validation au point d'utilisation plutôt que dans votre méthode.

Toutes les personnes
la source
0

Ceci indique souvent que votre classe a plus d'une responsabilité (c'est-à-dire que votre classe en fait TROP).

Voir le principe de responsabilité unique

pour plus de détails.

méthode d'aide
la source
0

Si vous passez trop de paramètres, essayez de refactoriser la méthode. Peut-être qu'il fait beaucoup de choses qu'il n'est pas censé faire. Si ce n'est pas le cas, essayez de remplacer les paramètres par une seule classe. De cette façon, vous pouvez tout encapsuler dans une seule instance de classe et transmettre l'instance et non les paramètres.

azamsharp
la source
0

Je dirais de rester fidèle à la façon dont vous l'avez fait avant. Le nombre de paramètres dans votre exemple n'est pas grand, mais les alternatives sont bien plus horribles.

  1. Carte - Il y a le problème d'efficacité que vous avez mentionné, mais le plus gros problème ici est:

    • Les appelants ne savent pas quoi vous envoyer sans faire référence à
      autre chose ... Avez-vous des javadocs qui indiquent exactement quelles clés et
      valeurs sont utilisées? Si vous le faites (ce qui est génial), avoir beaucoup de paramètres n'est pas non plus un problème.
    • Il devient très difficile d'accepter différents types d'arguments. Vous pouvez soit restreindre les paramètres d'entrée à un seul type, soit utiliser Map <String, Object> et convertir toutes les valeurs. Les deux options sont horribles la plupart du temps.
  2. Objets wrapper - cela ne fait que déplacer le problème puisque vous devez remplir l'objet wrapper en premier lieu - au lieu de directement dans votre méthode, ce sera vers le constructeur de l'objet paramètre. Déterminer si le déplacement du problème est approprié ou non dépend de la réutilisation dudit objet. Par exemple:

Je ne l'utiliserais pas: il ne serait utilisé qu'une seule fois lors du premier appel, donc beaucoup de code supplémentaire pour traiter 1 ligne ...?

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    SomeObject i = obj2.callAnotherMethod(a, b, c, h);
    FinalResult j = obj3.callAFinalMethod(c, e, f, h, i);
}

Peut l'utiliser: Ici, il peut faire un peu plus. Premièrement, il peut factoriser les paramètres pour 3 appels de méthode. il peut également exécuter 2 autres lignes en lui-même ... donc il devient une variable d'état dans un sens ...

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    e = h.resultOfSomeTransformation();
    SomeObject i = obj2.callAnotherMethod(a, b, c, d, e, f, g);
    f = i.somethingElse();
    FinalResult j = obj3.callAFinalMethod(a, b, c, d, e, f, g, h, i);
}
  1. Modèle de constructeur - c'est un anti-modèle à mon avis. Le mécanisme de gestion des erreurs le plus souhaitable consiste à détecter plus tôt, pas plus tard; mais avec le modèle de générateur, les appels avec des paramètres obligatoires manquants (le programmeur n'a pas pensé à l'inclure) sont déplacés du moment de la compilation au moment de l'exécution. Bien sûr, si le programmeur met intentionnellement null ou tel dans le slot, ce sera le runtime, mais toujours attraper des erreurs plus tôt est un bien plus grand avantage pour les programmeurs qui refusent de regarder les noms de paramètres de la méthode qu'ils appellent. Je trouve cela approprié uniquement lorsqu'il s'agit d'un grand nombre de paramètres optionnels , et même dans ce cas, l'avantage est au mieux marginal. Je suis tout à fait contre le «modèle» du constructeur.

L'autre chose que les gens oublient de considérer est le rôle de l'IDE dans tout cela. Lorsque les méthodes ont des paramètres, les IDE génèrent la plupart du code pour vous, et vous avez des lignes rouges vous rappelant ce que vous devez fournir / définir. Lorsque vous utilisez l'option 3 ... vous perdez complètement cela. C'est maintenant au programmeur de faire les choses correctement, et il n'y a pas d'indices pendant le codage et la compilation ... le programmeur doit le tester pour le savoir.

En outre, les options 2 et 3, si elles sont adoptées largement inutilement, ont des implications négatives à long terme en termes de maintenance en raison de la grande quantité de code dupliqué qu'elles génèrent. Plus il y a de code, plus il y a à maintenir, plus on passe de temps et d'argent pour le maintenir.

John
la source