L'injection de dépendance du pauvre est-elle un bon moyen d'introduire la testabilité dans une application héritée?

14

Au cours de la dernière année, j'ai créé un nouveau système utilisant l'injection de dépendance et un conteneur IOC. Cela m'a beaucoup appris sur DI!

Cependant, même après avoir appris les concepts et les modèles appropriés, je considère qu'il est difficile de découpler le code et d'introduire un conteneur IOC dans une application héritée. L'application est suffisamment grande au point qu'une véritable mise en œuvre serait écrasante. Même si la valeur était comprise et le temps accordé. Qui a le temps de faire quelque chose comme ça ??

Le but est bien sûr d'amener les tests unitaires à la logique métier!
Logique métier étroitement liée aux appels de base de données empêchant les tests.

J'ai lu les articles et je comprends les dangers de l'injection de dépendance du pauvre comme décrit dans cet article de Los Techies . Je comprends que cela ne dissocie vraiment rien.
Je comprends que cela peut impliquer une refactorisation à l'échelle du système car les implémentations nécessitent de nouvelles dépendances. Je n'envisagerais pas de l'utiliser sur un nouveau projet avec une certaine taille.

Question: Est-il correct d'utiliser l'ID de Poor Man pour introduire la testabilité dans une application héritée et démarrer le roulement?

De plus, l'utilisation de l'ID du pauvre comme approche populaire de l'injection de dépendance véritable est-elle un moyen précieux d'éduquer sur le besoin et les avantages du principe?

Pouvez-vous refactoriser une méthode qui a une dépendance d'appel de base de données et abstraire cet appel derrière une interface? Le simple fait d'avoir cette abstraction rendrait cette méthode testable car une implémentation simulée pourrait être transmise via une surcharge du constructeur.

Sur la route, une fois que l'effort aura gagné des partisans, le projet pourrait être mis à jour pour mettre en œuvre un conteneur du CIO et les constructeurs seraient là-bas pour prendre les abstractions.

Airn5475
la source
2
Juste une note. I consider it a challenge to decouple code and introduce an IOC container into a legacy applicationbien sûr que c'est. C'est ce qu'on appelle la dette technique. C'est pourquoi avant toute refonte majeure, il est préférable de refactoriser les petits et continus. Réduire les principaux défauts de conception et passer à l'IoC serait moins difficile.
Laiv

Réponses:

25

La critique de Poor Man's Injection dans NerdDinner a moins à voir avec l'utilisation ou non d'un conteneur DI qu'avec la configuration correcte de vos classes.

Dans l'article, ils déclarent que

public class SearchController : Controller {

    IDinnerRepository dinnerRepository;

    public SearchController() : this(new DinnerRepository()) { }

    public SearchController(IDinnerRepository repository) {
        dinnerRepository = repository;
    }
}

est incorrect car, bien que le premier constructeur fournisse un mécanisme de secours pratique pour construire la classe, il crée également une dépendance étroitement liée à DinnerRepository.

Le bon remède n'est bien sûr pas, comme le suggère Los Techies, d'ajouter un conteneur DI, mais plutôt de supprimer le constructeur incriminé.

public class SearchController : Controller 
{
    IDinnerRepository dinnerRepository;

    public SearchController(IDinnerRepository repository) {
        dinnerRepository = repository;
    }
}

La classe restante a maintenant ses dépendances correctement inversées. Vous êtes maintenant libre d'injecter ces dépendances comme bon vous semble.

Robert Harvey
la source
Merci pour vos pensées! Je comprends le «constructeur incriminé» et comment nous devons l'éviter. Je comprends qu'avoir cette dépendance ne dissocie rien, mais cela permet des tests unitaires. Je suis au début de l'introduction de la DI et de la mise en place des tests unitaires aussi rapidement que possible. J'essaie d'éviter la complication d'un conteneur DI / IOC ou d'une usine si tôt dans le jeu. Comme mentionné, plus tard, lorsque le support de DI augmentera, nous pourrions implémenter un conteneur et supprimer ces "constructeurs incriminés".
Airn5475
Le constructeur par défaut n'est pas requis pour les tests unitaires. Vous pouvez remettre la dépendance que vous aimez à la classe sans elle, y compris un objet factice.
Robert Harvey
2
@ Airn5475 attention que vous n'êtes pas sur comptes rendus analytiques aussi. Ces tests devraient tester des comportements significatifs - tester qu'un an XServicepasse un paramètre à un XRepositoryet retourne ce qu'il récupère n'est pas trop utile à personne et ne vous donne pas non plus beaucoup d'extensibilité. Écrivez vos tests unitaires pour tester un comportement significatif et assurez-vous que vos composants ne sont pas si étroits que vous déplacez réellement toute votre logique vers la racine de votre composition.
Ant P
Je ne comprends pas comment l'inclusion du constructeur incriminé aiderait pour les tests unitaires, cela fait sûrement le contraire? Supprimez le constructeur incriminé pour que la DI soit implémentée correctement et passez une dépendance simulée lors de l'instanciation des objets.
Rob
@Rob: Ce n'est pas le cas. C'est juste un moyen pratique pour le constructeur par défaut de lever un objet valide.
Robert Harvey
16

Vous faites ici une fausse hypothèse sur ce qu'est "l'ID du pauvre".

Créer une classe qui a un constructeur de "raccourci" qui crée toujours le couplage n'est pas l'ID du pauvre.

Ne pas utiliser de conteneur et créer toutes les injections et mappages manuellement est l'ID du pauvre.

Le terme «DI du pauvre» sonne comme une mauvaise chose à faire. Pour cette raison, le terme «DI pur» est encouragé de nos jours car il semble plus positif et décrit en fait plus précisément le processus.

Non seulement il est tout à fait correct d'utiliser DI pauvre / pur pour introduire DI dans une application existante, mais c'est également un moyen valable d'utiliser DI pour de nombreuses nouvelles applications. Et comme vous le dites, tout le monde devrait utiliser la DI pure sur au moins un projet pour vraiment comprendre comment la DI fonctionne, avant de gérer la responsabilité de la «magie» d'un conteneur IoC.

David Arno
la source
8
L'ID "Pauvre" ou "Pur", selon votre préférence, atténue également de nombreux problèmes avec les conteneurs IoC. J'avais l'habitude d'utiliser des conteneurs IoC pour tout, mais plus le temps passe, plus je préfère les éviter complètement.
Ant P
7
@AntP, je suis avec vous: je suis 100% pur DI ces jours-ci et j'évite activement les conteneurs. Mais je (nous) sommes en minorité sur ce point pour autant que je sache.
David Arno
2
Cela semble si triste - alors que je m'éloigne du camp "magique" des conteneurs IoC, des bibliothèques moqueuses, etc. vers le haut. +1 de toute façon.
Ant P
3
@AntP & David: C'est bon d'entendre que je ne suis pas seul dans le camp «Pure DI». Les conteneurs DI ont été créés pour combler les lacunes dans les langues. Ils ajoutent de la valeur dans ce sens. Mais dans mon esprit, la vraie réponse est de corriger les langues (ou de passer à d'autres langues) et non de construire des dépotoirs dessus.
JimmyJames
1

Les changements de Paragidm dans les anciennes équipes / bases de code sont extrêmement risqués:

Chaque fois que vous proposez des "améliorations" au code hérité et qu'il y a des programmeurs "hérités" dans l'équipe, vous dites simplement à tout le monde qu ' "ils ont mal agi" et vous devenez l'ennemi pour le reste de votre / leur temps avec l'entreprise.

L'utilisation d'un DI Framework comme un marteau pour briser tous les clous rendra le code hérité pire que meilleur dans tous les cas. C'est aussi extrêmement risqué personnellement.

Même dans les cas les plus limités, comme juste pour qu'il puisse être utilisé dans des cas de test, ces cas de test ne feront que "non standard" et "étranger" du code qui, au mieux, sera simplement marqué @Ignorequand ils cassent ou pire, se plaignent constamment du les programmeurs hérités ayant le plus d'influence auprès de la direction et se réécrivent "correctement" avec tout ce "temps perdu sur les tests unitaires" imputé uniquement à vous.

Introduire un framework DI, ou même le concept de "Pure DI" dans une application métier, encore moins une énorme base de code héritée sans la gestion, l'équipe et surtout le parrainage du développeur principal ne seront que le glas de la mort pour vous socialement et politiquement au sein de l'équipe / entreprise. Faire des choses comme ça est extrêmement risqué et peut être le pire des suicides politiques.

L'injection de dépendance est une solution à la recherche d'un problème.

Tout langage avec des constructeurs par définition utilise l'injection de dépendances par convention si vous savez ce que vous faites et comprenez comment utiliser correctement un constructeur, c'est juste une bonne conception.

L'injection de dépendance n'est utile qu'à petites doses pour une gamme très étroite de choses:

  • Les choses qui changent beaucoup ou qui ont beaucoup d'implémentations alternatives qui sont liées statiquement.

    • Les pilotes JDBC en sont un parfait exemple.
    • Clients HTTP qui peuvent varier d'une plateforme à l'autre.
    • Systèmes de journalisation qui varient selon la plate-forme.
  • Systèmes de plugins qui ont des plugins configurables qui peuvent être définis dans le code de configuration de votre framework et découverts automatiquement au démarrage et chargés / rechargés dynamiquement pendant l'exécution du programme.


la source
10
L'application tueur pour DI est un test unitaire. Comme vous l'avez souligné, la plupart des logiciels n'auront pas besoin de beaucoup de DI à eux seuls. Mais les tests sont également un client de ce code, nous devons donc concevoir pour la testabilité. En particulier, cela signifie placer des coutures dans notre conception où les tests peuvent observer le comportement. Cela ne nécessite pas un cadre DI sophistiqué, mais cela nécessite que les dépendances pertinentes soient rendues explicites et substituables.
amon
4
Si nous, les développeurs, savions si avancer ce qui pourrait changer, ce serait la moitié de la bataille. J'ai été sur de longs développements où beaucoup de choses soi-disant «fixes» ont changé. La pérennisation ici et là avec DI n'est pas une mauvaise chose. L'utiliser partout tout le temps sent la programmation culte du fret.
Robbie Dee
une complexité excessive du test signifie simplement qu'ils deviennent périmés et marqués ignorés au lieu d'être mis à jour à l'avenir. DI dans le code hérité est une fausse économie. Tout ce qu'ils auront à faire, c'est de faire de vous le "méchant" pour avoir mis tout ce "code non standard, trop complexe que personne ne comprend" dans la base de code. Tu étais prévenu.
4
@JarrodRoberson, c'est vraiment un environnement toxique. L'un des signes clés d'un bon développeur est qu'il revient sur le code qu'il a écrit dans le passé et pense: "Pouah, est-ce que j'ai vraiment écrit ça? C'est tellement faux!" C'est parce qu'ils ont grandi en tant que développeur et ont appris de nouvelles choses. Quelqu'un qui regarde le code qu'il a écrit il y a cinq ans et ne voit rien de mal à cela n'a rien appris au cours de ces cinq années. Donc, si dire aux gens qu'ils ont mal agi dans le passé fait de vous un ennemi, alors fuyez rapidement cette entreprise, car ils sont une équipe sans issue qui vous retiendra et vous réduira à leur niveau médiocre.
David Arno
1
Je ne suis pas sûr d'être d'accord avec les trucs de méchants, mais pour moi, le principal point à retenir est que vous n'avez pas besoin de DI pour faire des tests unitaires. L'implémentation de DI pour rendre le code plus testable ne fait que corriger le problème.
Ant P