quelle devrait être la position de l'enregistreur dans la liste des paramètres [fermé]


12

Dans mon code, j'injecte un enregistreur à plusieurs de mes classes via la liste des paramètres de leur constructeur

J'ai remarqué que je le mettais au hasard: parfois c'est le premier sur la liste, parfois le dernier, et parfois entre les deux

Avez-vous une préférence? Mon intuition dit que la cohérence est utile dans ce cas et ma préférence personnelle est de la mettre en premier afin qu'il soit plus facile de se faire remarquer lorsqu'elle est manquante et plus facile à ignorer lorsqu'elle est là.

Réponses:


31

Les bûcherons sont ce que nous appelons une «préoccupation transversale». Ils cèdent à des techniques telles que la programmation orientée aspect; si vous avez un moyen de décorer vos classes avec un attribut ou d'effectuer un tissage de code, alors c'est un bon moyen d'obtenir des capacités de journalisation tout en gardant vos objets et vos listes de paramètres «purs».

La seule raison pour laquelle vous souhaiterez peut-être transmettre un enregistreur est si vous souhaitez spécifier différentes implémentations de journalisation, mais la plupart des infrastructures de journalisation ont la flexibilité de vous permettre de les configurer, par exemple, pour différentes cibles de journalisation. (fichier journal, gestionnaire d'événements Windows, etc.)

Pour ces raisons, je préfère faire de la journalisation une partie naturelle du système, plutôt que de passer un enregistreur dans chaque classe à des fins de journalisation. Donc, ce que je fais généralement, c'est référencer l'espace de noms de journalisation approprié, et simplement utiliser l'enregistreur dans mes classes.

Si vous voulez toujours passer dans un enregistreur, ma préférence est que vous en fassiez le dernier paramètre de la liste des paramètres (faites-en un paramètre optionnel, si possible). Le faire être le premier paramètre n'a pas beaucoup de sens; le premier paramètre doit être le plus important, le plus pertinent pour le fonctionnement de la classe.


11
+! garder l'enregistreur hors des paramètres du constructeur; je préfère un enregistreur statique donc je sais que tout le monde utilise la même chose
Steven A. Lowe

1
@ StevenA.Lowe Notez cependant que l'utilisation d'enregistreurs statiques peut entraîner d'autres problèmes si vous ne faites pas attention (par exemple, fiasco d'ordre d'initialisation statique en C ++). Je suis d'accord que le fait que l'enregistreur soit une entité accessible à l'échelle mondiale a ses charmes, mais il convient d'évaluer soigneusement si et comment une telle conception s'intègre dans l'architecture globale.
ComicSansMS

@ComicSansMS: bien sûr, plus des problèmes de threading, etc. Juste une préférence personnelle - "aussi simple que possible, mais pas plus simple";)
Steven A. Lowe

Avoir un enregistreur statique peut être un problème. Cela rend l'injection de dépendance plus difficile (à moins que vous ne parliez d'un enregistreur singleton instancié par votre conteneur DI), et si jamais vous souhaitez changer votre architecture, cela peut être douloureux. En ce moment, par exemple, j'utilise des fonctions Azure qui transmettent un enregistreur en tant que paramètre à chaque exécution de fonction, donc je regrette de passer mon enregistreur via le constructeur.
Slothario

@Slothario: C'est la beauté d'un enregistreur statique. Aucune injection d'aucune sorte n'est requise.
Robert Harvey

7

Dans les langages avec surcharge de fonction, je dirais que plus un argument est susceptible d'être optionnel, plus il doit être correct. Cela crée de la cohérence lorsque vous créez des surcharges là où elles manquent:

foo(mandatory);
foo(mandatory, optional);
foo(mandatory, optional, evenMoreOptional);

Dans les langages fonctionnels, l'inverse est plus utile - plus vous êtes susceptible de choisir une valeur par défaut, plus elle devrait être à gauche. Cela facilite la spécialisation de la fonction en lui appliquant simplement des arguments:

addThreeToList = map (+3)

Cependant, comme mentionné dans les autres réponses, vous ne souhaiterez probablement pas passer explicitement l'enregistreur dans la liste d'arguments de chaque classe du système.


3

Vous pouvez certainement passer beaucoup de temps à sur-concevoir ce problème.

Pour les langages avec des implémentations de journalisation canonique, instanciez simplement l'enregistreur canonique directement dans chaque classe.

Pour les langues sans implémentation canonique, essayez de trouver un cadre de façade de journalisation et respectez-le. slf4j est un bon choix en Java.

Personnellement, je préfère m'en tenir à une seule implémentation de journalisation concrète et envoyer tout à syslog. Tous les bons outils d'analyse des journaux sont capables de combiner les journaux sysout de plusieurs serveurs d'applications dans un rapport complet.

Lorsqu'une signature de fonction inclut un ou deux services de dépendances ainsi que des arguments "réels", je place les dépendances en dernier:

int calculateFooBarSum(int foo, int bar, IntegerSummationService svc)

Étant donné que mes systèmes ont tendance à ne comporter que cinq services ou moins, je m'assure toujours que les services sont inclus dans le même ordre sur toutes les signatures de fonction. L'ordre alphabétique est aussi bon que n'importe quel autre. (En plus: le maintien de cette approche méthodologique pour la manipulation des mutex réduira également vos chances de développer des blocages.)

Si vous vous retrouvez à injecter plus d'une douzaine de dépendances dans votre application, le système doit probablement être divisé en sous-systèmes distincts (oserais-je dire des microservices?).


Il me semble gênant d'utiliser l'injection de propriété pour appeler CalculateFooBarSum.
un phu

2

Les enregistreurs sont un peu un cas particulier car ils doivent être disponibles littéralement partout.

Si vous avez décidé de passer un enregistreur dans le constructeur de chaque classe, vous devez définitivement définir une convention cohérente pour la façon dont vous faites cela (par exemple, toujours le premier paramètre, toujours transmis par référence, la liste d'initialisation du constructeur démarre toujours avec m_logger (theLogger), etc.). Tout ce qui sera utilisé dans l'ensemble de votre base de code bénéficiera un jour de la cohérence.

Alternativement, vous pouvez demander à chaque classe d'instancier son propre objet logger, sans avoir besoin de quoi que ce soit à transmettre. Le logger peut avoir besoin de connaître certaines choses "par magie" pour que cela fonctionne, mais coder en dur un chemin de fichier dans la définition de classe est potentiellement beaucoup plus facile à entretenir et moins fastidieux que de le passer correctement à des centaines de classes différentes, et sans doute beaucoup moins mal que d'utiliser une variable globale pour contourner cet ennui. (Certes, les enregistreurs sont parmi les très rares cas d'utilisation légitimes des variables globales)


1

Je suis d'accord avec ceux qui suggèrent que l'enregistreur doit être accessible de manière statique plutôt que transmis aux classes. Cependant, s'il y a une bonne raison pour laquelle vous voulez le transmettre (peut-être que différentes instances veulent se connecter à différents emplacements ou quelque chose), je vous suggère de ne pas le passer en utilisant le constructeur mais plutôt de faire un appel séparé pour le faire, par exemple Class* C = new C(); C->SetLogger(logger);plutôt queClass* C = new C(logger);

La raison de préférer cette méthode est que l'enregistreur n'est pas essentiellement une partie de la classe mais plutôt une fonctionnalité injectée utilisée dans un autre but. Le placer dans la liste des constructeurs en fait une exigence de la classe et implique qu'il fait partie de l'état logique réel de la classe. Il est raisonnable d'attendre, par exemple (avec la plupart des classes mais pas tous), que si X != Yalors , C(X) != C(Y)mais il est peu probable que vous tester l' inégalité de l' enregistreur si vous comparez aussi les instances de la même classe.


1
Bien sûr, cela présente l'inconvénient que l'enregistreur n'est pas disponible pour le constructeur.
Ben Voigt

1
J'aime vraiment cette réponse. Cela fait de la journalisation une préoccupation secondaire de la classe dont vous devez vous soucier séparément de simplement l'utiliser. Les chances sont que si vous ajoutez l'enregistreur au constructeur d'un grand nombre de classes, vous utilisez probablement l'injection de dépendances. Je ne peux pas parler pour tous les langages, mais je sais qu'en C #, certaines implémentations DI injecteront également directement dans les propriétés (getter / setter).
jpmc26

@BenVoigt: C'est vrai, et cela peut être une raison décourageante de ne pas le faire de cette façon mais, généralement, vous pouvez effectuer la journalisation que vous auriez autrement effectuée dans le constructeur en réponse à la définition d'un enregistreur.
Jack Aidley

0

Il convient de mentionner, quelque chose que je n'ai pas vu les autres réponses aborder ici, qu'en faisant l'injection de l'enregistreur via une propriété ou statique, il est difficile (euh) de tester à l'unité la classe. Par exemple, si vous faites injecter votre enregistreur via une propriété, vous devrez maintenant injecter cet enregistreur à chaque fois que vous testez une méthode qui utilise l'enregistreur. Cela signifie que vous pouvez aussi bien le définir comme une dépendance de constructeur car la classe le requiert.

La statique se prête au même problème; si l'enregistreur ne fonctionne pas, alors toute votre classe échoue (si votre classe utilise l'enregistreur) - même si l'enregistreur ne fait pas nécessairement partie de la responsabilité de la classe - bien que ce ne soit pas aussi mauvais que l'injection de propriété parce que vous sachez au moins que l'enregistreur est toujours "là" dans un sens.

Juste un peu de matière à réflexion, surtout si vous utilisez TDD. À mon avis, un enregistreur ne devrait vraiment pas faire partie d'une partie testable d'une classe (lorsque vous testez la classe, vous ne devriez pas également tester votre journalisation).


1
hmmm ... donc vous voulez que votre classe effectue la journalisation (la journalisation devrait être dans la spécification) mais vous ne voulez pas tester en utilisant un enregistreur. Est-ce possible? Je pense que votre point est un non-aller. De toute évidence, si vos outils de test sont cassés, vous ne pouvez pas tester - concevoir de manière à ne pas compter sur un outil de test est un peu trop d'ingénierie IMHO
Hogan

1
Mon point est que si j'utilise un constructeur et que j'appelle une méthode sur une classe et qu'elle échoue toujours parce que je n'ai pas défini de propriété, le concepteur de la classe a mal compris le concept derrière un constructeur. Si un enregistreur est requis par la classe, il doit être injecté dans le constructeur - c'est pour cela que le constructeur est là.
Dan Pantry

euh .. non pas vraiment. Si vous considérez le système de journalisation comme faisant partie du «framework», cela n'a aucun sens en tant que partie du constructeur. Mais cela a été clairement indiqué dans d'autres réponses.
Hogan

1
Je plaide contre l'injection de propriété. Je ne préconise pas nécessairement l'utilisation de l'injection dans le constructeur. Je dis simplement qu'à mon avis, c'est préférable à l'injection de propriété.
Dan Pantry

"Est-ce possible?" aussi, oui, le tissage IL est une chose qui existe et qui a été mentionné dans la réponse du haut ... mono-project.com/docs/tools+libraries/libraries/Mono.Cecil
Dan Pantry

0

Je suis trop paresseux pour transmettre un objet de journalisation à chaque instance de classe. Donc, dans mon code, ce genre de choses se trouve soit dans un champ statique soit dans une variable locale de thread dans un champ statique. Ce dernier est plutôt cool et vous permet d'utiliser un enregistreur différent pour chaque thread et vous permet d'ajouter des méthodes pour activer et désactiver la journalisation qui font quelque chose de significatif et attendu dans une application multi-thread.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.