Injection de dépendance: comment le vendre [fermé]


95

Sachez que je suis un grand partisan de l' injection de dépendance et des tests automatisés. Je pourrais en parler toute la journée.

Contexte

Récemment, notre équipe vient de recevoir ce grand projet qui doit être construit à partir de zéro. C'est une application stratégique avec des besoins métier complexes. Bien sûr, je voulais que ce soit beau et propre, ce qui pour moi voulait dire: maintenable et testable. Je voulais donc utiliser DI.

La résistance

Le problème était dans notre équipe, DI est tabou. Il a été évoqué à quelques reprises, mais les dieux n’approuvent pas. Mais cela ne m'a pas découragé.

Mon mouvement

Cela peut sembler étrange, mais les bibliothèques tierces ne sont généralement pas approuvées par notre équipe d'architectes (pensez: "tu ne parleras pas de Unity , Ninject , NHibernate , Moq ou NUnit , de peur de te couper les doigts"). Ainsi, au lieu d'utiliser un conteneur DI établi, j'ai écrit un conteneur extrêmement simple. En gros, il connecte toutes vos dépendances au démarrage, injecte toutes les dépendances (constructeur / propriété) et supprime les objets jetables à la fin de la requête Web. Il était extrêmement léger et a juste fait ce dont nous avions besoin. Et puis je leur ai demandé de l'examiner.

La réponse

Eh bien, pour faire court. J'ai rencontré une forte résistance. L'argument principal était: "Nous n'avons pas besoin d'ajouter cette couche de complexité à un projet déjà complexe". En outre, "ce n'est pas comme si nous allions brancher différentes implémentations de composants". Et "Nous voulons garder les choses simples, si possible, il suffit de tout mettre dans un assemblage. DI est une complexité sans fin qui ne présente aucun avantage".

Enfin, ma question

Comment gérerais-tu ma situation? Je ne suis pas doué pour présenter mes idées et j'aimerais savoir comment les gens présenteraient leur argument.

Bien sûr, je suppose que comme moi, vous préférez utiliser DI. Si vous n'êtes pas d'accord, dites-moi pourquoi, afin que je puisse voir le revers de la médaille. Il serait vraiment intéressant de voir le point de vue de quelqu'un qui n'est pas d'accord.

Mise à jour

Merci pour toutes les réponses. Cela met vraiment les choses en perspective. C'est assez sympa d'avoir une autre paire d'yeux pour te donner ton feedback, quinze c'est vraiment génial! Ce sont vraiment d'excellentes réponses et m'ont aidé à voir le problème de différents côtés, mais je ne peux choisir qu'une seule réponse, je vais donc choisir celle qui a obtenu le plus haut vote. Merci à tous d'avoir pris le temps de répondre.

J'ai décidé que ce n'était probablement pas le meilleur moment pour mettre en œuvre l'ID, et nous ne sommes pas prêts pour cela. Au lieu de cela, je concentrerai mes efforts sur la possibilité de tester la conception et essaierai de présenter des tests unitaires automatisés. Je suis conscient que la rédaction de tests est une charge supplémentaire, et si jamais on décidait que la charge supplémentaire ne valait pas la peine, personnellement, je la verrais toujours comme une situation gagnante puisque la conception est toujours testable. Et si jamais le test ou DI est un choix à l'avenir, la conception peut facilement le gérer.


64
On dirait qu'il faut
changer de

24
L'utilisation de DI est (du moins en Java, C #, ...) une conséquence presque naturelle de l'utilisation de tests unitaires. Votre entreprise utilise-t-elle des tests unitaires?
sebastiangeiger

27
Ou simplement ne pas focaliser sur DI. DI est différent de "maintenable et testable". Quand il y a différentes opinions sur un projet, vous devez composer et parfois accepter de ne pas décider. Par exemple, j’ai vu des catastrophes amplifiées par l’inversion des commandes, des DI et de multiples couches de frameworks rendant complexes des choses qui devraient être simples (je ne peux pas discuter pour vous).
Denys Séguret

34
"... Je pourrais en parler toute la journée." On dirait que vous devez cesser d’être obsédés par quelque chose qui est maltraité le plus souvent à cause de votre adhésion dogmatique à un papier blanc de la journée.

21
A première vue, vos collègues ont des arguments objectifs contre l'utilisation des conteneurs DI: «nous n'avons pas besoin d'ajouter cette couche de complexité à un projet déjà complexe» - ont-ils peut-être raison? Bénéficiez- vous de la complexité supplémentaire? Si c'est le cas, cela devrait être possible d'argumenter. Ils affirment que "l'ID est une complexité inutile, sans avantage." Si vous pouvez montrer un avantage objectif, vous devriez gagner cela assez facilement, non? Les tests sont assez souvent mentionnés dans les autres commentaires. mais le test n'a pas fondamentalement besoin de DI, sauf si vous avez besoin de simulacres (ce qui n'est pas une donnée).
Konrad Rudolph

Réponses:


62

Prenant quelques contre-arguments:

"Nous voulons que cela reste simple, si possible, il suffit de tout mettre dans un assemblage. DI est une complexité sans précédent qui ne présente aucun avantage".

"ce n'est pas comme si nous allions brancher différentes implémentations de composants".

Ce que vous voulez, c'est que le système soit testable. Pour être facilement testable, vous devez vous moquer des différentes couches du projet (base de données, communications, etc.) et dans ce cas, vous devrez brancher différentes implémentations de composants.

Vendre DI sur les avantages de test qu'il vous donne. Si le projet est complexe, vous aurez besoin de bons tests unitaires.

Un autre avantage est que, lorsque vous codez des interfaces, si vous proposez une meilleure implémentation (plus rapide, moins gourmande en mémoire, etc.) de l'un de vos composants, l'utilisation de DI facilite beaucoup l'échange de l'ancienne implémentation pour la précédente. Nouveau.

Ce que je dis ici, c'est que vous devez aborder les avantages apportés par l'ID plutôt que de plaider en faveur de l'ID pour le bien de l'ID. En amenant les gens à accepter la déclaration:

"Nous avons besoin de X, Y et Z"

Vous déplacez ensuite le problème. Vous devez vous assurer que DI est la réponse à cette affirmation. En agissant de la sorte, vos collègues seront les propriétaires de la solution au lieu de penser que celle-ci leur a été imposée.


+1 Bref et au point. Malheureusement, d'après les arguments avancés par les collègues de Mel, il aura du mal à les convaincre qu'il vaut la peine d'essayer - ce que Spoike a dit dans sa réponse, relève davantage d'un problème de personnel que de problème technique.
Patryk Ćwiek

1
@ Trustme-I'maDoctor - Oh, je suis d'accord pour dire que c'est un problème de personnel, mais avec cette approche, vous montrez les avantages plutôt que de défendre les intérêts de la DI.
ChrisF

C'est vrai et je lui souhaite bonne chance, sa vie en tant que développeur sera plus facile grâce à une testabilité appropriée, etc. :)
Patryk Ćwiek

4
+1 Je pense que votre dernier paragraphe devrait être le premier. C'est au coeur du problème. Si le test unitaire pouvait être poussé et que la conversion de tout le système en modèle DI était suggérée, je lui dirais non également.
Andrew T Finnell

+1 très utile en effet. Je ne dirais pas que DI est un must absolu pour les tests unitaires, mais cela facilite énormément les choses.
Mel

56

D'après ce que vous écrivez, le DI n'est pas tabou en soi, mais l'utilisation de conteneurs DI, ce qui est différent. J'imagine que votre équipe ne rencontrera aucun problème si vous écrivez simplement des composants indépendants qui font en sorte que d'autres composants dont ils ont besoin soient transmis, par exemple par le constructeur. Il suffit de ne pas l'appeler "DI".

Vous pouvez penser que de tels composants, ne pas utiliser un conteneur DI est fastidieux et vous avez raison, mais donnez à votre équipe le temps de le découvrir par eux-mêmes.


10
+1 une solution pragmatique à une impasse politique / dogmatique
k3b

32
Dans tous les cas, il vaut la peine d’envisager de câbler manuellement tous vos objets et d’introduire un contenant DI seulement quand il commence à devenir poilu. Si vous faites cela, ils ne verront pas les conteneurs DI comme une complexité supplémentaire - ils y verront une simplicité accrue.
Phil

2
Le seul problème est que le modèle de passage de dépendances faiblement couplées aux dépendants est DI. IoC, ou l'utilisation d'un "conteneur" pour résoudre les dépendances faiblement couplées, est une ID automatisée .
KeithS

51

Depuis que vous avez demandé, je prendrai le côté de votre équipe contre DI.

L'affaire contre l'injection de dépendance

Il existe une règle que j'appelle «conservation de la complexité» et analogue à la règle de la physique appelée «conservation de l'énergie». Il indique qu'aucune ingénierie ne peut réduire la complexité totale d'une solution optimale à un problème, elle ne peut que déplacer cette complexité. Les produits Apple ne sont pas moins complexes que ceux de Microsoft, ils déplacent simplement la complexité de l'espace utilisateur vers l'espace d'ingénierie. (Il est une analogie imparfaite, bien. Alors que vous ne pouvez pas créer de l' énergie, les ingénieurs ont une mauvaise habitude de créer la complexité à chaque tour. En fait, beaucoup de programmeurs semblent profiter d' ajouter la complexité et l' utilisation de la magie, qui est par définition une complexité que le programmeur ne comprend pas complètement. Cet excès de complexité peut être réduit.) Généralement, ce qui ressemble à de la réduction de la complexité consiste simplement à déplacer la complexité ailleurs, généralement vers une bibliothèque.

De même, DI ne réduit pas la complexité de la liaison d'objets client avec des objets serveur. Ce qu'il fait est de le sortir de Java et de le déplacer en XML. C'est une bonne chose en ce sens qu'il centralise tout dans un (ou plusieurs) fichier (s) XML où il est facile de voir tout ce qui se passe et où il est possible de changer les choses sans recompiler le code. En revanche, c'est mauvais car les fichiers XML ne sont pas Java et ne sont donc pas disponibles pour les outils Java. Vous ne pouvez pas les déboguer dans le débogueur, vous ne pouvez pas utiliser l'analyse statique pour suivre leur hiérarchie d'appels, etc. En particulier, les bogues dans la structure DI deviennent extrêmement difficiles à détecter, identifier et corriger.

Il est donc beaucoup plus difficile de prouver qu'un programme est correct lorsqu'il utilise DI. Beaucoup de gens disent que les programmes utilisant DI sont plus faciles à tester , mais les tests ne prouvent jamais l'absence de bogues . (Notez que le document lié a environ 40 ans et qu'il en est de même de certaines références arcaniques et de certaines pratiques dépassées, mais bon nombre des déclarations de base sont toujours vraies. En particulier, que P <= p ^ n, où P est la probabilité que le système soit exempt de bugs, p est la probabilité qu'un composant du système soit exempt de bugs et n est le nombre de composants. Bien sûr, cette équation est simplifiée à l'extrême, mais elle indique une vérité importante.) La Le crash de NASDAQ lors de l’introduction en bourse de Facebook est un exemple récent dans lequelils ont affirmé avoir passé 1 000 heures à tester le code sans toujours découvrir les bugs à l'origine du crash. 1 000 heures, c'est la moitié d'une année-personne, alors ce n'est pas comme si on lésinait sur les tests.

Certes, pratiquement personne n’a jamais entrepris (et encore moins terminé) la tâche de prouver formellement qu’un code est correct, mais même les tentatives de preuve les plus faibles ont vaincu la plupart des régimes de test pour la recherche de bogues. De fortes tentatives de preuve, par exemple L4.verified , découvrent invariablement une grande quantité de bogues que les tests ne détectent pas et ne pourront probablement jamais découvrir, à moins que le testeur ne connaisse déjà la nature exacte du bogue. Tout ce qui rend le code plus difficile à vérifier lors de l'inspection peut être considéré comme réduisant les chances que des bogues soient détectés , même si cela augmente la capacité de tester.

De plus, le DI n'est pas nécessaire pour les tests . Je l'utilise rarement à cette fin, car je préfère tester le système par rapport au logiciel réel du système plutôt qu'à une version de test alternative. Tout ce que je modifie dans test est (ou pourrait aussi bien être) stocké dans des fichiers de propriétés qui dirigent le programme vers des ressources dans l'environnement de test plutôt que dans la production. Je préférerais de beaucoup passer du temps à configurer un serveur de base de données de test avec une copie de la base de données de production plutôt que de passer du temps à coder une base de données fictive, car cela me permettra de suivre les problèmes liés aux données de production (les problèmes de codage de caractères ne sont qu'un exemple. ) et des bugs d’intégration système ainsi que des bugs dans le reste du code.

L'injection de dépendance n'est également pas requise pour l'inversion de dépendance . Vous pouvez facilement utiliser des méthodes fabriques statiques pour implémenter Dependency Inversion. C'est en fait ce qui s'est passé dans les années 90.

En vérité, bien que j'utilise DI depuis une décennie, les seuls avantages que cette solution offre de convaincante sont la possibilité de configurer des bibliothèques tierces pour utiliser d'autres bibliothèques tierces (par exemple, configurer Spring pour utiliser Hibernate et les deux pour utiliser Log4J. ) et l'activation d'IoC via des proxies. Si vous n'utilisez pas de bibliothèques tierces et n'utilisez pas IoC, cela n'a pas beaucoup de sens et cela ajoute effectivement une complexité injustifiée.


1
Je ne suis pas d'accord, il est possible de réduire la complexité. Je sais parce que je l'ai fait. Bien sûr, certains problèmes / exigences forcent la complexité, mais au-dessus de cela, la majeure partie peut être éliminée avec effort.
Ricky Clarkson

10
@ Ricky, c'est une distinction subtile. Comme je l'ai dit, les ingénieurs peuvent ajouter de la complexité, et cette complexité peut être éliminée. Vous avez donc peut-être réduit la complexité de la mise en œuvre, mais vous ne pouvez pas, par définition, réduire la complexité de la solution optimale à un problème. Le plus souvent, ce qui ressemble à une réduction de la complexité consiste simplement à la déplacer ailleurs (généralement vers une bibliothèque). En tout cas, cela est secondaire à mon argument principal, à savoir que l'ID ne réduit pas la complexité.
Vieux Pro

2
@Lee, la configuration de DI dans le code est encore moins utile. L'un des avantages les plus largement acceptés de DI est la possibilité de modifier l'implémentation du service sans recompiler le code. Si vous configurez DI en code, vous perdez cela.
Vieux Pro

7
@OldPro - La configuration dans le code présente l'avantage de la sécurité de type, et la majorité des dépendances sont statiques et n'ont aucun avantage à être définies en externe.
Lee

3
@Lee Si les dépendances sont statiques, pourquoi ne sont-elles pas codées en dur en premier lieu, sans détour par DI?
Konrad Rudolph

25

Je suis désolé de dire que le problème que vous avez, étant donné ce que vos collègues ont dit dans votre question, est de nature politique plutôt que technique. Il y a deux mantras que vous devriez garder à l'esprit:

  • "C'est toujours un problème de personnes"
  • "Le changement fait peur"

Bien sûr, vous pouvez soulever beaucoup de contre-arguments avec de solides raisons techniques et avantages, mais cela vous mettra dans une mauvaise situation avec le reste de la société. Ils sont déjà convaincus qu'ils n'en veulent pas (parce qu'ils savent comment les choses fonctionnent maintenant). Cela pourrait donc même nuire à votre relation avec vos collègues si vous persistez. Faites-moi confiance car cela m'est arrivé et m'a finalement renvoyé il y a plusieurs années (jeune et naïf que j'étais); C'est une situation dans laquelle vous ne voulez pas vous mettre.

Le fait est que vous devez atteindre un certain niveau de confiance avant que les gens aillent jusqu’à changer leur façon de travailler actuelle. À moins que vous ayez une grande confiance et que vous puissiez décider de ce genre de choses, comme jouer le rôle d'architecte ou de responsable technique, vous ne pouvez pas faire grand-chose pour convaincre vos collègues. Il faudra beaucoup de temps avec la persuasion et les efforts (par exemple, en prenant de petites étapes d'amélioration) pour gagner la confiance afin de faire évoluer la culture de votre entreprise. Nous parlons d'une période de plusieurs mois ou années.

Si vous constatez que la culture d'entreprise actuelle ne fonctionne pas avec vous, vous savez au moins une chose à demander lors de votre prochain entretien d'embauche. ;-)


23

Ne pas utiliser DI. Oublie.

Créez une implémentation du modèle de fabrique GoF qui "crée" ou "trouve" vos dépendances particulières et les transmet à votre objet. Laissez-le ainsi, mais ne l'appelez pas DI et ne créez pas de structure généralisée complexe. Restez simple et pertinent. Assurez-vous que les dépendances et vos classes sont telles qu’un basculement vers DI est possible; mais gardez le maître d'usine et gardez-le extrêmement limité.

Avec le temps, vous pouvez espérer qu’il se développera et même un jour de transition vers l’ID. Laissez les pistes arriver à la conclusion à leur heure et ne poussez pas. Même si vous n'y arrivez jamais, au moins votre code présente certains des avantages du DI, par exemple, le code testable par unité.


3
+1 pour grandir avec le projet et "ne l'appelle pas DI"
Kevin McCormick

5
Je suis d'accord, mais à la fin, c'est DI. Ce n'est tout simplement pas en utilisant un conteneur DI; il transfère le fardeau à l'appelant plutôt que de le centraliser au même endroit. Cependant, il serait judicieux de l'appeler "Externalisation des dépendances" au lieu de DI.
Juan Mendes

5
Il m'est souvent venu à l'esprit que pour vraiment adopter un concept de tour d'ivoire comme celui de DI, il fallait suivre le même processus que les évangélistes pour se rendre compte qu'il y avait un problème à résoudre. Les auteurs l'oublient souvent. Tenez-vous en avec un langage de motif de bas niveau et la nécessité d'un conteneur peut en découler (ou non).
Tom W

@JuanMendes d'après leur réponse, je pense que l'externalisation des dépendances est précisément ce à quoi ils s'opposent car ils n'aiment pas les bibliothèques tierces non plus et veulent tout dans un seul assemblage (monolith?). Si c'est vrai, l'appeler "Externaliser les dépendances" n'est pas mieux que d'appeler DI depuis leur point de vue.
Jonathan Neufeld

22

J'ai vu des projets qui ont poussé DI à l'extrême afin de tester et de simuler des objets. À tel point que la configuration DI est devenue le langage de programmation en soi avec autant de configuration nécessaire pour faire fonctionner un système en tant que code réel.

Le système souffre-t-il actuellement du manque d’API internes appropriées qui ne peuvent pas être testées? Espérez-vous que le passage du système à un modèle DI vous permettrait de mieux tester les tests unitaires? Vous n'avez pas besoin d'un conteneur pour tester votre code à l'unité. L'utilisation d'un modèle DI vous permettrait de le tester à l'unité plus facilement avec des objets simulés, mais ce n'est pas obligatoire.

Vous n'avez pas besoin de basculer tout le système pour qu'il soit compatible DI en même temps. Vous ne devriez pas non plus écrire arbitrairement des tests unitaires. Code du refactor lorsque vous le rencontrez dans vos tickets de fonctionnalité et de bogue. Assurez-vous ensuite que le code que vous avez touché est facilement testable.

Pensez à CodeRot en tant que maladie. Vous ne voulez pas commencer à pirater les choses de façon arbitraire. Vous avez un patient, vous voyez un point sensible, alors vous commencez à réparer le point et les choses qui l'entourent. Une fois que cette zone est propre, vous passez à la suivante. Tôt ou tard, vous aurez touché la majorité du code et cela ne nécessitera pas ce changement architectural "massif".

Je n'aime pas que DI et Mock testing s'excluent mutuellement. Et après avoir vu un projet qui allait, faute de meilleur mot, fou d'utiliser DI, je serais fatigué aussi sans voir le bénéfice.

Il y a des endroits où il est logique d'utiliser DI:

  • La couche d'accès aux données pour échanger des stratégies de journalisation des données
  • Couche d'authentification et d'autorisation.
  • Thèmes pour l'interface utilisateur
  • Prestations de service
  • Points d'extension de plug-in que votre système ou un tiers peut utiliser.

Il est frustrant de demander à chaque développeur de savoir où se trouve le fichier de configuration DI (je sais que les conteneurs peuvent être instanciés via du code, mais pourquoi le feriez-vous?) Quand ils veulent effectuer un RegEx. Oh, je vois qu'il y a IRegExCompiler, DefaultRegExCompiler et SuperAwesomeRegExCompiler. Pourtant, je ne peux effectuer aucune analyse dans le code pour voir où Default est utilisé et SuperAwesome est utilisé.

Ma propre personnalité réagirait mieux si quelqu'un me montrait quelque chose de mieux, puis essayait de me convaincre avec ses mots. Il y a quelque chose à dire sur quelqu'un qui consacrerait du temps et des efforts pour améliorer le système. Je ne trouve pas que beaucoup de développeurs se soucient suffisamment d'améliorer le produit. Si vous pouviez leur demander de véritables changements dans le système et leur montrer comment cela simplifiait et simplifiait les choses, cela vaut plus que toute recherche en ligne.

En résumé, le meilleur moyen de modifier un système consiste à améliorer progressivement le code que vous touchez à chaque passage. Tôt ou tard, vous aurez été capable de transformer le système en quelque chose de mieux.


"DI et Mock testing s’excluent mutuellement" C’est faux, ils ne s’incluent pas non plus. Ils travaillent bien ensemble. Vous indiquez également les endroits que le DI est bon, la couche de service, le dao et l’interface utilisateur - c’est un peu partout, non?
NimChimpsky

@ Andrew points valides. Bien sûr, je ne voudrais pas que cela aille trop loin. Je souhaitais avant tout les tests automatisés pour les entreprises, car nous devons implémenter des règles métier très complexes. Et je me rends compte que je peux toujours les tester sans conteneur, mais personnellement, il est logique d'utiliser un conteneur. J'ai en fait fait un petit échantillon montrant le concept. Mais ce n'était même pas regardé. Je suppose que mon erreur est que je n’ai pas créé de test montrant à quel point il est facile de se tester / se moquer
Mel

Tellement d'accord! Je déteste vraiment les fichiers de configuration DI et leurs effets sur le suivi du déroulement du programme. Le tout se transforme en "action fantasmagorique à distance" sans liens apparents entre les pièces. Quand il devient plus facile de lire du code dans un débogueur que dans des fichiers sources, quelque chose ne va vraiment pas.
Zan Lynx

19

DI ne nécessite pas d'inversion invasive de conteneurs de contrôle ou de cadres moqueurs. Vous pouvez découpler le code et autoriser DI via une conception simple. Vous pouvez effectuer des tests unitaires via les fonctionnalités intégrées de Visual Studio.

Pour être juste, vos collègues ont un point. Une mauvaise utilisation de ces bibliothèques (et comme elles ne les connaissent pas, il y aura des difficultés d'apprentissage) posera des problèmes. En fin de compte, le code est important. Ne bousillez pas votre produit pour vos tests.

Rappelez-vous simplement que le compromis est nécessaire dans ces scénarios.


2
Je conviens que DI n’a pas besoin de conteneurs IoC. Bien que je ne l'appelle pas invasive (cette implémentation au moins) puisque le code a été conçu pour être agnostique pour les conteneurs (principalement des injections de constructeurs) et que tout le contenu des DI se déroule au même endroit. Donc, tout fonctionne toujours sans conteneur DI - je mets des constructeurs sans paramètre qui "injectent" les implémentations concrètes dans l'autre constructeur avec des dépendances. De cette façon, je peux toujours facilement m'en moquer. Alors oui, je suis d’accord, l’ID est en réalité atteint par la conception.
Mel

+1 pour compromis. Si personne ne veut faire de compromis, tout le monde finit par souffrir.
Tjaart

14

Je ne pense pas que vous puissiez plus vendre de DI, car il s’agit d’une décision dogmatique (ou, comme l’a appelé @Spoike plus poliment, politique ).

Dans cette situation, discuter des meilleures pratiques largement acceptées telles que les principes de conception SOLID n’est plus possible.

Quelqu'un qui a plus de pouvoir politique que vous a pris la décision (peut-être fausse) de ne pas utiliser de DI.

De votre côté, vous vous êtes opposé à cette décision en mettant en œuvre votre propre conteneur, car l’utilisation d’un conteneur établi était interdite. Cette politique de faits accomplis que vous avez tentée aurait peut-être aggravé la situation.

Une analyse

À mon avis, les ID sans développement axé sur les tests (TDD) et les tests unitaires ne présentent aucun avantage significatif qui l'emporte sur les coûts supplémentaires initiaux.

Ce problème concerne-t-il vraiment

  • nous ne voulons pas de DI ?

ou est-ce plus sur

  • tdd / unittesting est un gaspillage de ressources ?

[Mise à jour]

Si vous voyez votre projet à partir des erreurs classiques de la vue Gestion de projet , vous voyez

#12: Politics placed over substance.
#21: Inadequate design. 
#22: Shortchanged quality assurance.
#27: Code-like-hell programming.

pendant que les autres voient

#3: Uncontrolled problem employees. 
#30: Developer gold-plating. 
#32: Research-oriented development. 
#34: Overestimated savings from new tools or methods. 

nous ne faisons actuellement aucun test unitaire dans notre équipe. Je soupçonne que votre dernière déclaration est parfaite. J'ai évoqué les tests unitaires à quelques reprises, mais personne ne s'y intéressait vraiment et il y avait toujours des raisons pour lesquelles nous ne pouvions pas l'adopter.
Mel

10

Si vous devez "vendre" un principe à votre équipe, vous êtes probablement déjà à plusieurs kilomètres du mauvais chemin.

Personne ne veut faire un travail supplémentaire, donc si ce que vous essayez de leur vendre leur ressemble à du travail supplémentaire, vous ne les convaincrez pas par Invocation répétée d'argument supérieur. Ils ont besoin de savoir que vous leur montrez un moyen de réduire leur charge de travail et non de l'augmenter.

L’ID est souvent considérée comme une complexité supplémentaire, avec la promesse d’une réduction potentielle de la complexité plus tard , ce qui a de la valeur, mais pas tellement pour une personne qui tente de réduire sa charge de travail initiale. Et dans de nombreux cas, DI n’est qu’une autre couche de YAGNI . Et le coût de cette complexité n’est pas nul, il est en fait assez élevé pour une équipe qui n’est pas habituée à ce type de modèle. Et ce sont de vrais dollars qui sont mis à la pelle dans un seau qui ne donnera jamais aucun bénéfice.

Mettez-le à sa place
Votre meilleur choix est d'utiliser DI si c'est d'une utilité criante, plutôt que là où c'est commun. Par exemple, de nombreuses applications utilisent DI pour sélectionner et configurer des bases de données. Et si votre application est livrée avec une couche de base de données, il est tentant de la mettre. Toutefois, si votre application est livrée avec une base de données intégrée invisible par l'utilisateur qui est par ailleurs étroitement intégrée au code, les chances de devoir remplacer la base de données à l'exécution sont proches de zéro. Et vendre DI en disant: "Regardez, nous pouvons facilement faire cette chose que nous ne voudrons jamais faire", est susceptible de vous placer sur la liste "ce gars a de mauvaises idées" avec votre patron.

Mais disons plutôt que vous avez une sortie de débogage qui obtient normalement IFDEF dans la version. Et chaque fois qu'un utilisateur rencontre un problème, votre équipe de support doit leur envoyer une version de débogage afin qu'ils puissent obtenir la sortie du journal. Vous pouvez utiliser DI ici pour permettre au client de basculer entre les pilotes de journal - LogConsolepour les développeurs, LogNullpour les clients et LogNetworkpour les clients rencontrant des problèmes. Une construction unifiée, configurable à l'exécution.

Dans ce cas, vous n'avez même pas à l'appeler DI, mais simplement "La très bonne idée de Mel" et vous êtes maintenant sur la liste des bonnes idées au lieu de la mauvaise. Les gens verront si le modèle fonctionne correctement et commenceront à l'utiliser là où cela a du sens dans leur code.

Lorsque vous vendez correctement une idée, les gens ne sauront pas que vous les avez convaincus de quoi que ce soit.


8

Bien entendu, l’ inversion de contrôle (IoC) présente un grand nombre d’avantages: elle simplifie le code, ajoute de la magie aux tâches typiques, etc.

Pourtant:

  1. Cela ajoute beaucoup de dépendances. Une simple application hello world écrite avec Spring peut peser une douzaine de Mo. J'ai déjà vu Spring ajouté uniquement pour éviter quelques lignes de constructeurs et de setters.

  2. Il ajoute la magie susmentionnée , qui est difficile à comprendre pour les étrangers (par exemple, les développeurs d'interface graphique qui doivent apporter des modifications à la couche de gestion). Voir l'excellent article Les esprits novateurs ne pensent pas de la même manière - New York Times , qui décrit comment cet effet est sous-estimé par les experts .

  3. Il est difficile de retracer l'utilisation des classes. Les IDE comme Eclipse ont de grandes capacités pour suivre les utilisations de classes données ou d'invocations de méthodes. Mais cette trace est perdue lorsqu'il y a une injection de dépendance et une réflexion invoquées.

  4. L’utilisation d’IoC ne se termine normalement pas avec l’injection de dépendances, mais avec un nombre incalculable de mandataires. Vous obtenez une trace de pile comptant des centaines de lignes, dont 90% sont des mandataires et invoquées par réflexion. Cela complique grandement l'analyse des traces de pile.

Donc, étant moi-même un grand fan de Spring et d'IoC, j'ai récemment dû repenser les questions ci-dessus. Certains de mes collègues sont des développeurs Web issus du monde Web régi par Python et PHP dans le monde Java. Ce qui est évident pour les javaistes est tout ce qui est évident pour eux. Certains de mes collègues font des trucs (bien sûr sans papiers) ce qui rend l'erreur très difficile à trouver. Bien sûr, les abus IoC leur simplifient la vie;)

Mon conseil, si vous imposez l'utilisation d'IoC dans votre travail, vous serez tenu responsable de tout abus commis par quelqu'un d'autre;)


+1, comme avec tout outil puissant, il est facilement mal utilisé et vous DEVEZ comprendre quand NE PAS l'utiliser.
Phil

4

Je pense que ChrisF est dans quelque chose de juste. Ne vendez pas de DI en soi. Mais vendons l'idée de tester le code par unité.

Une approche pour intégrer un conteneur DI dans l’architecture pourrait être de laisser ensuite lentement les tests conduire l’implémentation à un système compatible avec cette architecture. Par exemple, disons que vous travaillez sur un contrôleur dans une application ASP.NET MVC. Dites que vous écrivez la classe comme ceci:

public class UserController {
    public UserController() : this (new UserRepository()) {}

    public UserController(IUserRepository userRepository) {
        ...
    }

    ...
}

Cela vous permet d'écrire des tests unitaires découplés de la mise en œuvre réelle du référentiel d'utilisateurs. Mais la classe fonctionne toujours sans conteneur DI pour le créer pour vous. Le constructeur sans paramètre le créera avec le référentiel par défaut.

Si vos collègues peuvent voir que cela conduit à des tests beaucoup plus propres, ils peuvent peut-être se rendre compte que c'est un meilleur moyen. Peut-être que vous pouvez les amener à commencer à coder comme ça.

Une fois qu'ils ont compris qu'il s'agissait d'une meilleure conception que UserController connaissant à propos d'une implémentation UserRepository spécifique, vous pouvez prendre le déplacement suivant et leur montrer que vous pouvez avoir un composant, le conteneur DI, qui peut automatiquement fournir les instances dont vous avez besoin.


Génial! Je le fais actuellement parce que je veux vraiment les tests unitaires automatisés, même si je ne peux pas obtenir de DI. J'ai oublié de mentionner que les tests unitaires ne sont pas non plus effectués dans notre équipe :( donc si je serai le premier.
Mel

2
Dans ce cas, je dirais que c'est le vrai problème ici. Trouvez la racine de la résistance aux tests unitaires et attaquez-vous. Laissons DI mentir pour le moment. Le test est plus important à mon humble avis.
Christian Horsdal

@ChristianHorsdal Je suis d'accord, je voulais que ce soit couplé de manière lâche pour permettre de se moquer / de tester facilement.
Mel

Cette idée est vraiment cool ... Super vendre pour tester
bunglestink

2

Peut-être que la meilleure chose à faire est de prendre du recul et de vous demander pourquoi vous souhaitez introduire les conteneurs DI. Pour améliorer la testabilité, votre première tâche consiste à amener l'équipe à participer aux tests unitaires. Personnellement, je serais beaucoup plus préoccupé par l'absence de tests unitaires que par l'absence d'un conteneur DI.

Armé d'une ou de plusieurs séries de tests unitaires complets, vous pouvez commencer à faire évoluer votre conception avec le temps. Sans eux, vous devez faire face à une lutte de plus en plus difficile pour maintenir votre conception en phase avec l'évolution des exigences, une lutte que de nombreuses équipes perdent finalement.

Donc, mon conseil serait de défendre d'abord les tests unitaires et la refactorisation, puis d'introduire à leur tour les conteneurs DI et enfin les conteneurs DI.


2

J'entends des gros no-nos de votre équipe ici:

  • "Aucune bibliothèque tierce" - AKA "Non inventé ici" ou "NIH". La crainte est qu’en implémentant une bibliothèque tierce et en rendant ainsi la base de code dépendante de celle-ci, si la bibliothèque avait un bogue, une faille de sécurité ou même une limitation que vous deviez surmonter, vous deviendriez dépendant de cette troisième. partie de réparer leur bibliothèque afin que votre code fonctionne. En attendant, parce que c'est "votre" programme qui a échoué de façon spectaculaire, ou qui a été piraté, ou ne fait pas ce qu'ils ont demandé, vous assumez toujours la responsabilité (et les développeurs tiers diront que leur outil est "tel quel" ", À utiliser à vos risques et périls).

    Le contre-argument est que le code qui résout le problème existe déjà dans la bibliothèque tierce. Construisez-vous vos ordinateurs à partir de zéro? Avez-vous écrit Visual Studio? Est-ce que vous évitez les bibliothèques intégrées dans .NET? Non. Intel / AMD et Microsoft ont un niveau de confiance élevé. Ils trouvent constamment des bogues (et ne les corrigent pas toujours rapidement). L'ajout d'une bibliothèque tierce vous permet de faire fonctionner rapidement une base de code maintenable, sans avoir à réinventer la roue. Et l'armée d'avocats de Microsoft rigolera lorsque vous leur direz qu'un bogue dans les bibliothèques de cryptographie .NET les rend responsables du piratage subi par votre client lors de l'utilisation de votre programme .NET. Alors que Microsoft va certainement sauter pour réparer les failles de sécurité "zéro heure" de ses produits,

  • "Nous voulons tout mettre dans un assemblage" - en fait, vous ne le faites pas. Au moins dans .NET, si vous modifiez une ligne de code, l'assembly entier contenant cette ligne de code doit être reconstruit. Une bonne conception de code repose sur plusieurs assemblys que vous pouvez reconstruire de la manière la plus indépendante possible (ou du moins, vous ne devez reconstruire que des dépendances, et non des dépendants). S'ils veulent VRAIMENT libérer un fichier EXE qui n'est que le fichier EXE (qui a de la valeur dans certaines circonstances), il existe une application pour cela; ILMerge.

  • "DI est tabou" - WTF? DI est une pratique recommandée dans tout langage O / O avec une construction de code "interface". Comment votre équipe adhère-t-elle aux méthodologies de conception GRASP ou SOLID si elles ne couplent pas vaguement les dépendances entre les objets? S'ils ne s'embarrassent pas, ils perpétuent l'usine de spaghettis qui en fait le produit le plus compliqué qui soit. Désormais, DI accroît la complexité en augmentant le nombre d’objets. Bien qu’il facilite la substitution d’une autre implémentation, il est plus difficile de modifier l’interface elle-même (en ajoutant une couche supplémentaire d’objet code qui doit être modifiée).

  • "Nous n'avons pas besoin d'ajouter cette couche de complexité à un projet déjà complexe" - Ils ont peut-être un sens. YAGNI est un élément crucial à prendre en compte dans tout développement de code. "Vous n'en aurez pas besoin". Une conception SOLIDly conçue dès le début est une conception SOLID "sur la foi"; vous ne savez pas si vous aurez besoin de la facilité de changement offerte par les conceptions SOLID, mais vous avez un pressentiment. Bien que vous ayez raison, vous pouvez aussi vous tromper; une conception très SOLIDE pourrait finir par être considérée comme un "code de lasagne" ou un "code de raviole", en ayant tellement de couches ou de morceaux bouchés qu'il devient difficile de faire des changements apparemment triviaux car il faut creuser dans autant de couches et / ou tracer à travers une telle pile d'appels compliquée.

    Je soutiens une règle des trois coups: Faites-le fonctionner, nettoyez-le, rendez-le SOLID. Lorsque vous écrivez une ligne de code pour la première fois, cela doit simplement fonctionner et vous ne recevez pas de points pour les conceptions de tours d'ivoire. Supposons que ce soit un cas unique et faites ce que vous devez faire. La deuxième fois que vous examinez ce code, vous envisagez probablement de le développer ou de le réutiliser, et ce n'est pas le cas isolé que vous pensiez. À ce stade, rendez-le plus élégant et compréhensible en le refacturant; simplifiez la logique compliquée, extrayez le code répété dans des boucles et / ou des appels de méthode, ajoutez quelques commentaires ici et là pour tout kludge que vous devez laisser en place, etc. La troisième fois que vous consultez ce code, c'est probablement une grosse affaire. Maintenant, vous devriez adhérer aux règles SOLID; injecter des dépendances au lieu de les construire, extraire des classes pour contenir des méthodes moins cohérentes avec la "viande" du code,

  • "Ce n'est pas comme si nous allions utiliser différentes implémentations" - Vous avez, monsieur, une équipe qui ne croit pas aux tests unitaires sur vos mains. Je pense qu’il est impossible d’écrire un test unitaire qui fonctionne de manière isolée et qui n’a pas d’effets secondaires, sans pouvoir "se moquer" des objets qui ont ces effets secondaires. Tout programme non-trivial a des effets secondaires; si votre programme lit ou écrit dans une base de données, ouvre ou enregistre des fichiers, communique via un socket réseau ou périphérique, lance un autre programme, dessine des fenêtres, affiche la sortie sur la console ou utilise autrement la mémoire ou des ressources situées en dehors du "bac à sable" de son processus: un effet secondaire. Si cela ne fait rien, que fait-il et pourquoi devrais-je l'acheter?

    Pour être juste, les tests unitaires ne sont pas le seul moyen de prouver que le programme fonctionne; vous pouvez écrire des tests d'intégration, coder des algorithmes mathématiquement éprouvés, etc. Cependant, les tests unitaires montrent très rapidement que, avec les entrées A, B et C, cet élément de code génère le X attendu (ou non), et donc le code fonctionne correctement (ou ne fonctionne pas) dans un cas donné. Des séries de tests unitaires peuvent démontrer avec un degré de confiance élevé que le code fonctionne comme prévu dans tous les cas, et fournissent également des éléments qu'une preuve mathématique ne peut pas; une démonstration que le programme fonctionne encore comme prévu. Une preuve mathématique de l'algorithme ne prouve pas qu'il a été implémenté correctement, pas plus que les modifications apportées l'ont été correctement et ne l'ont pas cassé.

En bref, si cette équipe ne voit aucune valeur dans ce que ferait DI, elle ne la soutiendra pas et tout le monde (du moins tous les seniors) devra faire quelque chose pour un vrai changement. se passer. Ils mettent beaucoup d'emphase sur YAGNI. Vous accordez beaucoup d'importance aux tests SOLID et automatisés. Quelque part au milieu se trouve la vérité.


1

Soyez prudent en ajoutant des fonctionnalités supplémentaires (comme DI) à un projet non approuvé. Si vous avez un plan de projet avec des limites de temps, vous pourriez tomber dans le piège de ne pas avoir assez de temps pour terminer les fonctions approuvées.

Même si vous n'utilisez pas DI actuellement, impliquez-vous dans les tests pour savoir exactement quelles sont les attentes en matière de tests dans votre entreprise. Il y aura un autre projet et vous pourrez confronter les problèmes avec les tests du projet actuel par rapport à DI.

Si vous n'utilisez pas DI, vous pouvez faire preuve de créativité et rendre votre code DI- "prêt". Utilisez un constructeur sans paramètre pour appeler d'autres constructeurs:

MyClassConstructor()
{
    MyClassConstructor(new Dependency)
    Property = New Dependent Property
}

MyClassConstructor(Dependency)
{
    _Dependency = Dependency
}

0

Je pense que l’une de leurs plus grandes préoccupations est au contraire le contraire: DI ne rend pas le projet complexe. Dans la plupart des cas, cela réduit beaucoup de complexité.

Pensez simplement sans DI, qu'allez-vous obtenir l'objet / composant dépendant dont vous avez besoin? Normalement, il faut rechercher dans un référentiel, obtenir un singleton ou s'instancier soi-même.

L'ancien 2 implique un code supplémentaire dans la localisation du composant dépendant. Dans le monde DI, vous n'avez rien fait pour effectuer la recherche. La complexité de vos composants est en fait réduite.

Si vous instanciez vous-même dans certains cas, cela ne serait pas approprié, cela créerait même plus de complexité. Vous devez savoir comment créer correctement l'objet, vous devez savoir quelles valeurs lui permettent d'initialiser l'objet à un état utilisable, ce qui crée une complexité supplémentaire pour votre code.

Ainsi, DI ne rend pas le projet complexe, il le simplifie en simplifiant les composants.

Un autre argument important est que vous n'allez pas brancher une implémentation différente. Oui, il est vrai que dans le code de production, il n’est pas courant d’avoir beaucoup d’implémentations différentes dans le code de production réel. Cependant, il y a un endroit majeur qui nécessite une implémentation différente: Mock / Stub / Test Doubles dans les tests unitaires. Pour que DI fonctionne, dans la plupart des cas, il s’appuie sur un modèle de développement piloté par l’interface, ce qui permet à son tour de réaliser des moqueries / stub dans les tests unitaires. Outre le remplacement de la mise en œuvre, la "conception orientée interface" permet également une conception plus propre et de meilleure qualité. Bien sûr, vous pouvez toujours concevoir avec une interface sans DI. Cependant, les tests unitaires et l’ID vous poussent à adopter de telles pratiques.

À moins que vos "dieux" en compagnie ne pensent que: "un code plus simple", "des tests unitaires", "une modularisation", une "responsabilité unique", etc. sont tous quelque chose contre eux, il ne devrait y avoir aucune raison pour qu'ils refusent d'utiliser DI.


C'est bien en théorie, mais en pratique, l'ajout d'un conteneur DI est une complexité supplémentaire. La deuxième partie de votre argumentation montre pourquoi cela vaut la peine de travailler, mais cela reste du travail.
Schlingel

En fait, à partir de mes expériences passées, DI réduit plutôt que d’ajouter à la complexité. Oui, il a ajouté des éléments supplémentaires au système, ce qui contribue à la complexité. Cependant, avec DI, la complexité des autres composants du système est considérablement réduite. Au final, la complexité est en fait réduite. Pour la deuxième partie, à moins qu’ils ne fassent pas de tests unitaires, ce ne sont PAS des travaux supplémentaires. Sans DI, leurs tests unitaires seront pour la plupart difficiles à écrire et à maintenir. Avec DI, l'effort est considérablement réduit. Donc, il s’agit en réalité de RÉDUCTION du travail (à moins qu’ils ne le soient pas et ne fassent pas confiance aux tests unitaires)
Adrian Shum

Je devrais souligner une chose: dans ma réponse, DI ne signifie aucun cadre de DI existant. C'est juste la méthodologie pour concevoir / développer des composants dans votre application. Vous pouvez en tirer de nombreux avantages si votre code est basé sur une interface, vos composants s'attendent à ce que des dépendances soient injectées au lieu d'être recherchées / créées. Avec une telle conception, même si vous écrivez un "chargeur" ​​spécifique à une application qui effectue le câblage des composants (aucune utilisation générale de DI fw), vous bénéficiez toujours de ce que j'ai mentionné: réduction de la complexité et réduction du travail dans les tests unitaires
Adrian Shum

0

"Nous voulons que cela reste simple, si possible, il suffit de tout mettre dans un assemblage. DI est une complexité sans précédent qui ne présente aucun avantage".

L'avantage est qu'il favorise la conception d'interfaces et permet de se moquer facilement. Ce qui facilite par conséquent les tests unitaires. Les tests unitaires garantissent un code fiable, modulaire et facilement maintenable. Si votre patron maintient toujours que c'est une mauvaise idée, il n'y a rien que vous puissiez faire - c'est la meilleure pratique acceptée de l'industrie contre laquelle ils s'opposent.

Demandez-leur d’essayer d’écrire un test unitaire avec un framework d’ID et une bibliothèque moqueuse, ils vont bientôt apprendre à l’aimer.


Je trouve maintenant utile de doubler ou de tripler le nombre de classes dans votre système. Ceci est similaire au débat qui consiste à essayer d'obtenir une couverture à 100% des tests unitaires en écrivant un test unique par méthode ou en écrivant des tests unitaires significatifs.
Andrew T Finnell

Je ne comprends pas, vous pensez que les tests unitaires sont une bonne idée? Si vous le faites, vous vous moquez aussi des objets? C'est plus facile avec les interfaces et DI. Mais cela semble être ce contre quoi vous vous disputez.
NimChimpsky

C'est vraiment le problème de la poule et de l'œuf. Les interfaces / DI et les tests unitaires sont si étroitement liés qu'il est difficile de faire l'un sans l'autre. Mais j'estime que les tests unitaires sont une solution meilleure et plus facile sur une base de code existante. Refactoriser progressivement l'ancien code kludgy en composants cohérents. Après cela, vous pourriez faire valoir que la création d'objet devrait être faite avec un framework DI au lieu d' newêtre partout et d'écrire des classes d'usine puisque tous ces composants sont en place.
Spoike

0

Travaillez-vous avec ces personnes sur des projets plus petits, ou juste un grand? Il est plus facile de convaincre les gens d'essayer quelque chose de nouveau sur un projet secondaire / tertiaire que le pain perdu. Les raisons principales sont que si cela échoue, le coût de son retrait / remplacement est beaucoup plus petit et que c'est beaucoup moins de travail de le faire partout dans un petit projet. Dans un grand parc existant, vous avez soit un coût initial élevé pour effectuer le changement partout en une fois, soit une période prolongée dans laquelle le code est un mélange de l'ancienne / nouvelle approche qui ajoute des frictions, car vous devez vous rappeler de quelle manière chaque classe est conçue. travailler.


0

Bien que l’une des choses qui favorisent l’ID soit le test unitaire, mais j’estime que cela ajoute une couche de complexité dans certains cas.

  • Mieux vaut avoir une voiture difficile à tester, mais facile à réparer qu'une voiture facile à tester et difficile à réparer.

  • De plus, il me semble que le groupe qui milite pour la DI ne présente ses idées qu’en influençant le fait que certaines approches de conception existantes sont mauvaises.

  • si nous avons une méthode faisant 5 fonctions différentes

    DI-People dit:

    • pas de séparation des préoccupations. [quel est le problème de cela? ils essaient de donner une mauvaise image, ce qui, dans la logique et la programmation, vaut beaucoup mieux savoir ce que chacun fait de près pour mieux éviter les conflits et les attentes erronées et pour voir facilement l’impact du changement de code, car nous ne pouvons pas tout faire pour vous les objets non liés les uns aux autres à 100% ou du moins nous ne pouvons pas le garantir {à moins que les tests de régression soient importants?}]
    • Complexe [Ils veulent réduire la complexité en créant cinq classes supplémentaires chacune pour gérer une fonction et une classe supplémentaire (Container) pour créer la classe pour la fonction requise désormais basée sur des paramètres (XML ou Dictionnaire) en plus de la philosophie "pour avoir une valeur par défaut / ou pas "discussions, ...] puis ils ont déplacé la complexité vers le conteneur et vers la structure, [pour moi, cela ressemble à déplacer la complexité d'un endroit à deux endroits avec toutes les complexités de spécificités linguistiques pour gérer cela." ]

Je pense qu'il est préférable de créer notre propre modèle de conception qui réponde aux besoins de l'entreprise plutôt que d'être influencé par DI.

en faveur de DI, cela peut aider dans certaines situations, par exemple, si vous avez des centaines d'implémentations pour la même interface dépend d'un sélecteur et que ces implémentations sont très différentes en logique, dans ce cas, j'utiliserais des techniques similaires à DI ou DI. mais si nous avons peu d'implémentations pour la même interface, nous n'avons pas besoin de DI.

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.