Iniezione delle dipendenze: come venderlo [chiuso]


95

Fai sapere che sono un grande fan dell'iniezione di dipendenza (DI) e dei test automatizzati. Potrei parlarne tutto il giorno.

sfondo

Di recente, il nostro team ha appena ricevuto questo grande progetto che deve essere costruito da zero. È un'applicazione strategica con requisiti aziendali complessi. Certo, volevo che fosse bello e pulito, il che per me significava: mantenibile e testabile. Quindi volevo usare DI.

Resistenza

Il problema era nel nostro team, DI è un tabù. È stato allevato alcune volte, ma gli dei non approvano. Ma questo non mi ha scoraggiato.

La mia mossa

Questo può sembrare strano ma le librerie di terze parti di solito non sono approvate dal nostro team di architetti (pensa: "non parlerai di Unity , Ninject , NHibernate , Moq o NUnit , per evitare che ti tagli il dito"). Quindi, invece di utilizzare un contenitore DI stabilito, ho scritto un contenitore estremamente semplice. Fondamentalmente ha cablato tutte le tue dipendenze all'avvio, inietta tutte le dipendenze (costruttore / proprietà) e ha eliminato tutti gli oggetti usa e getta alla fine della richiesta web. Era estremamente leggero e faceva proprio quello di cui avevamo bisogno. E poi ho chiesto loro di esaminarlo.

La risposta

Bene, per farla breve. Ho incontrato una forte resistenza. L'argomento principale era "Non è necessario aggiungere questo livello di complessità a un progetto già complesso". Inoltre, "Non è che inseriremo diverse implementazioni di componenti". E "Vogliamo mantenerlo semplice, se possibile semplicemente racchiudere tutto in un unico assieme. DI è una complessità non necessaria senza alcun vantaggio".

Infine, la mia domanda

Come gestiresti la mia situazione? Non sono bravo a presentare le mie idee e vorrei sapere come le persone avrebbero presentato le loro argomentazioni.

Certo, presumo che, come me, tu preferisca usare DI. Se non sei d'accordo, per favore dì perché così posso vedere l'altro lato della medaglia. Sarebbe davvero interessante vedere il punto di vista di qualcuno che non è d'accordo.

Aggiornare

Grazie per le risposte di tutti. Mette davvero le cose in prospettiva. È abbastanza carino avere un altro paio di occhi per darti un feedback, quindici è davvero fantastico! Queste sono risposte davvero fantastiche e mi hanno aiutato a vedere il problema da diverse parti, ma posso scegliere solo una risposta, quindi sceglierò solo la prima votata. Grazie a tutti per il tempo dedicato a rispondere.

Ho deciso che probabilmente non è il momento migliore per implementare DI, e non siamo pronti per questo. Invece, concentrerò i miei sforzi nel rendere testabile il progetto e tenterò di presentare test di unità automatizzati. Sono consapevole che scrivere test è un sovraccarico aggiuntivo e se mai si decide che l'overhead aggiuntivo non ne vale la pena, personalmente lo vedrei ancora come una situazione vincente poiché il design è ancora testabile. E se mai testare o DI è una scelta in futuro, il design può facilmente gestirlo.


64
Sembra che tu debba trasferirti in un'altra compagnia ...
Sardathrion,

24
L'uso di DI è (almeno in Java, C #, ...) quasi una conseguenza naturale dell'utilizzo di unit test. La tua azienda utilizza test unitari?
sebastiangeiger,

27
O semplicemente non focalizzare su DI. DI è diverso da "gestibile e testabile". Quando ci sono opinioni diverse su un progetto, devi comporre e talvolta accettare di non decidere. Per esempio, ho visto i disastri amplificati dall'inversione di controlli, DI e più livelli di framework rendendo le cose complesse che dovrebbero essere semplici (non posso discutere nel tuo caso).
Denys Séguret,

34
"... Potrei parlarne tutto il giorno." Sembra che tu abbia bisogno di smettere di essere ossessionato da qualcosa che viene abusato più spesso che non a causa dell'adesione dogmatica a qualche libro bianco del giorno.

21
Dal suono di ciò, i tuoi colleghi hanno argomentazioni obiettive che parlano contro l'uso di contenitori DI: "non abbiamo bisogno di aggiungere questo livello di complessità a un progetto già complesso" - forse hanno ragione? Approfitti in effetti della complessità aggiunta? In tal caso, dovrebbe essere possibile discutere. Affermano che "DI è una complessità non necessaria senza alcun vantaggio". Se puoi mostrare un vantaggio oggettivo, dovresti vincere abbastanza facilmente, no? Il test è menzionato abbastanza spesso negli altri commenti; ma i test non hanno fondamentalmente bisogno di DI, a meno che tu non abbia bisogno di beffe (che non è un dato).
Konrad Rudolph,

Risposte:


62

Prendendo un paio di contro argomenti:

"Vogliamo mantenerlo semplice, se possibile semplicemente racchiudere tutto in un unico assieme. DI è una complessità non necessaria senza alcun vantaggio".

"non è che collegheremo diverse implementazioni di componenti".

Quello che vuoi è che il sistema sia testabile. Per essere facilmente verificabile è necessario essere guardando beffardo i vari strati del progetto (database, comunicazioni, ecc) e in questo caso si dovrà essere collegare le diverse implementazioni di componenti.

Vendi DI sui vantaggi del test che ti dà. Se il progetto è complesso, avrai bisogno di test unitari validi e solidi.

Un altro vantaggio è che, mentre si codifica per le interfacce, se si ottiene un'implementazione migliore (più veloce, meno memoria, qualunque cosa) di uno dei componenti, l'utilizzo di DI semplifica notevolmente lo scambio della vecchia implementazione con nuovo.

Quello che sto dicendo qui è che devi affrontare i benefici che DI porta piuttosto che discutere di DI per il bene di DI. Facendo in modo che le persone accettino l'affermazione:

"Abbiamo bisogno di X, Y e Z"

Quindi si sposta il problema. Devi assicurarti che DI sia la risposta a questa affermazione. In questo modo i tuoi collaboratori possederanno la soluzione piuttosto che ritenere che sia stata loro imposta.


+1. Breve e al punto. Sfortunatamente, dalle argomentazioni avanzate dai colleghi di Mel, avrà difficoltà a convincerli che vale la pena provare anche solo - ciò che Spoike ha detto nella sua risposta, più un problema di persone che un problema tecnico.
Patryk iewiek,

1
@ Trustme-I'maDoctor - Oh, sono d'accordo che si tratta di un problema di persone, ma con questo approccio stai mostrando i vantaggi piuttosto che litigare per DI per se stesso.
ChrisF

Vero e gli auguro buona fortuna, la sua vita come sviluppatore sarà più facile con una corretta testabilità ecc. :)
Patryk Ćwiek,

4
+1 Penso che il tuo ultimo paragrafo dovrebbe essere il primo. Questo è fondamentale per il problema. Se potessi spingere in Unit Test e suggerire di convertire l'intero sistema in un modello DI, direi di no anche a lui.
Andrew T Finnell,

+1 davvero molto utile. Non direi che DI è un must assoluto per i test unitari, ma rende le cose molto più facili.
Mel

56

Da quello che stai scrivendo, DI non è di per sé un tabù, ma l'uso di contenitori DI, che è una cosa diversa. Suppongo che non avrai problemi con il tuo team quando scrivi semplicemente componenti indipendenti che ottengono altri componenti di cui hanno bisogno, ad esempio, dal costruttore. Basta non chiamarlo "DI".

Potresti pensare che tali componenti non usare un contenitore DI sia noioso e hai ragione, ma dai al tuo team il tempo di scoprirlo da solo.


10
+1 a una soluzione pragmatica a un punto morto politico / dogmatico
k3b,

32
Vale la pena considerare di cablare tutti gli oggetti manualmente in ogni caso e introdurre un contenitore DI solo quando inizia a diventare peloso. Se lo fai, non vedranno i contenitori DI come ulteriore complessità, ma vedranno che aggiungono semplicità.
Phil

2
L'unico problema è che il modello di passaggio di dipendenze vagamente accoppiate in dipendenti è DI. L'IoC, o l'uso di un "contenitore" per risolvere dipendenze vagamente accoppiate, è DI automatizzato .
KeithS,

51

Da quando me l'hai chiesto, affronterò la squadra contro DI.

Il caso contro l'iniezione di dipendenza

Esiste una regola che io chiamo "Conservazione della complessità" che è analoga alla regola della fisica chiamata "Conservazione dell'energia". Afferma che nessuna quantità di ingegneria può ridurre la complessità totale di una soluzione ottimale a un problema, tutto ciò che può fare è spostare quella complessità. I prodotti Apple non sono meno complessi di quelli di Microsoft, spostano semplicemente la complessità dallo spazio dell'utente allo spazio tecnico. (È un'analogia imperfetta, sebbene. Sebbene non sia possibile creare energia, gli ingegneri hanno la cattiva abitudine di creare complessità ad ogni turno. In effetti, molti programmatori sembrano divertirsi aggiungendo complessità e usando la magia, che è per definizione complessità che il programmatore non comprende completamente. Questa complessità in eccesso può essere ridotta.) Solitamente ciò che sembra ridurre la complessità è semplicemente spostare la complessità da qualche altra parte, di solito in una libreria.

Allo stesso modo, DI non riduce la complessità del collegamento di oggetti client con oggetti server. Quello che fa è estrarlo da Java e spostarlo in XML. È positivo in quanto centralizza tutto in uno (o pochi) file XML in cui è facile vedere tutto quello che sta succedendo e dove è possibile cambiare le cose senza ricompilare il codice. D'altra parte, non va bene perché i file XML non sono Java e quindi non sono disponibili per gli strumenti Java. Non è possibile eseguirne il debug nel debugger, non è possibile utilizzare l'analisi statica per rintracciare la gerarchia delle chiamate e così via. In particolare, i bug nel framework DI diventano estremamente difficili da rilevare, identificare e correggere.

È quindi molto più difficile dimostrare che un programma è corretto quando si usa DI. Molte persone sostengono che i programmi che utilizzano DI sono più facili da testare , ma i test dei programmi non possono mai dimostrare l'assenza di bug . (Si noti che il documento collegato ha circa 40 anni e quindi ha alcuni riferimenti arcani e cita alcune pratiche obsolete, ma molte delle affermazioni di base sono ancora vere. In particolare, P <= p ^ n, dove P è la probabilità che il sistema sia privo di bug, p è la probabilità che un componente del sistema sia privo di bug e n sia il numero di componenti. Naturalmente, questa equazione è semplificata eccessivamente, ma indica un'importante verità.) Un crash del NASDAQ durante l'IPO di Facebook è un esempio recente, dovehanno affermato di aver trascorso 1.000 ore a testare il codice e non hanno ancora scoperto i bug che hanno causato l'incidente. 1.000 ore sono la metà di una persona all'anno, quindi non è come se stessero risparmiando sui test.

È vero, quasi nessuno si impegna (per non dire a completare) il compito di dimostrare formalmente che qualsiasi codice è corretto, ma anche i deboli tentativi di prova hanno battuto la maggior parte dei regimi di test per trovare bug. Forti tentativi di dimostrazione, ad esempio L4.verificato , rivelano invariabilmente una grande quantità di bug che i test non hanno riscontrato e probabilmente non avrebbero mai scoperto a meno che il tester non conoscesse già la natura esatta del bug. Tutto ciò che rende il codice più difficile da verificare mediante ispezione può essere visto come una riduzione della possibilità che i bug vengano rilevati , anche se aumenta la capacità di testare.

Inoltre DI non è richiesto per i test . Lo uso raramente a tale scopo, poiché preferisco testare il sistema con il software reale nel sistema piuttosto che una versione di test alternativa. Tutto ciò che cambio in test viene (o potrebbe anche essere) memorizzato in file di proprietà che indirizzano il programma verso risorse nell'ambiente di test piuttosto che in produzione. Preferirei spendere molto tempo a configurare un server di database di prova con una copia del database di produzione piuttosto che passare il tempo a codificare un database fittizio, perché in questo modo sarò in grado di monitorare i problemi con i dati di produzione (i problemi di codifica dei caratteri sono solo un esempio ) e bug di integrazione del sistema e bug nel resto del codice.

L'iniezione di dipendenza non è richiesta anche per Inversione di dipendenza . È possibile utilizzare facilmente metodi statici di fabbrica per implementare l'inversione delle dipendenze. È così, infatti, come è stato fatto negli anni '90.

In verità, sebbene utilizzi DI da un decennio, gli unici vantaggi che mi risulta convincente sono la possibilità di configurare librerie di terze parti per utilizzare altre librerie di terze parti (ad esempio impostare Spring per usare Hibernate ed entrambi per usare Log4J ) e l'abilitazione dell'IoC tramite proxy. Se non si utilizzano librerie di terze parti e non si utilizza l'IoC, non ha molto senso e aggiunge complessità ingiustificata.


1
Non sono d'accordo, è possibile ridurre la complessità. Lo so perché l'ho fatto. Naturalmente, alcuni problemi / requisiti impongono la complessità, ma soprattutto la maggior parte può essere rimossa con uno sforzo.
Ricky Clarkson,

10
@ Rick, è una sottile distinzione. Come ho detto, gli ingegneri possono aggiungere complessità e tale complessità può essere eliminata, quindi potresti aver ridotto la complessità dell'implementazione, ma per definizione non puoi ridurre la complessità della soluzione ottimale a un problema. Molto spesso ciò che sembra ridurre la complessità è semplicemente spostarlo altrove (di solito in una libreria). In ogni caso è secondario al mio punto principale che è che DI non riduce la complessità.
Old Pro,

2
@Lee, la configurazione di DI nel codice è ancora meno utile. Uno dei vantaggi più ampiamente accettati di DI è la possibilità di modificare l'implementazione del servizio senza ricompilare il codice e, se si configura DI in codice, lo si perde.
Old Pro,

7
@OldPro - La configurazione nel codice ha il vantaggio della sicurezza del tipo e la maggior parte delle dipendenze è statica e non ha alcun vantaggio dalla definizione esterna.
Lee,

3
@Lee Se le dipendenze sono statiche, perché non sono in primo luogo codificate, senza la deviazione tramite DI?
Konrad Rudolph,

25

Mi dispiace dire che il problema che hai, dato quello che i tuoi colleghi hanno detto nella tua domanda, è di natura politica piuttosto che tecnica. Ci sono due mantra che dovresti tenere a mente:

  • "È sempre un problema di persone"
  • "Il cambiamento fa paura"

Certo, puoi sollevare molte contro argomentazioni con solide ragioni e vantaggi tecnici, ma questo ti metterà in una brutta situazione con il resto dell'azienda. Hanno già una posizione che non lo vogliono (perché sono a proprio agio con come funzionano le cose ora). Quindi potrebbe anche danneggiare la tua relazione con i tuoi colleghi se continui a essere persistente al riguardo. Fidati di me perché è successo a me e alla fine mi hanno licenziato diversi anni fa (giovane e ingenuo che fossi); questa è una situazione in cui non vuoi entrare.

Il fatto è che devi raggiungere un certo livello di fiducia prima che le persone arrivino al punto di cambiare il loro attuale modo di lavorare. A meno che tu non sia in una posizione in cui hai molta fiducia o non puoi decidere questo tipo di cose, come avere il ruolo di architetto o di capo tecnico, c'è molto poco che puoi fare per cambiare i tuoi colleghi. Ci vorrà molto tempo con persuasione e sforzo (come nel prendere piccoli passi di miglioramento) per guadagnare fiducia per far cambiare la cultura della tua azienda. Stiamo parlando di un periodo di molti mesi o anni.

Se scopri che l'attuale cultura aziendale non funziona con te, almeno conosci un'altra cosa da chiedere al tuo prossimo colloquio di lavoro. ;-)


23

Non usare DI. Dimenticalo.

Crea un'implementazione del modello di fabbrica GoF che "crea" o "trova" le tue dipendenze particolari e le passa al tuo oggetto e lascialo a quello, ma non chiamarlo DI e non creare un quadro generalizzato complesso. Mantenerlo semplice e pertinente. Assicurati che le dipendenze e le tue classi siano tali da consentire un passaggio a DI; ma mantenere la fabbrica padrone e mantenerla estremamente limitata.

Col tempo, puoi sperare che cresca e anche un giorno di transizione a DI. Lascia che i lead arrivino alla conclusione nel loro tempo libero e non spingere. Anche se non ci riesci mai, almeno il tuo codice presenta alcuni dei vantaggi di DI, ad esempio il codice testabile dell'unità.


3
+1 per crescere insieme al progetto e "non chiamarlo DI"
Kevin McCormick,

5
Sono d'accordo, ma alla fine è DI. Non sta semplicemente usando un contenitore DI; sta passando l'onere al chiamante piuttosto che centralizzarlo in un unico posto. Tuttavia, sarebbe intelligente chiamarlo "Esternalizzazione delle dipendenze" anziché DI.
Juan Mendes,

5
Mi viene spesso in mente che per ingannare davvero un concetto di torre d'avorio come DI, devi aver attraversato lo stesso processo degli evangelisti per rendersi conto che in primo luogo c'era un problema da risolvere. Gli autori spesso lo dimenticano. Attenersi a un linguaggio di pattern di basso livello e la necessità di un contenitore potrebbe (o meno) emergere di conseguenza.
Tom W,

@JuanMendes dalla loro risposta Sto pensando che l'esternalizzazione delle dipendenze è specificamente ciò a cui si oppongono perché non amano neanche le librerie di terze parti e vogliono tutto in un unico assembly (monolite?). Se questo è vero, chiamarlo "Esternalizzare le dipendenze" non è meglio che chiamare DI dal loro punto di vista.
Jonathan Neufeld,

22

Ho visto progetti che hanno portato DI all'estremo per testare l'unità e deridere oggetti. Tanto che la configurazione DI è diventata il linguaggio di programmazione in sé con la stessa configurazione necessaria per far funzionare un sistema come codice reale.

Il sistema soffre in questo momento a causa della mancanza di API interne appropriate che non possono essere testate? Speri che passare dal sistema a un modello DI ti consenta di testare meglio le unità? Non è necessario un contenitore per testare l'unità del codice. L'uso di un modello DI ti consentirebbe di testare l'unità più facilmente con oggetti Mock, ma non è necessario.

Non è necessario cambiare l'intero sistema per renderlo compatibile con DI contemporaneamente. Non dovresti nemmeno scrivere arbitrariamente test unitari. Codice del refactor quando lo trovi nella tua funzione e biglietti funzionanti con bug. Quindi assicurati che il codice che hai toccato sia facilmente testabile.

Pensa a CodeRot come a una malattia. Non vuoi iniziare a hackerare arbitrariamente le cose. Hai un paziente, vedi un punto dolente, quindi inizi a riparare il punto e le cose intorno ad esso. Una volta che l'area è pulita, si passa al successivo. Prima o poi avrai toccato la maggior parte del codice e non è stato necessario questo "massiccio" cambiamento architettonico.

Non mi piace il fatto che i test DI e Mock si escludano a vicenda. E dopo aver visto un progetto che è andato, per mancanza di una parola migliore, impazzito con DI, sarei stanco anche senza vedere il beneficio.

Ci sono alcuni posti che hanno senso usare DI:

  • Il livello di accesso ai dati per scambiare strategie per i dati di storying
  • Livello di autenticazione e autorizzazione.
  • Temi per l'interfaccia utente
  • Servizi
  • Punti di estensione del plug-in che possono essere utilizzati dal proprio sistema o da terze parti.

Richiedere a ciascuno sviluppatore di sapere dove si trova il file di configurazione DI (mi rendo conto che i contenitori possono essere istanziati tramite il codice, ma perché dovresti farlo.) Quando vogliono eseguire un RegEx è frustrante. Oh, vedo che c'è IRegExCompiler, DefaultRegExCompiler e SuperAwesomeRegExCompiler. Tuttavia, nessuna parte del codice è in grado di eseguire analisi per vedere dove viene utilizzato Default e SuperAwesome.

La mia personalità reagirebbe meglio se qualcuno mi mostrasse qualcosa di meglio, quindi provando a convincermi con le sue parole. C'è qualcosa da dire su qualcuno che impiegherebbe tempo e sforzi per migliorare il sistema. Non trovo che molti sviluppatori si preoccupino abbastanza per rendere il prodotto davvero migliore. Se potessi andare da loro con cambiamenti reali nel sistema e mostrare loro come lo rendeva migliore e più facile per te, vale più di ogni ricerca online.

In sintesi, il modo migliore per effettuare la modifica in un sistema è rendere lentamente il codice che si sta toccando meglio ad ogni passaggio. Prima o poi sarai riuscito a trasformare il sistema in qualcosa di meglio.


"I test DI e Mock si escludono a vicenda" Questo è falso, non si escludono a vicenda. Funzionano bene insieme. Elencate anche i luoghi in cui DI è buono, livello di servizio, dao e interfaccia utente - è quasi ovunque, vero?
NimChimpsky,

@Andrew punti validi. Certo, non vorrei che andasse troppo lontano. Volevo maggiormente i test automatizzati per le business class, poiché dobbiamo implementare regole aziendali molto complesse. E mi rendo conto che posso ancora testarli senza contenitore, ma personalmente, ha senso usare un contenitore. In realtà ho fatto un piccolo esempio che mostrava il concetto. Ma non è stato nemmeno guardato. Immagino che il mio errore sia stato non aver creato un test che dimostrasse quanto sia facile il test / derisione.
Mel

Tanto accordo! Odio davvero i file di configurazione DI e l'effetto che ha sul monitoraggio del flusso del programma. L'intera cosa si trasforma in "azione spettrale a distanza" senza connessioni apparenti tra i pezzi. Quando diventa più facile leggere il codice in un debugger che nei file sorgente, qualcosa è veramente sbagliato.
Zan Lynx,

19

DI non richiede l'inversione invasiva di contenitori di controllo o quadri di derisione. È possibile disaccoppiare il codice e consentire DI tramite un design semplice. È possibile eseguire test di unità tramite le funzionalità integrate di Visual Studio.

Ad essere onesti, i tuoi colleghi hanno ragione. L'uso scorretto di tali librerie (e poiché non hanno familiarità con esse, ci saranno problemi di apprendimento) causerà problemi. Alla fine, il codice conta. Non rovinare il prodotto per i test.

Basta ricordare che il compromesso è necessario in questi scenari.


2
Sono d'accordo che DI non richiede contenitori IoC. anche se non lo definirei invasivo (almeno questa implementazione) poiché il codice è stato progettato per essere indipendente dal contenitore (principalmente iniezioni di costruttori) e tutte le cose DI avvengono in un unico posto. Quindi funziona ancora senza un contenitore DI: inserisco costruttori senza parametri che "iniettano" le dipendenze dalle implementazioni concrete all'altro costruttore. In questo modo posso ancora facilmente deriderlo. Quindi sì, sono d'accordo, DI è effettivamente realizzato dal design.
Mel

+1 per compromesso. Se nessuno vuole scendere a compromessi, tutti finiscono per soffrire.
Tjaart,

14

Non penso che tu possa più vendere DI, perché questa è una decisione dogmatica (o, come @Spoike lo ha definito più educatamente, politico ).

In questa situazione non è più possibile discutere con le migliori pratiche ampiamente accettate come i principi di progettazione SOLID .

Qualcuno che ha più potere politico di te ha preso una decisione (forse sbagliata) di non usare DI.

Dall'altro lato, vi siete opposti a questa decisione implementando il proprio contenitore perché era proibito l'uso di un contenitore stabilito. Questa politica di fatti compiuti che hai provato potrebbe aver peggiorato la situazione.

Analisi

A mio avviso, DI senza sviluppo testdriven (TDD) e unit test non ha alcun vantaggio significativo che superi i costi aggiuntivi iniziali.

È davvero questo problema

  • non vogliamo DI ?

o si tratta più di questo

  • tdd / unittesting è uno spreco di risorse ?

[Aggiornare]

Se vedi il tuo progetto dai classici errori nella vista di gestione del progetto , vedi

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

mentre gli altri vedono

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

al momento non stiamo eseguendo test unitari nel nostro team. Sospetto che la tua ultima affermazione sia corretta. Ho sollevato alcune volte i test unitari, ma nessuno ha mostrato alcun interesse reale e ci sono sempre stati motivi per cui non possiamo adottarlo.
Mel

10

Se ti ritrovi a dover "vendere" un principio alla tua squadra, probabilmente sei già a diverse miglia lungo la strada sbagliata.

Nessuno vuole fare un lavoro extra, quindi se quello che stai cercando di vendergli sembra un lavoro extra, allora non li convincerai con Invocazione ripetuta dell'argomento superiore. Devono sapere che stai mostrando loro un modo per ridurre il loro carico di lavoro, non aumentarlo.

DI è spesso visto come ulteriore complessità ora con la promessa di una potenziale complessità ridotta in seguito , il che ha valore, ma non tanto per qualcuno che sta cercando di ridurre il loro carico di lavoro iniziale. E in molti casi, DI è solo un altro livello di YAGNI . E il costo di questa complessità non è zero, in realtà è piuttosto alto per una squadra che non è abituata a questo tipo di schema. E questi sono dollari reali che vengono spalati in un secchio che potrebbe non produrre alcun beneficio.

Mettilo al suo posto La
tua migliore scommessa è usare DI dove è palesemente utile, piuttosto che dove è comune. Ad esempio, molte app usano DI per selezionare e configurare i database. E se la tua app viene fornita con un livello di database, questo è un posto allettante per dirla. Ma se la tua app viene fornita con un database invisibile per l'utente incorporato che è altrimenti strettamente integrato nel codice, le possibilità di sostituire il DB in fase di runtime si avvicinano allo zero. E vendendo DI dicendo "guarda, possiamo facilmente fare questa cosa che non vorremmo mai e poi mai fare", è probabile che ti porti sulla lista "questo ragazzo ha cattive idee" con il tuo capo.

Supponiamo invece che tu abbia un output di debug che normalmente viene rilasciato in uscita da IFDEF. E ogni volta che un utente ha un problema, il team di supporto deve inviare loro una build di debug in modo che possano ottenere l'output del registro. È possibile utilizzare DI qui per consentire al cliente di alternare tra i driver di registro - LogConsoleper gli sviluppatori, LogNullper i clienti e LogNetworkper i clienti con problemi. Una build unificata, runtime configurabile.

Quindi non devi nemmeno chiamarlo DI, invece si chiama semplicemente "la buona idea di Mel" e ora sei nella lista delle buone idee invece di quella cattiva. Le persone vedranno quanto bene funziona il modello e inizieranno ad usarlo dove ha senso nel loro codice.

Quando vendi correttamente un'idea, le persone non sapranno che li hai convinti di qualcosa.


8

Naturalmente, Inversion of control (IoC) offre numerosi vantaggi: semplifica il codice, aggiunge un po 'di magia a svolgere i compiti tipici, ecc.

Però:

  1. Aggiunge molte dipendenze. La semplice applicazione Hello World scritta con Spring può pesare una dozzina di MB e ho visto Spring aggiunta solo per evitare alcune righe di costruttore e setter.

  2. Aggiunge la magia di cui sopra , che è difficile da capire per gli estranei (ad esempio, gli sviluppatori di GUI che devono apportare alcune modifiche al livello aziendale). Vedi il grande articolo Menti innovative che non pensano allo stesso modo - New York Times , che descrive come questo effetto sia sottovalutato dagli esperti .

  3. Indurisce a rintracciare l'uso delle classi. Gli IDE come Eclipse hanno grandi capacità di tracciare gli usi di determinate classi o invocazioni di metodi. Ma questa traccia si perde quando si fa l'iniezione di dipendenza e si invoca la riflessione.

  4. L'utilizzo dell'IoC normalmente non termina con l'iniezione di dipendenze, termina con innumerevoli proxy e si ottiene la traccia dello stack che conta centinaia di righe, il 90% delle quali sono proxy e invoca tramite la riflessione. Ciò complica notevolmente l'analisi dello stack stack.

Quindi, essendo un grande fan di Spring e IoC, di recente ho dovuto ripensare alle domande precedenti. Alcuni dei miei colleghi sono sviluppatori web presi nel mondo Java dal mondo Web governato da Python e PHP e l' ovvio per le cose javaist è tutt'altro che ovvio per loro. Alcuni dei miei colleghi stanno facendo brutti scherzi (ovviamente privi di documenti) che rendono molto difficile trovare l'errore. Naturalmente il misusage dell'IoC rende le cose più leggere per loro;)

Il mio consiglio, se forzerai l'uso dell'IoC nel tuo lavoro, sarai ritenuto responsabile per qualsiasi uso improprio di qualcun altro;)


+1 come con ogni potente strumento, è facilmente utilizzato in modo improprio e DEVI capire quando NON utilizzarlo.
Phil

4

Penso che ChrisF abbia qualcosa di giusto. Non vendere DI in sé. Ma vendi l'idea di test unitari del codice.

Un approccio per ottenere un contenitore DI nell'architettura potrebbe essere quindi quello di consentire lentamente ai test di guidare l'implementazione in qualcosa che si adatta bene a quell'architettura. Ad esempio, supponiamo che tu stia lavorando su un controller in un'applicazione MVC ASP.NET. Di 'che scrivi la lezione in questo modo:

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

    public UserController(IUserRepository userRepository) {
        ...
    }

    ...
}

Ciò consente di scrivere unit test disaccoppiati dall'attuazione effettiva del repository utente. Ma la classe funziona ancora senza un contenitore DI per crearlo per te. Il costruttore senza parametri lo creerà con il repository predefinito.

Se voi colleghi vedete che questo porta a test molto più puliti, forse possono rendersi conto che è un modo migliore. Forse puoi farli iniziare a scrivere codice così.

Una volta che si sono resi conto che si tratta di una progettazione migliore rispetto a quella di UserController che conosce un'implementazione specifica di UserRepository, puoi fare la mossa successiva e mostrare loro che puoi avere un componente, il contenitore DI, che può fornire automaticamente le istanze richieste.


Grande! Attualmente lo sto facendo perché voglio davvero che i test delle unità automatizzate vengano eseguiti, anche se non riesco a ottenere DI. Ho dimenticato di dire che anche i test unitari non vengono eseguiti nel nostro team :( quindi se sarò il primo.
Mel

2
In tal caso, direi che questo è il vero problema qui. Trova la radice della resistenza ai test unitari e attacca. Lascia che DI menti per ora. Il test è più importante IMHO.
Christian Horsdal,

@ChristianHorsdal Sono d'accordo, volevo che fosse accoppiato liberamente per consentire un facile derisione / test.
Mel

L'idea è davvero fantastica ... Grande vendita per i test
bunglestink

2

Forse la cosa migliore da fare è fare un passo indietro e chiedersi perché si desidera introdurre contenitori DI? Se si tratta di migliorare la testabilità, allora il tuo primo compito è convincere il team ad acquistare per i test unitari. Personalmente sarei molto più preoccupato per la mancanza di unit test che per la mancanza di un contenitore DI.

Grazie a una suite completa o suite di test unitari, puoi tranquillamente iniziare a sviluppare il tuo design nel tempo. Senza di loro, affronti una lotta sempre più dura per mantenere il tuo design al passo con i requisiti in evoluzione, una lotta che alla fine molti team perdono.

Quindi il mio consiglio sarebbe quello di sostenere prima i test unitari e il refactoring quindi, a loro volta introdurre contenitori DI e infine DI.


2

Sto ascoltando alcuni grandi numeri della tua squadra qui:

  • "Nessuna libreria di terze parti" - AKA "Not Invented Here" o "NIH". Il timore è che, implementando una libreria di terze parti e rendendo quindi la base di codice dipendente da essa, se la libreria dovesse avere un bug, una falla di sicurezza o anche solo una limitazione che devi superare, diventerai dipendente da questa terza parte per riparare la propria libreria affinché il codice funzioni. Nel frattempo, poiché il suo "tuo" programma ha fallito in modo spettacolare, o è stato violato o non fa ciò che ha chiesto, tu continui a sostenere la responsabilità (e gli sviluppatori di terze parti diranno che il loro strumento è "così com'è" ", Utilizzare a proprio rischio).

    L'argomento contrario è che il codice che risolve il problema esiste già nella libreria di terze parti. Stai costruendo i tuoi computer da zero? Hai scritto Visual Studio? Stai evitando le librerie integrate in .NET? No. Hai un livello di fiducia in Intel / AMD e Microsoft e trovano sempre dei bug (e non sempre li risolvono rapidamente). L'aggiunta di una libreria di terze parti consente di far funzionare rapidamente una base di codice gestibile, senza dover reinventare la ruota. E l'esercito di avvocati di Microsoft ti riderà in faccia quando dici loro che un bug nelle librerie di crittografia .NET li rende responsabili per l'incidente di hacking del tuo client subito durante l'utilizzo del tuo programma .NET. Mentre Microsoft salterà sicuramente per correggere i buchi di sicurezza "zero-hour" in tutti i suoi prodotti,

  • "Vogliamo mettere tutto in un unico assemblaggio" - in realtà no. Almeno in .NET, se si modifica una riga di codice, è necessario ricostruire l'intero assembly contenente quella riga di codice. Una buona progettazione del codice si basa su più assiemi che è possibile ricostruire nel modo più indipendente possibile (o almeno ricostruire dipendenze, non dipendenze). Se vogliono davvero rilasciare un EXE che è solo il EXE (che ha valore in determinate circostanze), c'è un'app per quello; ILMerge.

  • "DI è tabù" - WTF? DI è una best practice accettata in qualsiasi linguaggio O / O con un costrutto di codice "interfaccia". In che modo il team aderisce alle metodologie di progettazione GRASP o SOLID se non accoppiano liberamente le dipendenze tra gli oggetti? Se non danno fastidio, allora stanno perpetuando la fabbrica di spaghetti che rende il prodotto la complicata morassa che è. Ora DI aumenta la complessità aumentando il conteggio degli oggetti e, sebbene faciliti la sostituzione di un'implementazione diversa, rende più difficile cambiare l'interfaccia stessa (aggiungendo un ulteriore livello di oggetto codice che deve cambiare).

  • "Non è necessario aggiungere questo livello di complessità a un progetto già complesso" - Potrebbero avere senso. Una cosa cruciale da considerare in ogni sviluppo di codice è YAGNI; "Non ne avrai bisogno". Un design che è SOLIDly ingegnerizzato dall'inizio è un design che è reso SOLID "sulla fede"; non sai se avrai bisogno della facilità di cambiamento fornita dai progetti SOLID, ma hai il sospetto. Mentre potresti avere ragione, potresti anche avere torto; un design molto SOLID potrebbe finire per essere visto come "codice lasagna" o "codice ravioli", con così tanti strati o blocchi di dimensioni morso che rendere difficili le modifiche apparentemente banali perché devi scavare così tanti strati e / o tracciare uno stack di chiamate così contorto.

    Sostengo una regola dei tre avvertimenti: fallo funzionare, rendilo pulito, rendilo SOLIDO. Quando scrivi per la prima volta una riga di codice, deve solo funzionare e non ottieni punti per i progetti di torri d'avorio. Supponiamo che sia una tantum e fai quello che devi fare. La seconda volta che guardi quel codice, probabilmente stai cercando di espanderlo o riutilizzarlo, e non è l'unica cosa che pensavi fosse. A questo punto, rendilo più elegante e comprensibile rifattorizzandolo; semplifica la logica contorta, estrai il codice ripetuto in loop e / o chiamate di metodo, aggiungi qualche commento qua e là per qualsiasi kludge che devi lasciare in atto, ecc. La terza volta che guardi quel codice è probabilmente un grosso problema. Ora dovresti aderire alle regole SOLID; iniettare dipendenze invece di costruirle, estrarre classi per contenere metodi meno coerenti con la "carne" del codice,

  • "Non è che inseriremo diverse implementazioni" - Lei, signore, ha una squadra che non crede nei test unitari tra le mani. Suppongo che sia impossibile scrivere un test unitario, che viene eseguito in modo isolato e non ha effetti collaterali, senza essere in grado di "deridere" gli oggetti che hanno quegli effetti collaterali. Qualsiasi programma non banale ha effetti collaterali; se il tuo programma legge o scrive su un DB, apre o salva file, comunica su una rete o su un socket periferico, avvia un altro programma, disegna finestre, invia alla console o utilizza in altro modo memoria o risorse al di fuori della "sandbox" del suo processo, ha un effetto collaterale. Se non fa nessuna di queste cose, cosa fa e perché dovrei acquistarlo?

    Ad essere onesti, i test unitari non sono l'unico modo per dimostrare che un programma funziona; puoi scrivere test di integrazione, codice su algoritmi provati matematicamente, ecc. Tuttavia, i test unitari mostrano, molto rapidamente, che dati gli input A, B e C, questo pezzo di codice genera la X prevista (o meno), e quindi che il codice funziona correttamente (o non funziona) in un determinato caso. Le suite di unit test possono dimostrare con un alto grado di sicurezza che il codice funziona come previsto in tutti i casi e forniscono anche qualcosa che una prova matematica non può; una dimostrazione che il programma STILL funziona nel modo previsto. Una prova matematica dell'algoritmo non dimostra che sia stata implementata correttamente, né dimostra che le modifiche apportate siano state implementate correttamente e non siano state interrotte.

Il fatto è che, se questa squadra non vede alcun valore in ciò che DI farebbe, allora non lo supporteranno, e tutti (almeno tutti i ragazzi senior) devono comprare qualcosa per un vero cambiamento accadere. Stanno mettendo molta enfasi su YAGNI. Stai ponendo molta enfasi su SOLID e test automatici. Da qualche parte nel mezzo c'è la verità.


1

Fai attenzione ad aggiungere funzionalità extra (come DI) a un progetto quando non approvato. Se si dispone di un piano di progetto con limiti di tempo, si potrebbe cadere in una trappola di non avere abbastanza tempo per completare le funzionalità approvate.

Anche se non usi DI adesso, partecipa ai test in modo da sapere esattamente quali sono le aspettative per i test presso la tua azienda. Ci sarà un altro progetto e sarai in grado di contrastare i problemi con i test del progetto corrente rispetto a DI.

Se non usi DI, puoi diventare creativo e rendere il tuo codice DI- "pronto". Utilizzare un costruttore senza parametri per chiamare altri costruttori:

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

MyClassConstructor(Dependency)
{
    _Dependency = Dependency
}

0

Penso che una delle loro maggiori preoccupazioni sia proprio l'opposto: DI non sta rendendo complesso il progetto. Nella maggior parte dei casi sta riducendo molta complessità.

Basti pensare senza DI, che cosa hai intenzione di ottenere l'oggetto / componente dipendente di cui hai bisogno? Normalmente è la ricerca da un repository, o ottenere un singleton o istanziare te stesso.

Il primo 2 comporta un codice aggiuntivo nel localizzare il componente dipendente, nel mondo DI, non hai fatto nulla per eseguire la ricerca. La complessità dei componenti è infatti ridotta.

Se stai istanziando te stesso in alcuni casi che non è appropriato, sta anche creando più complessità. Devi sapere come creare correttamente l'oggetto, devi sapere quali valori inizializzare l'oggetto su uno stato realizzabile, tutto ciò crea ulteriore complessità al tuo codice.

Di conseguenza, DI non sta rendendo il progetto complesso, lo sta rendendo più semplice, rendendo i componenti più semplici.

Un altro grande argomento è che non hai intenzione di collegare diverse implementazioni. Sì, è vero che nel codice di produzione non è comune avere molte implementazioni diverse nel codice di produzione reale. Tuttavia, esiste un luogo importante che richiede un'implementazione diversa: Mock / Stub / Test raddoppia nei test unitari. Per far funzionare DI, nella maggior parte dei casi si basa su un modello di sviluppo basato sull'interfaccia, a sua volta rende possibile la derisione / stub nei test unitari. Oltre a sostituire l'implementazione, il "design orientato all'interfaccia" rende anche un design più pulito e migliore. Ovviamente puoi comunque progettare con l'interfaccia senza DI. Tuttavia, Unit test e DI ti stanno spingendo ad adottare tali pratiche.

A meno che i tuoi "dei" in azienda non pensino che: "codice più semplice", "unit test", "modularizzazione", "singola responsabilità", ecc. Siano tutti elementi a loro sfavore, non dovrebbero esserci motivi per rifiutare di usare DI.


È bello in teoria, ma in pratica l'aggiunta di un contenitore DI è una complessità aggiuntiva. La seconda parte del tuo argomento mostra perché vale la pena lavorare, ma è ancora lavoro.
schlingel,

In effetti, dalle mie esperienze passate, DI sta RIDUCENDO anziché un'aggiunta alla complessità. Sì, ha aggiunto qualcosa in più al sistema, il che contribuisce alla complessità. Tuttavia, con DI, la complessità di altri componenti nel sistema è notevolmente ridotta. Alla fine, la complessità è infatti ridotta. Per la seconda parte, a meno che non stiano eseguendo test unitari, NON si tratta di lavori extra. Senza DI, i loro test unitari saranno per lo più difficili da scrivere e mantenere. Con DI, lo sforzo è notevolmente ridotto. Quindi, in effetti, è RIDURRE del lavoro (a meno che non lo siano e non si fidino dei test unitari)
Adrian Shum,

Dovrei sottolineare una cosa: DI nella mia risposta non significa alcun quadro DI esistente. È solo la metodologia nella progettazione / sviluppo di componenti nella tua app. Puoi trarne un grande vantaggio se il tuo codice è guidato dall'interfaccia, i tuoi componenti prevedono che le dipendenze vengano iniettate invece di cercare / creare. Con una tale progettazione, anche se stai scrivendo un "caricatore" specifico per l'applicazione che esegue il cablaggio dei componenti (nessun scopo generico DI fw), stai ancora beneficiando di ciò che ho menzionato: riduzione della complessità e riduzione del lavoro nei test unitari
Adrian Shum,

0

"Vogliamo mantenerlo semplice, se possibile semplicemente racchiudere tutto in un unico assieme. DI è una complessità non necessaria senza alcun vantaggio".

Il vantaggio è che promuove la progettazione di interfacce e consente un facile derisione. Ciò facilita di conseguenza i test unitari. I test unitari garantiscono un codice affidabile, modulare e facilmente gestibile. Se il tuo capo mantiene ancora una cattiva idea, non c'è niente che tu possa fare: le sue migliori pratiche del settore contro cui stanno litigando.

Fagli provare a scrivere un test unitario con un framework DI e una libreria beffarda, impareranno presto ad amarlo.


Raddoppiare o triplicare letteralmente il numero di classi nel tuo sistema ora è ciò che trovo utile. Ciò è simile al dibattito sul tentativo di ottenere una copertura unitaria del 100% scrivendo un singolo test per metodo o scrivendo test unitari significativi.
Andrew T Finnell,

Non capisco, pensi che i test unitari siano una buona idea? Se lo fai, prendi in giro anche gli oggetti? Questo è più facile con interfacce e DI. Ma sembra che tu stia litigando.
NimChimpsky,

È davvero il problema del pollo e delle uova. Interfacce / DI e test unitari sono così strettamente rilevanti tra loro che è difficile fare l'uno senza l'altro. Ma credo che i test unitari siano una strada migliore e più facile da iniziare su una base di codice esistente. Rifactoring graduale del vecchio codice kludgy in componenti coesivi. Dopodiché, potresti sostenere che la creazione di oggetti dovrebbe essere eseguita con un framework DI invece di essere newovunque e scrivere classi di fabbrica poiché hai tutti questi componenti a posto.
Spoike,

0

Lavori con queste persone su progetti più piccoli o solo uno grande? È più facile convincere le persone a provare qualcosa di nuovo in un progetto secondario / terziario rispetto al team / aziende pane e burro. Le ragioni principali sono che se fallisce il costo della rimozione / sostituzione è molto più piccolo, ed è molto meno lavoro per ottenerlo ovunque in un piccolo progetto. In un grande esistente hai un grande costo iniziale per apportare la modifica ovunque in una sola volta o un periodo prolungato in cui il codice è una miscela del vecchio / nuovo approccio che aggiunge attrito perché devi ricordare in che modo è progettata ogni classe lavorare.


0

Sebbene una delle cose che promuovono la DI sia il unit test, ma credo che in alcuni casi aggiunga un livello di complessità.

  • Meglio avere un'auto dura in prova, ma facile in riparazione che un'auto facile in prova e dura in riparazione.

  • Inoltre, mi sembra che il gruppo che spinge per DI stia presentando le proprie idee solo influenzando il fatto che alcuni approcci di progettazione esistenti siano negativi.

  • se abbiamo un metodo che fa 5 diverse funzioni

    La gente dice:

    • nessuna separazione delle preoccupazioni. [qual è il problema? stanno cercando di farlo apparire male, che nella logica e nella programmazione è molto meglio sapere cosa stanno facendo l'un l'altro per evitare meglio conflitti e aspettative sbagliate e vedere facilmente l'impatto della modifica del codice, perché non possiamo fare tutto oggetti non correlati tra loro al 100% o almeno non possiamo garantirlo {a meno che il motivo del test di regressione sia importante?}]
    • Complesso [Vogliono ridurre la complessità creando ulteriori cinque classi ciascuna per gestire una funzione e una classe aggiuntiva (Contenitore) per creare la classe per la funzione necessaria ora basata su impostazioni (XML o Dizionario) oltre alla filosofia "avere un valore predefinito / oppure no "discussioni, ...] poi hanno spostato la complessità nel contenitore e nella struttura, [a me sembra come spostare la complessità da un posto a due posti con tutte le complessità specifiche del linguaggio per gestirlo. ]

Credo che sia meglio creare il nostro modello di progettazione che soddisfi le esigenze aziendali piuttosto che essere influenzato da DI.

a favore di DI, potrebbe essere utile in alcune situazioni, ad esempio se hai centinaia di implementazioni per la stessa interfaccia dipende da un selettore e queste implementazioni sono molto diverse nella logica, in questo caso andrei con DI o DI tecniche simili. ma se abbiamo poche implementazioni per la stessa interfaccia non abbiamo bisogno di DI.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.