L'injection de dépendance en vaut-elle la peine en dehors de UnitTesting

17

Étant donné un constructeur qui n'aura jamais, jamais besoin d'utiliser des implémentations différentes de plusieurs objets qu'il initialise, est-il toujours pratique d'utiliser DI? Après tout, nous pourrions toujours vouloir faire un test unitaire.

La classe en question initialise quelques autres classes dans son constructeur et les classes qu'elle utilise sont assez spécifiques. Il n'utilisera jamais une autre implémentation. Sommes-nous justifiés d'éviter d'essayer de programmer sur une interface?

Seph
la source
3
KISS - est toujours la meilleure approche.
Reactgular
5
À mon avis, cette question est plus spécifique que le double proposé. concevoir-pour-les-futurs-changements-ou-résoudre-le-problème-à-portée , donc je vote pour ne pas fermer
k3b
1
La testabilité n'est-elle pas une raison suffisante?
ConditionRacer
Cela ne ressemble pas vraiment à un doublon autant qu'à une réponse. Si cela ne se produira jamais, jamais, la conception n'est pas conçue pour les changements futurs. Même les astronautes de l'architecture ne conçoivent que des changements qui ne se produiront jamais par mauvais jugement.
Steve314

Réponses:

13

Cela dépend du degré de certitude que "jamais, jamais" est correct (pendant la durée d'utilisation de votre application).

En général:

  • Ne modifiez pas votre conception uniquement pour la testabilité. Les tests unitaires sont là pour vous servir, et non l'inverse.
  • Ne te répète pas. Faire une interface qui n'aura qu'un seul héritier est inutile.
  • Si une classe n'est pas testable, elle n'est probablement pas flexible / extensible pour les cas d'utilisation réels non plus. Parfois, c'est bien - il est peu probable que vous ayez besoin de cette flexibilité, mais la simplicité / fiabilité / maintenabilité / performance / tout ce qui est plus important.
  • Si vous ne le savez pas, codez l'interface. Les exigences changent.
Telastyn
la source
12
Vous avez presque donné -1 pour "ne modifiez pas votre conception uniquement pour la testabilité". Gagner la testabilité est à mon humble avis l'une des principales raisons de changer un design! Mais vos autres points sont ok.
Doc Brown
5
@docBrown - (et d'autres) Je diffère ici de beaucoup, et peut-être même de la majorité. ~ 95% du temps, rendre les choses testables les rend également flexibles ou extensibles. Vous devriez avoir cela dans votre conception (ou l'ajouter à votre conception) la plupart du temps - la testabilité doit être damnée. L'autre ~ 5% a des exigences étranges qui empêchent ce type de flexibilité en faveur d'autre chose, ou est si essentiel / immuable que vous saurez assez rapidement s'il est cassé, même sans tests dédiés.
Telastyn
4
peut être sûr, mais votre première déclaration ci-dessus pourrait être mal interprétée trop facilement car "laissez du code mal conçu tel qu'il est lorsque votre seule préoccupation est la testabilité".
Doc Brown
1
Pourquoi diriez-vous que «créer une interface qui n'aura qu'un seul héritier est inutile.»? L'interface peut fonctionner comme un contrat.
kaptan
2
@kaptan - parce que l'objet concret peut fonctionner aussi bien qu'un contrat. Faire deux signifie simplement que vous devez écrire le code deux fois (et modifier le code deux fois lorsqu'il change). Certes, la grande majorité des interfaces auront plusieurs implémentations (non testées) ou vous devrez vous préparer à cette éventualité. Mais pour ce cas rare où tout ce dont vous avez besoin est un simple objet scellé, utilisez-le directement.
Telastyn
12

Sommes-nous justifiés d'éviter d'essayer de programmer sur une interface?

Bien que l'avantage du codage par rapport à une interface soit assez évident, vous devez vous demander ce qui est exactement gagné en ne le faisant pas.

La question suivante est: fait-il partie de la responsabilité de la classe en question de choisir les implémentations ou non? Cela peut ou non être le cas et vous devez agir en conséquence.

En fin de compte, il est toujours possible qu'une contrainte maladroite sorte de nulle part. Vous souhaiterez peut-être exécuter le même morceau de code simultanément, et la possibilité d'injecter l'implémentation pourrait aider à la synchronisation. Ou après avoir profilé votre application, vous découvrirez peut-être que vous souhaitez une stratégie d'allocation différente de l'instanciation ordinaire. Ou des préoccupations transversales surgissent et vous n'avez pas de support AOP à portée de main. Qui sait?

YAGNI suggère que lors de l'écriture de code, il est important de ne rien ajouter de superflu. L'une des choses que les programmeurs ont tendance à ajouter à leur code sans même s'en rendre compte, ce sont les hypothèses superflues. Comme "cette méthode pourrait être utile" ou "cela ne changera jamais". Les deux ajoutent de l'encombrement à votre conception.

back2dos
la source
2
+1 pour avoir cité YAGNI ici comme argument pour DI, pas contre.
Doc Brown
4
@DocBrown: Considérant que YAGNI peut être utilisé ici, pour justifier également de ne pas utiliser DI. Je pense que c'est plus un argument contre YAGNI étant une mesure utile pour quoi que ce soit.
Steven Evers
1
+1 pour les hypothèses superflues incluses dans YAGNI, qui nous ont frappé à la tête plusieurs fois
Izkata
@SteveEvers: Je pense que l'utilisation de YAGNI pour justifier l'ignorance du principe d'inversion de dépendance empêche un manque de compréhension de YAGNI ou DIP ou peut-être même certains des principes les plus fondamentaux de la programmation et du raisonnement. Vous ne devez ignorer le DIP que si vous en avez besoin - une circonstance dans laquelle YAGNI n'est tout simplement pas applicable par sa nature même.
back2dos
@ back2dos: La supposition que DI est utile en raison de "peut-être des exigences" et "qui sait?" est précisément le courant de pensée que YAGNI est destiné à combattre, au lieu de cela, vous l'utilisez comme justification pour un outillage et une infrastructure supplémentaires.
Steven Evers
7

Cela dépend de divers paramètres mais voici quelques raisons pour lesquelles vous pouvez toujours vouloir utiliser DI:

  • les tests sont une bonne raison en soi, je crois
  • les classes requises par votre objet peuvent être utilisées dans d'autres endroits - si vous les injectez, cela vous permet de mettre en commun les ressources (par exemple si les classes en question sont des threads ou des connexions de base de données - vous ne voulez pas que chacune de vos classes crée de nouvelles Connexions)
  • si l'initialisation de ces autres objets peut échouer, un code plus haut dans la pile sera probablement mieux à même de traiter les problèmes que votre classe qui transmettra alors probablement simplement les erreurs

Bottom line: vous pouvez probablement vivre sans DI dans ce cas, mais il y a des chances que l'utilisation de DI aboutisse à un code plus propre.

assylias
la source
3

Lorsque vous concevez un programme ou une fonctionnalité, une partie du processus de réflexion devrait être de savoir comment vous allez le tester. L'injection de dépendance est l'un des outils de test et doit être considérée comme faisant partie du mélange.

Les avantages supplémentaires de l'injection de dépendances et de l'utilisation d'interfaces au lieu de classes sont également bénéfiques lorsqu'une application est étendue, mais ils peuvent également être adaptés ultérieurement au code source plus facilement et en toute sécurité en utilisant la recherche et le remplacement. - même sur de très gros projets.

Michael Shaw
la source
2
 > Dependency Injection worth it **outside** of UnitTesting?
 > Are we justified in avoiding trying to program to an interface?

De nombreuses réponses à cette question peuvent être débattues comme «vous en aurez peut-être besoin parce que…» comme YAGNI si vous ne voulez pas faire de test.

Si vous demandez quelles raisons, à côté des unités, doivent être programmées sur une interface

Oui , l'injection de dépendance en vaut la peine en dehors de UnitTesting si vous avez besoin d'une inversion de contrôle . Par exemple, si une implémentation d'un module a besoin d'un autre module qui n'est pas accessible dans sa couche.

Exemple: si vous avez les modules gui=> businesslayer=> driver=>common .

Dans ce scénario businesslayerpeut utiliser drivermais driverne peut pas utiliserbusinesslayer .

Si drivervous avez besoin de fonctionnalités de niveau supérieur, vous pouvez implémenter cette fonctionnalité dans businesslayerlaquelle implémente une interface en commoncouche. driveril suffit de connaître l'interface en commoncouche.

k3b
la source
1

Oui, vous êtes - tout d'abord, oubliez les tests unitaires comme une raison de concevoir votre code autour des outils de test unitaire, ce n'est jamais une bonne idée de plier la conception de votre code pour l'adapter à une contrainte artificielle. Si vos outils vous obligent à le faire, procurez-vous de meilleurs outils (par exemple, Microsoft Fakes / Moles qui vous offrent beaucoup plus d'options pour créer des objets fictifs).

Par exemple, diviseriez-vous vos classes en méthodes uniquement publiques simplement parce que les outils de test ne fonctionnent pas avec des méthodes privées? (Je sais que la sagesse qui prévaut est de prétendre que vous n'avez pas besoin de tester des méthodes privées, mais je pense que c'est une réaction à la difficulté de le faire avec les outils actuels, pas une véritable réaction au fait de ne pas avoir besoin de tester des particuliers).,

Dans l'ensemble, cela se résume à quel type de TDDer vous êtes - le "mockist" comme Fowler les décrit , doit changer le code en fonction des outils qu'ils utilisent, tandis que les testeurs "classiques" créent des tests qui sont plus d'intégration dans la nature (c.-à-d. tester la classe en tant qu'unité, pas chaque méthode) donc il y a moins besoin d'interfaces, surtout si vous utilisez les outils qui peuvent se moquer des classes concrètes.

gbjbaanb
la source
3
Je ne pense pas que la sagesse courante selon laquelle les méthodes privées ne devraient pas être testées n'a rien à voir avec la difficulté de les tester. S'il y avait un bogue dans une méthode privée, mais cela n'affectait pas le comportement de l'objet, alors cela n'affecte rien. S'il y avait un bogue dans une méthode privée qui affectait le comportement de l'objet, alors il y a un test échouant ou manquant sur au moins une des méthodes publiques d'un objet.
Michael Shaw
Cela revient à savoir si vous testez le comportement ou l'état. Les méthodes privées doivent être testées d'une manière ou d'une autre, évidemment, mais si vous êtes un TDD classique, vous les testerez en testant la classe "dans son ensemble", les outils de test que la plupart des gens utilisent sont axés sur le test de chaque méthode individuellement ... et mon point est que ces derniers ne testent pas correctement car ils manquent la chance d'effectuer un test complet. Je suis donc d'accord avec vous, tout en essayant de mettre en évidence la façon dont TDD a été renversé par la façon dont certaines personnes (la plupart?) Font des tests unitaires aujourd'hui.
gbjbaanb
1

L' injection de dépendance rend également plus clair que votre objet A dépendances.

Lorsque quelqu'un d'autre va utiliser votre objet naïvement (sans regarder le constructeur), il trouvera qu'il devra configurer des services, des connexions, etc. Ce n'était pas évident car ils n'avaient pas à les transmettre au constructeur . La transmission des dépendances rend plus clair ce qui est nécessaire.

Vous cachez également potentiellement le fait que votre classe peut violer SRP. Si vous passez de nombreuses dépendances (plus de 3), votre classe en fait peut-être trop et doit être refactorisée. Mais si vous les créez dans le constructeur, cette odeur de code sera masquée.

Schleis
la source
0

Je suis d'accord avec les deux camps ici et la question est toujours ouverte à un débat à mon avis. YAGNI exige que je ne m'attaque pas à changer mon code pour l'adapter à la supposition. Les tests unitaires ne sont pas un problème ici parce que je crois fermement que la dépendance ne changera jamais. Mais si j'avais fait des tests unitaires comme prévu au départ, je ne serais jamais arrivé à ce point de toute façon. Comme j'aurais fini par me frayer un chemin dans DI.

Permettez-moi de proposer une autre alternative pour mon scénario en particulier

Avec un modèle d'usine, je peux localiser les dépendances en un seul endroit. Lors des tests, je peux changer l'implémentation en fonction du contexte, et c'est assez bien. Cela ne va pas à l'encontre de YAGNI en raison des avantages d'abstraction de plusieurs constructions de classe.

Seph
la source