Poor Man's Dependency Injection è un buon modo per introdurre la testabilità in un'applicazione legacy?


14

L'anno scorso ho creato un nuovo sistema usando Dependency Injection e un contenitore IOC. Questo mi ha insegnato molto su DI!

Tuttavia, anche dopo aver appreso i concetti e gli schemi corretti, lo considero una sfida per disaccoppiare il codice e introdurre un contenitore IOC in un'applicazione legacy. L'applicazione è abbastanza grande al punto che un'implementazione vera sarebbe schiacciante. Anche se il valore è stato compreso e il tempo è stato concesso. A chi è concesso tempo per qualcosa del genere ??

L'obiettivo ovviamente è portare unit test alla logica aziendale!
Logica aziendale che si intreccia con le chiamate al database che impediscono i test.

Ho letto gli articoli e capisco i pericoli dell'iniezione di dipendenza da uomo povero come descritto in questo articolo di Los Techies . Capisco che non disaccoppia davvero nulla.
Comprendo che può comportare molto refactoring a livello di sistema poiché le implementazioni richiedono nuove dipendenze. Non prenderei in considerazione l'utilizzo su un nuovo progetto di qualsiasi dimensione.

Domanda: Va bene usare il DI di Poor Man per introdurre la testabilità in un'applicazione legacy e iniziare a girare la palla?

Inoltre, usare il DI di Poor Man come approccio di base alla vera iniezione di dipendenza è un modo prezioso per educare sulla necessità e sui benefici del principio?

Riesci a refactoring di un metodo che ha una dipendenza dalle chiamate del database e un abstract che chiama dietro un'interfaccia? Il semplice fatto di avere tale astrazione renderebbe quel metodo testabile poiché un'implementazione simulata potrebbe essere trasmessa tramite un sovraccarico del costruttore.

Lungo la strada, una volta che lo sforzo ha guadagnato sostenitori, il progetto potrebbe essere aggiornato per implementare un container IOC e i costruttori sarebbero là fuori a prendere le astrazioni.



2
Solo una nota. I consider it a challenge to decouple code and introduce an IOC container into a legacy applicationcerto che lo è. Si chiama debito tecnico. Questo è il motivo per cui prima di qualsiasi rinnovamento importante, è preferibile refactors piccoli e continui. Ridurre i principali difetti di progettazione e passare a IoC sarebbe meno impegnativo.
Laiv

Risposte:


25

La critica sull'iniezione di Poor Man in NerdDinner ha meno a che fare con l'uso o meno di un contenitore DI rispetto all'impostazione corretta delle classi.

Nell'articolo, lo affermano

public class SearchController : Controller {

    IDinnerRepository dinnerRepository;

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

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

non è corretto perché, mentre il primo costruttore fornisce un comodo meccanismo di fallback per la costruzione della classe, crea anche una dipendenza strettamente legata DinnerRepository.

Il rimedio corretto ovviamente non è, come suggerisce Los Techies, aggiungere un contenitore DI, ma piuttosto rimuovere il costruttore offensivo.

public class SearchController : Controller 
{
    IDinnerRepository dinnerRepository;

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

La classe rimanente ora ha le sue dipendenze correttamente invertite. Ora sei libero di iniettare quelle dipendenze come preferisci.


Grazie per i tuoi pensieri! Capisco il "costruttore offensivo" e come dovremmo evitarlo. Capisco che avere questa dipendenza non disaccoppia nulla, ma consente test unitari. Sono sul lato iniziale dell'introduzione di DI e della messa in atto dei test unitari il più rapidamente possibile. Sto cercando di evitare la complicazione di un container DI / IOC o di una fabbrica all'inizio del gioco. Come accennato, in seguito quando il supporto per DI cresce, potremmo implementare un Container e rimuovere quei "costruttori offensivi".
Airn5475,

Il costruttore predefinito non è richiesto per i test unitari. Puoi assegnare qualsiasi classe alla dipendenza senza la classe, incluso un oggetto finto.
Robert Harvey,

2
@ Airn5475 fare attenzione a che non si è più di astrazione troppo. Questi test dovrebbero testare comportamenti significativi: testare che un XServicepassa un parametro a un XRepositorye restituisce ciò che restituisce non è eccessivamente utile a nessuno e non ti dà molto in termini di estensibilità. Scrivi i tuoi test unitari per testare comportamenti significativi e assicurati che i tuoi componenti non siano così ristretti da spostare tutta la tua logica sulla radice della composizione.
Formica P

Non capisco come l'inclusione del costruttore offensivo sarebbe utile per i test unitari, sicuramente fa il contrario? Rimuovere il costruttore offensivo in modo che il DI sia implementato correttamente e passare una dipendenza derisa quando si crea un'istanza degli oggetti.
Rob,

@Rob: non lo fa. È solo un modo conveniente per il costruttore predefinito di alzare un oggetto valido.
Robert Harvey,

16

In questo caso fai una falsa supposizione su cosa sia la "DI del povero".

La creazione di una classe che ha un costruttore "collegamento" che crea ancora l'accoppiamento non lo è la DI del povero.

Non usare un contenitore e creare manualmente tutte le iniezioni e le mappature è il DI del povero.

Il termine "DI di un uomo povero" suona come una brutta cosa da fare. Per questo motivo, il termine "DI puro" è incoraggiato in questi giorni in quanto sembra più positivo e in realtà descrive più accuratamente il processo.

Non solo è assolutamente corretto utilizzare DI di puro / puro per introdurre DI in un'app esistente, ma è anche un modo valido di utilizzare DI per molte nuove applicazioni. E come dici tu, tutti dovrebbero usare DI puro su almeno un progetto per capire veramente come funziona DI, prima di gestire la responsabilità sulla "magia" di un contenitore IoC.


8
"Poor man's" o "Pure" DI, qualunque preferiate, mitiga anche molti problemi con i contenitori IoC. Usavo i contenitori IoC per tutto, ma più tempo passa e più preferisco evitarli del tutto.
Formica P

7
@AntP, sono con te: sono puro al 100% in questi giorni ed evito attivamente i contenitori. Ma io (noi) siamo in minoranza su quel punto per quanto posso dire.
David Arno,

2
Sembra così tristemente - mentre mi allontano dal campo "magico" di contenitori IoC, deridendo le librerie e così via e mi ritrovo ad abbracciare OOP, incapsulamento, test doppi laminati a mano e verifica dello stato, sembra che la tendenza per la magia sia ancora sulla su. +1 comunque.
Ant P

3
@AntP & David: È bello sapere che non sono solo nel campo 'Pure DI'. I contenitori DI sono stati creati per affrontare le carenze nelle lingue. Aggiungono valore in questo senso. Ma nella mia mente, la vera risposta è riparare le lingue (o passare ad altre lingue) e non costruire un'innesto su di esse.
JimmyJames,

1

I cambiamenti di paragidm nei team legacy / basi di codice sono estremamente rischiosi:

Ogni volta che suggerisci "miglioramenti" al codice legacy e ci sono programmatori "legacy" nel team, stai solo dicendo a tutti che "hanno sbagliato" e diventi The Enemy per il resto del tuo tempo con l'azienda.

L'uso di un DI Framework come martello per distruggere tutti i chiodi renderà il codice legacy peggiore che migliore in tutti i casi. È anche estremamente rischioso personalmente.

Anche nei casi più limitati come il solo modo in cui può essere utilizzato nei casi di test, questi codici di test "non standard" e "estranei" saranno semplicemente contrassegnati @Ignorequando si rompono o peggio vengono costantemente lamentati dal programmatori legacy con il maggior peso nella gestione e vengono riscritti "correttamente" con tutto questo "tempo sprecato in unit test" incolpato solo di te.

L'introduzione di un framework DI, o persino il concetto di "DI puro" in un'app line-of-business, tanto meno un'enorme base di codice legacy senza la gestione off, il team e soprattutto la sponsorizzazione dello sviluppatore principale saranno solo la morte per te socialmente e politicamente nella squadra / compagnia. Fare cose del genere è estremamente rischioso e può essere un suicidio politico nel peggiore dei modi.

L'iniezione delle dipendenze è una soluzione alla ricerca di un problema.

Qualsiasi linguaggio con costruttori per definizione usa l'iniezione di dipendenza per convenzione se sai cosa stai facendo e capisci come usare correttamente un costruttore, questo è solo un buon design.

L'iniezione di dipendenza è utile solo a piccole dosi per una gamma molto ristretta di cose:

  • Cose che cambiano molto o hanno molte implementazioni alternative che sono staticamente vincolate.

    • I driver JDBC sono un esempio perfetto.
    • Client HTTP che potrebbero variare da piattaforma a piattaforma.
    • Sistemi di registrazione che variano in base alla piattaforma.
  • Sistemi di plugin che hanno plugin configurabili che possono essere definiti nel codice di configurazione nel framework e scoperti automaticamente all'avvio e caricati / ricaricati dinamicamente mentre il programma è in esecuzione.


10
L'app killer per DI è un test unitario. Come hai sottolineato, la maggior parte dei software non avrà bisogno di molti DI da sola. Ma i test sono anche client di questo codice, quindi dovremmo progettare per la testabilità. In particolare, ciò significa posizionare le cuciture nel nostro design in cui i test possono osservare il comportamento. Ciò non richiede un framework DI sofisticato, ma richiede che le dipendenze rilevanti siano rese esplicite e sostituibili.
Amon,

4
Se noi sviluppatori sapessimo se avanzare cosa potrebbe cambiare, sarebbe metà della battaglia. Sono stato su lunghi sviluppi in cui molte cose apparentemente "riparate" sono cambiate. A prova di futuro qua e là usando DI non è una cosa negativa. Usandolo ovunque in ogni momento sa di programmazione di culto del carico.
Robbie Dee,

complicare troppo il test significa solo che diventano obsoleti e contrassegnati come ignorati anziché aggiornati in futuro. DI nel codice legacy è una falsa economia. Tutto ciò che esisterà per fare è renderti il ​​"cattivo" per mettere tutto questo "codice non standard, troppo complesso che nessuno capisce" nella base di codice. Sei stato avvertito.

4
@JarrodRoberson, quello è davvero un ambiente tossico. Uno dei segni chiave di un buon sviluppatore è che guardano indietro al codice che hanno scritto in passato e pensano: "Uff, l'ho davvero scritto? È così sbagliato!" Questo perché sono cresciuti come sviluppatori e hanno imparato cose nuove. Qualcuno che guarda al codice che hanno scritto cinque anni fa e non vede nulla di sbagliato in esso non ha imparato nulla in quei cinque anni. Quindi se dire alla gente che hanno fatto qualcosa di sbagliato in passato ti rende un nemico, quindi fuggi rapidamente da quella compagnia, perché sono una squadra senza uscita che ti terrà indietro e ti ridurrà al loro scarso livello.
David Arno,

1
Non sono sicuro di essere d'accordo con le cose dei cattivi, ma per me il punto chiave da questo è che non hai bisogno di DI per fare test di unità. L'implementazione di DI per rendere il codice più testabile è solo correggere il problema.
Ant P
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.