wake-up-neo.net

Quali sono gli svantaggi dell'utilizzo dell'iniezione di dipendenza?

Sto cercando di introdurre DI come modello qui al lavoro e uno dei nostri sviluppatori principali vorrebbe sapere: quali - se del caso - sono gli aspetti negativi all'uso del modello di iniezione di dipendenza?

Nota Sto cercando un elenco, se possibile, esaustivo, non una discussione soggettiva sull'argomento.


Chiarimento : sto parlando dell'iniezione di dipendenza modello (vedi questo articolo di Martin Fowler), not un framework specifico, basato su XML (come Spring) o basato su codice (come Guice) o "auto-rollato".


Modifica : Altre grandi discussioni/ranting/dibattiti in corso / r/programmazione qui.

332
Epaga

Un paio di punti:

  • DI aumenta la complessità, di solito aumentando il numero di classi poiché le responsabilità sono più separate, il che non è sempre vantaggioso
  • Il tuo codice sarà (in qualche modo) accoppiato al framework di iniezione delle dipendenze che usi (o più in generale come decidi di implementare il modello DI)
  • I contenitori DI o gli approcci che eseguono la risoluzione dei tipi generalmente comportano una leggera penalità di runtime (molto trascurabile, ma è lì)

In generale, il vantaggio del disaccoppiamento semplifica la lettura e la comprensione di ogni attività, ma aumenta la complessità dell'orchestrazione delle attività più complesse.

202
Håvard S

Lo stesso problema di base che si presenta spesso con la programmazione orientata agli oggetti, le regole di stile e praticamente tutto il resto. È possibile - molto comune, in effetti - fare troppa astrazione, aggiungere troppa indiretta e applicare generalmente buone tecniche eccessivamente e nei posti sbagliati.

Ogni modello o altro costrutto applicato porta complessità. L'astrazione e l'indirizzamento indiretto diffondono le informazioni, a volte spostando i dettagli irrilevanti di mezzo, ma ugualmente a volte rendendo più difficile capire esattamente cosa sta succedendo. Ogni regola che applichi comporta inflessibilità, escludendo opzioni che potrebbero essere l'approccio migliore.

Il punto è scrivere codice che fa il lavoro ed è solido, leggibile e gestibile. Sei uno sviluppatore di software, non un costruttore di torri d'avorio.

Collegamenti pertinenti

http://thedailywtf.com/Articles/The_Inner-Platform_Effect.aspx

http://www.joelonsoftware.com/articles/fog0000000018.html


Probabilmente la forma più semplice di iniezione di dipendenza (non ridere) è un parametro. Il codice dipendente dipende dai dati e tali dati vengono iniettati mediante il passaggio del parametro.

Sì, è sciocco e non si rivolge al punto di iniezione di dipendenza orientato agli oggetti, ma un programmatore funzionale ti dirà che (se hai funzioni di prima classe) questo è l'unico tipo di iniezione di dipendenza di cui hai bisogno. Il punto qui è fare un esempio banale e mostrare i potenziali problemi.

Prendiamo questa semplice funzione tradizionale: la sintassi C++ non è significativa qui, ma devo scriverla in qualche modo ...

void Say_Hello_World ()
{
  std::cout << "Hello World" << std::endl;
}

Ho una dipendenza che voglio estrarre e iniettare: il testo "Hello World". Abbastanza facile ...

void Say_Something (const char *p_text)
{
  std::cout << p_text << std::endl;
}

In che modo è più flessibile dell'originale? Bene, e se decidessi che l'output dovrebbe essere unicode. Probabilmente voglio passare da std :: cout a std :: wcout. Ma ciò significa che le mie stringhe devono essere di wchar_t, non di char. O ogni chiamante deve essere modificato, o (più ragionevolmente), la vecchia implementazione viene sostituita con un adattatore che traduce la stringa e chiama la nuova implementazione.

Questo è il lavoro di manutenzione che non sarebbe necessario se avessimo conservato l'originale.

E se sembra banale, dai un'occhiata a questa funzione del mondo reale dall'API Win32 ...

http://msdn.Microsoft.com/en-us/library/ms632680%28v=vs.85%29.aspx

Sono 12 le "dipendenze" da affrontare. Ad esempio, se le risoluzioni dello schermo diventano davvero enormi, forse avremo bisogno di valori di coordinate a 64 bit e di un'altra versione di CreateWindowEx. E sì, c'è già una versione precedente ancora in giro, che presumibilmente viene mappata alla versione più recente dietro le quinte ...

http://msdn.Microsoft.com/en-us/library/ms632679%28v=vs.85%29.aspx

Queste "dipendenze" non sono solo un problema per lo sviluppatore originale: tutti coloro che usano quell'interfaccia devono cercare quali sono le dipendenze, come sono specificate e cosa significano, e capire cosa fare per la loro applicazione. È qui che le parole "impostazioni predefinite sensibili" possono semplificare la vita.

L'iniezione di dipendenza orientata agli oggetti non è diversa in linea di principio. Scrivere una classe è un overhead, sia nel testo del codice sorgente che nel tempo dello sviluppatore, e se quella classe è scritta per fornire dipendenze in base ad alcune specifiche degli oggetti dipendenti, allora l'oggetto dipendente è bloccato nel supportare quell'interfaccia, anche se è necessario per sostituire l'implementazione di quell'oggetto.

Niente di tutto ciò dovrebbe essere letto come affermando che l'iniezione di dipendenza è male - tutt'altro. Ma ogni buona tecnica può essere applicata eccessivamente e nel posto sbagliato. Proprio come non tutte le stringhe devono essere estratte e trasformate in un parametro, non tutti i comportamenti di basso livello devono essere estratti da oggetti di alto livello e trasformati in una dipendenza iniettabile.

180
Steve314

Ecco la mia reazione iniziale: sostanzialmente gli stessi aspetti negativi di qualsiasi modello.

  • ci vuole tempo per imparare
  • se frainteso può portare a più danni che benefici
  • se portato all'estremo può essere più lavoro di quanto giustificherebbe il beneficio
75
Epaga

Il più grande "svantaggio" di Inversion of Control (non proprio DI, ma abbastanza vicino) è che tende a rimuovere il fatto di avere un singolo punto per vedere una panoramica di un algoritmo. Questo è fondamentalmente ciò che accade quando hai il codice disaccoppiato: la capacità di guardare in un posto è un artefatto di accoppiamento stretto.

45
kyoryu
41

Ho usato Guice (Java DI framework) ampiamente negli ultimi 6 mesi. Mentre nel complesso penso che sia fantastico (soprattutto dal punto di vista dei test), ci sono alcuni aspetti negativi. Soprattutto:

  • Il codice può diventare più difficile da capire. L'iniezione di dipendenza può essere utilizzata in modi molto ... creativi .... Ad esempio mi sono appena imbattuto in un codice che utilizzava un'annotazione personalizzata per iniettare un certo IOStreams (ad esempio: @ Server1Stream, @ Server2Stream). Mentre questo funziona, e ammetto che ha una certa eleganza, rende la comprensione delle iniezioni di Guice un prerequisito per comprendere il codice.
  • Curva di apprendimento superiore durante l'apprendimento del progetto. Questo è correlato al punto 1. Per capire come funziona un progetto che utilizza l'iniezione di dipendenza, è necessario comprendere sia il modello di iniezione di dipendenza sia il quadro specifico. Quando ho iniziato il mio attuale lavoro, ho trascorso alcune ore confuse a lamentarmi di ciò che Guice stava facendo dietro le quinte.
  • I costruttori diventano grandi. Anche se questo può essere in gran parte risolto con un costruttore predefinito o una fabbrica.
  • Gli errori possono essere offuscati. Il mio esempio più recente di questo è stato che ho avuto una collisione su 2 nomi di bandiera. Guice inghiottì l'errore in silenzio e una delle mie bandiere non fu inizializzata.
  • Gli errori vengono inviati al runtime. Se il modulo Guice viene configurato in modo errato (riferimento circolare, associazione errata, ...) la maggior parte degli errori non viene rilevata durante la compilazione. Al contrario, gli errori vengono visualizzati quando il programma viene effettivamente eseguito.

Ora che mi sono lamentato. Lasciami dire che continuerò (volentieri) a usare Guice nel mio progetto attuale e molto probabilmente nel prossimo. L'iniezione di dipendenza è un modello eccezionale e incredibilmente potente. Ma sicuramente può essere fonte di confusione e quasi sicuramente trascorrerai del tempo imprecando contro qualunque framework di iniezione di dipendenza tu scelga.

Inoltre, sono d'accordo con altri poster che l'iniezione di dipendenza può essere abusata.

40
Andy Peck

Il codice senza DI comporta il noto rischio di aggrovigliarsi in codice Spaghetti - alcuni sintomi sono che le classi e i metodi sono troppo grandi, fanno troppo e non possono essere facilmente modificati, suddivisi, refactored o testato.

Il codice con DI usato molto può essere codice Ravioli dove ogni piccola classe è come una singola pepita di ravioli - fa una piccola cosa e il principio di responsabilità singola è rispettato, al quale è buono. Ma guardare le lezioni da soli è difficile vedere cosa fa il sistema nel suo insieme, poiché questo dipende da come tutte queste piccole parti si incastrano, cosa difficile da vedere. Sembra solo un grande mucchio di piccole cose.

Evitando la complessità degli spaghetti di grandi quantità di codice accoppiato all'interno di una grande classe, si corre il rischio di un altro tipo di complessità, dove ci sono molte piccole classi semplici e le interazioni tra loro sono complesse.

Non penso che questo sia un rovescio della medaglia fatale - DI è ancora molto utile. Un certo grado di ravioli con piccole classi che fanno solo una cosa è probabilmente buono. Anche in eccesso, non penso che sia male come codice spaghetti. Ma essere consapevoli che può essere fatto troppo lontano è il primo passo per evitarlo. Segui i link per la discussione su come evitarlo.

24
Anthony

Se hai una soluzione domestica, le dipendenze sono proprio nel tuo costruttore. O forse come parametri del metodo che di nuovo non è troppo difficile da individuare. Sebbene le dipendenze gestite dal framework, se portate agli estremi, possono iniziare ad apparire come per magia.

Tuttavia, avere troppe dipendenze in troppe classi è un chiaro segno che la struttura della classe è rovinata. Quindi, in un certo senso, l'iniezione di dipendenza (coltivata in casa o gestita da un framework) può aiutare a far emergere evidenti problemi di progettazione che potrebbero altrimenti essere nascosti in agguato nel buio.


Per illustrare meglio il secondo punto, ecco un estratto da questo articolo ( fonte originale ) che credo sinceramente sia il problema fondamentale nella costruzione di qualsiasi sistema, non solo dei sistemi informatici.

Supponiamo che tu voglia progettare un campus universitario. Devi delegare parte del progetto a studenti e professori, altrimenti l'edificio di fisica non funzionerà bene per le persone di fisica. Nessun architetto sa abbastanza di cosa le persone fisiche hanno bisogno per fare tutto da sole. Ma non puoi delegare il design di ogni stanza ai suoi occupanti, perché in questo modo otterrai un enorme mucchio di macerie.

Come è possibile distribuire la responsabilità della progettazione attraverso tutti i livelli di una grande gerarchia, pur mantenendo la coerenza e l'armonia della progettazione generale? Questo è il problema di progettazione architettonica che Alexander sta cercando di risolvere, ma è anche un problema fondamentale dello sviluppo dei sistemi informatici.

DI risolve questo problema? No . Ma ti aiuta a vedere chiaramente se stai cercando di delegare la responsabilità di progettare ogni stanza per i suoi occupanti.

13
Anurag

Una cosa che mi fa dimenare un po 'con DI è l'assunto che tutti gli oggetti iniettati sono economico da istanziare e non producono effetti collaterali -O- la dipendenza viene utilizzata così frequentemente che supera ogni costo di istanza associato.

Dove questo può essere significativo è quando una dipendenza non è frequentemente utilizzata all'interno di una classe consumante; come qualcosa come un IExceptionLogHandlerService. Ovviamente, un servizio come questo viene invocato (si spera :)) raramente all'interno della classe - presumibilmente solo su eccezioni che devono essere registrate; ma il modello canonico dell'iniezione del costruttore ...

Public Class MyClass
    Private ReadOnly mExLogHandlerService As IExceptionLogHandlerService

    Public Sub New(exLogHandlerService As IExceptionLogHandlerService)
        Me.mExLogHandlerService = exLogHandlerService
    End Sub

     ...
End Class

... richiede che venga fornita un'istanza "live" di questo servizio, maledetti i costi/effetti collaterali necessari per ottenerlo. Non che probabilmente lo farebbe, ma cosa succede se la costruzione di questa istanza di dipendenza implica un hit del servizio/database, o configura le ricerche dei file o blocca una risorsa fino allo smaltimento? Se questo servizio fosse invece costruito secondo necessità, localizzato nel servizio o generato in fabbrica (tutti con problemi propri), i costi di costruzione sarebbero sostenuti solo quando necessario.

Ora, è un principio di progettazione del software generalmente accettato che costruire un oggetto è economico e non produce effetti collaterali. E mentre questa è una buona idea, non è sempre il caso. L'uso dell'iniezione tipica del costruttore richiede fondamentalmente che sia così. Significato quando si crea un'implementazione di una dipendenza, è necessario progettarla tenendo presente DI. Forse avresti reso la costruzione di oggetti più costosa per ottenere benefici altrove, ma se questa implementazione verrà iniettata, probabilmente ti costringerà a riconsiderare quel progetto.

A proposito, alcune tecniche possono mitigare questo esatto problema consentendo il caricamento lento delle dipendenze iniettate, ad es. fornendo una classe a Lazy<IService> istanza come dipendenza. Ciò cambierebbe i costruttori dei tuoi oggetti dipendenti e renderebbe ancora più consapevoli i dettagli dell'implementazione come la spesa per la costruzione di oggetti, che probabilmente non è neppure desiderabile.

13
ckittel

L'illusione di aver disaccoppiato il codice semplicemente implementando l'iniezione di dipendenza senza REALMENTE disaccoppiarlo. Penso che sia la cosa più pericolosa di DI.

12
Joey Guerra

Questo è più un gioco da ragazzi. Ma uno degli svantaggi dell'iniezione di dipendenza è che rende un po 'più difficile per gli strumenti di sviluppo ragionare e navigare nel codice.

In particolare, se fai clic tenendo premuto il tasto Ctrl/Comando su un richiamo del metodo nel codice, verrai indirizzato alla dichiarazione del metodo su un'interfaccia anziché all'implementazione concreta.

Questo è davvero un aspetto negativo del codice debolmente accoppiato (codice progettato dall'interfaccia) e si applica anche se non si utilizza l'iniezione di dipendenza (cioè, anche se si usano semplicemente fabbriche). Ma l'avvento dell'iniezione di dipendenza è ciò che ha davvero incoraggiato il codice debolmente accoppiato alle masse, quindi ho pensato di menzionarlo.

Inoltre, i vantaggi del codice debolmente accoppiato superano di gran lunga questo, quindi lo chiamo nitpick. Anche se ho lavorato abbastanza a lungo per sapere che questo è il tipo di push-back che potresti ottenere se tenti di introdurre l'iniezione di dipendenza.

In effetti, mi permetto di indovinare che per ogni "aspetto negativo" che puoi trovare per l'iniezione di dipendenza, troverai molti aspetti positivi che lo superano di gran lunga.

11
Jack Leow

basato sul costruttore l'iniezione di dipendenza (senza l'ausilio di "framework" magici) è un modo pulito e vantaggioso per strutturare OO. Nelle migliori basi di codice che ho visto , negli anni trascorsi con altri ex colleghi di Martin Fowler, ho iniziato a notare che la maggior parte delle buone lezioni scritte in questo modo finiscono per avere un unico metodo doSomething.

Il principale svantaggio, quindi, è che quando ti rendi conto che è tutto solo un kludgy a lungo termine OO modo di scrivere chiusure come classi al fine di ottenere i benefici della programmazione funzionale, la tua motivazione a scrivere = OO può evaporare rapidamente.

10
sanityinc

Trovo che l'iniezione del costruttore possa portare a brutti costruttori (e la uso in tutto il mio codice - forse i miei oggetti sono troppo granulari?). Inoltre, a volte con l'iniezione del costruttore finisco con orribili dipendenze circolari (anche se questo è molto raro), quindi potresti trovarti a dover avere una sorta di ciclo di vita dello stato pronto con diversi cicli di iniezione di dipendenza in un sistema più complesso.

Tuttavia, preferisco l'iniezione del costruttore rispetto all'iniezione del setter perché una volta che il mio oggetto è stato costruito, allora so senza dubbio in che stato si trova, se si trova in un ambiente di unit test o caricato in alcuni IOC = container. Che, in una sorta di rotonda, sta dicendo quello che ritengo sia il principale svantaggio dell'iniezione setter.

(come sidenote, trovo l'intero argomento piuttosto "religioso", ma il tuo chilometraggio varierà con il livello di zelo tecnico nel tuo team di sviluppo!)

9
James B

Se stai utilizzando DI senza un IOC, il più grande svantaggio è vedere rapidamente quante dipendenze ha il tuo codice e quanto tutto è strettamente accoppiato. ("Ma ho pensato che fosse un buon design! ") La naturale progressione è quella di spostarsi verso un contenitore IOC che può richiedere un po 'di tempo per apprendere e implementare (non tanto quanto la curva di apprendimento del WPF, ma non è gratis.) L'ultimo aspetto negativo è che alcuni sviluppatori inizieranno a scrivere onestamente ai test di unità di bontà e ci vorrà del tempo per capirlo. Gli sviluppatori che in precedenza potevano fare qualcosa in mezza giornata passeranno improvvisamente due giorni cercando di capire come deridere tutte le loro dipendenze.

Simile alla risposta di Mark Seemann, la linea di fondo è che passi il tempo a diventare uno sviluppatore migliore piuttosto che hackerare pezzi di codice insieme e lanciarli fuori dalla porta/in produzione. Quale sarebbe la tua azienda preferirebbe? Solo tu puoi rispondere a questo.

8
Mike Post

DI è una tecnica o un modello e non è correlato a nessun framework. È possibile collegare manualmente le dipendenze. DI ti aiuta con SR (responsabilità singola) e SoC (separazione delle preoccupazioni). DI porta a un design migliore. Dal mio punto di vista ed esperienza non ci sono aspetti negativi. Come con qualsiasi altro modello, puoi sbagliarlo o utilizzarlo in modo improprio (ma cosa succede nel caso di DI piuttosto difficile).

Se si introduce DI come principio in un'applicazione legacy, usando un framework - l'errore più grande che si può fare è abusarne come Service Locator. DI + Framework è fantastico e ha migliorato le cose ovunque l'ho visto! Dal punto di vista organizzativo, ci sono i problemi comuni con ogni nuovo processo, tecnica, modello, ...:

  • Devi formare la tua squadra
  • Devi cambiare la tua applicazione (che include i rischi)

In generale devi investire tempo e denaro, a parte questo, non ci sono aspetti negativi, davvero!

5
Robert

Leggibilità del codice. Non sarai in grado di capire facilmente il flusso di codice poiché le dipendenze sono nascoste nei file XML.

3
Rahul

Sembra che i presunti benefici di un linguaggio tipizzato staticamente diminuiscano significativamente quando si utilizzano costantemente tecniche per aggirare la tipizzazione statica. Un grande Java negozio che ho appena intervistato era mappare le loro dipendenze di build con l'analisi del codice statico ... che doveva essere in grado di analizzare tutti i file Spring per essere efficace.

2
Chris D

Due cose:

  • Richiedono un supporto aggiuntivo per gli strumenti per verificare che la configurazione sia valida.

Ad esempio, IntelliJ (edizione commerciale) supporta la verifica della validità di una configurazione Spring e segnalerà errori come violazioni dei tipi nella configurazione. Senza quel tipo di supporto degli strumenti, non è possibile verificare se la configurazione è valida prima di eseguire i test.

Questo è uno dei motivi per cui il modello 'cake' (come è noto alla comunità Scala) è una buona idea: il cablaggio tra i componenti può essere controllato dal controllo del tipo. che beneficiano di annotazioni o XML.

  • Rende molto difficile l'analisi statica globale dei programmi.

Frame come Spring o Guice rendono difficile determinare staticamente l'aspetto del grafico a oggetti creato dal container. Sebbene creino un grafico a oggetti all'avvio del contenitore, non forniscono API utili che descrivono il grafico a oggetti che/verrebbe/verrà creato.

2
Martin Ellis

Può aumentare i tempi di avvio dell'app perché il contenitore IoC dovrebbe risolvere le dipendenze in modo corretto e talvolta richiede diverse iterazioni.

1
Roman