Czy wstrzykiwanie zależności przez biednego człowieka jest dobrym sposobem na wprowadzenie testowalności do starszej aplikacji?

14

W ubiegłym roku stworzyłem nowy system przy użyciu Dependency Injection i kontenera IOC. To nauczyło mnie wiele o DI!

Jednak nawet po zapoznaniu się z pojęciami i właściwymi wzorami uważam za wyzwanie rozdzielić kod i wprowadzić kontener IOC do starszej aplikacji. Aplikacja jest na tyle duża, że ​​prawdziwa implementacja byłaby przytłaczająca. Nawet jeśli wartość została zrozumiana i czas został przyznany. Kto ma czas na coś takiego?

Celem jest oczywiście wprowadzenie testów jednostkowych do logiki biznesowej!
Logika biznesowa powiązana z zabezpieczającymi wywołania wywołaniami bazy danych.

Przeczytałem artykuły i rozumiem niebezpieczeństwa wstrzyknięcia uzależnienia od biednego człowieka, jak opisano w tym artykule Los Techies . Rozumiem, że tak naprawdę niczego nie oddziela.
Rozumiem, że może to wymagać dużego refaktoryzacji całego systemu, ponieważ implementacje wymagają nowych zależności. Nie rozważałbym użycia go w nowym projekcie o dowolnej wielkości.

Pytanie: Czy można używać DI Poor Man do wprowadzenia testowalności do starszej aplikacji i zacząć toczyć piłkę?

Ponadto, czy stosowanie DI biednego człowieka jako oddolnego podejścia do prawdziwego wstrzykiwania zależności jest cennym sposobem edukowania na temat potrzeby i korzyści wynikających z tej zasady?

Czy można refaktoryzować metodę, która ma zależność od wywołań bazy danych i streszczenie, która wywołuje interfejs? Po prostu posiadanie tej abstrakcji sprawiłoby, że ta metoda byłaby testowalna, ponieważ próbna implementacja mogłaby zostać przekazana przez przeciążenie konstruktora.

W przyszłości, gdy wysiłek zyska zwolenników, projekt może zostać zaktualizowany w celu wdrożenia kontenera MKOl, a konstruktorzy będą tam, którzy przyjmą abstrakcje.

Airn5475
źródło
2
Tylko uwaga. I consider it a challenge to decouple code and introduce an IOC container into a legacy applicationoczywiście że tak. To się nazywa dług techniczny. Dlatego przed każdą poważną modernizacją lepiej jest stosować małe i ciągłe refaktory. Zmniejszenie głównych wad projektowych i przejście do IoC byłoby mniej trudne.
Laiv

Odpowiedzi:

25

Krytyka dotycząca iniekcji Biednego Człowieka w NerdDinner ma mniej wspólnego z tym, czy używasz kontenera DI, czy z prawidłowym konfigurowaniem klas.

W artykule stwierdzają to

public class SearchController : Controller {

    IDinnerRepository dinnerRepository;

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

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

jest niepoprawny, ponieważ chociaż pierwszy konstruktor zapewnia wygodny mechanizm rezerwowy do konstruowania klasy, tworzy również ściśle związaną z nim zależność DinnerRepository.

Prawidłowym lekarstwem nie jest oczywiście, jak sugeruje Los Techies, dodanie kontenera DI, ale usunięcie szkodliwego konstruktora.

public class SearchController : Controller 
{
    IDinnerRepository dinnerRepository;

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

Pozostała klasa ma teraz poprawnie odwrócone zależności. Możesz teraz wstrzykiwać te zależności w dowolny sposób.

Robert Harvey
źródło
Dzięki za twoje przemyślenia! Rozumiem „obrażającego konstruktora” i jak go unikać. Rozumiem, że posiadanie tej zależności niczego nie oddziela, ale pozwala na testy jednostkowe. Jestem na wczesnym etapie wprowadzania DI i przeprowadzania testów jednostkowych tak szybko, jak to możliwe. Staram się uniknąć komplikacji kontenera DI / IOC lub fabryki na wczesnym etapie gry. Jak wspomniano, później, gdy rośnie wsparcie dla DI, możemy zaimplementować kontener i usunąć te „obrażające konstruktory”.
Airn5475,
Domyślny konstruktor nie jest wymagany do testów jednostkowych. Bez niej możesz przekazać dowolną zależność, jaką chcesz, w tym próbny obiekt.
Robert Harvey
2
@ Airn5475 uważać, że nie są na abstrahując też. Testy te powinny testować sensowne zachowania - testowanie, że XServiceparametr przekazuje parametr do XRepositoryi zwraca to, co otrzymuje, nie jest zbyt przydatne dla nikogo i nie daje zbyt wiele możliwości rozszerzenia. Napisz testy jednostkowe, aby przetestować sensowne zachowanie i upewnić się, że komponenty nie są tak wąskie, że w rzeczywistości przenosisz całą logikę do katalogu głównego kompozycji.
Ant P
Nie rozumiem, w jaki sposób włączenie obrażającego konstruktora pomogłoby w testach jednostkowych, na pewno robi to odwrotnie? Usuń obraźliwy konstruktor, aby DI został poprawnie zaimplementowany i przekaż pozorowaną zależność podczas tworzenia instancji obiektów.
Rob
@Rob: Nie. Jest to po prostu wygodny sposób, by domyślny konstruktor mógł postawić prawidłowy obiekt.
Robert Harvey
16

Zakładasz tutaj fałszywe przypuszczenie, co to jest „DI biedaka”.

Stworzenie klasy, która ma konstruktor „skrótu”, który wciąż tworzy sprzężenie, nie jest DI dla biednych ludzi.

Nieużywanie kontenera i ręczne tworzenie wszystkich zastrzyków i mapowań to DI biedaka.

Termin „DI biedaka” wydaje się złym posunięciem. Z tego powodu zachęca się dziś do określenia „czysta DI”, ponieważ brzmi to bardziej pozytywnie i właściwie dokładniej opisuje ten proces.

Nie tylko absolutnie w porządku jest użycie DI biednego człowieka / czystego człowieka do wprowadzenia DI do istniejącej aplikacji, ale jest to również prawidłowy sposób używania DI dla wielu nowych aplikacji. I jak mówisz, każdy powinien używać czystego DI w co najmniej jednym projekcie, aby naprawdę zrozumieć, jak działa DI, zanim przejmie odpowiedzialność za „magię” pojemnika IoC.

David Arno
źródło
8
„Biedny człowiek” lub „Czysta” DI, w zależności od tego, co wolisz, również łagodzi wiele problemów z kontenerami IoC. Zawsze korzystałem z kontenerów IoC do wszystkiego, ale im więcej czasu mija, tym bardziej wolę ich unikać.
Ant P
7
@AntP, jestem z tobą: w tej chwili jestem w 100% czysty DI i aktywnie unikam pojemników. Ale ja (my) jestem w mniejszości pod tym względem, o ile wiem.
David Arno,
2
Wydaje się to tak smutne - kiedy odchodzę od „magicznego” obozu kontenerów IoC, drwiących bibliotek i tak dalej, i odkrywam, że obejmuję OOP, enkapsulację, ręcznie podwajane testy i weryfikację stanu, wydaje się, że trend w magii wciąż trwa w górę. W każdym razie +1.
Ant P
3
@AntP i David: Dobrze jest słyszeć, że nie jestem sam w obozie „Pure DI”. Kontenery DI zostały utworzone w celu usunięcia braków w językach. W tym sensie dodają wartość. Ale moim zdaniem prawdziwą odpowiedzią jest poprawienie języków (lub przejście do innych języków) i nie budowanie na nich cruft.
JimmyJames
1

Zmiany paragidów w starszych zespołach / bazach kodu są niezwykle ryzykowne:

Za każdym razem, gdy sugerujesz „ulepszenia” starszego kodu, a w zespole są „pradawni” programiści, po prostu mówisz wszystkim, że „zrobili to źle” i stajesz się wrogiem na resztę swojego czasu w firmie.

Użycie DI Framework jako młota do zniszczenia wszystkich gwoździ sprawi, że starszy kod będzie gorszy niż lepszy we wszystkich przypadkach. Jest to również bardzo ryzykowne osobiście.

Nawet w najbardziej ograniczonych przypadkach, takich jak tylko tak, aby można go było użyć w przypadkach testowych, sprawi, że te przypadki testowe będą „niestandardowe” i „obce”, które w najlepszym wypadku zostaną po prostu zaznaczone, @Ignoregdy się zepsują lub gorzej, będą ciągle narzekać programiści o największej sile rażenia w zarządzaniu i przepisywani „poprawnie” z całym tym „marnowaniem czasu na testy jednostkowe” obwinianym wyłącznie za ciebie.

Wprowadzenie frameworka DI, a nawet koncepcji „Pure DI” do linii aplikacji biznesowych, a tym bardziej ogromna baza kodu bez zarządzania, zespół, a zwłaszcza sponsorowanie głównego dewelopera, będzie po prostu zapowiedzią śmierci dla ciebie społecznie i politycznie w zespole / firmie. Robienie takich rzeczy jest niezwykle ryzykowne i może być politycznym samobójstwem w najgorszy sposób.

Dependency Injection to rozwiązanie szukające problemu.

Każdy język z konstruktorami z definicji używa wstrzykiwania zależności zgodnie z konwencją, jeśli wiesz, co robisz i rozumiesz, jak prawidłowo korzystać z konstruktora, jest to po prostu dobry projekt.

Wstrzyknięcie zależności jest przydatne tylko w małych dawkach dla bardzo wąskiego zakresu rzeczy:

  • Rzeczy, które bardzo się zmieniają lub mają wiele alternatywnych implementacji, które są związane statycznie.

    • Sterowniki JDBC są doskonałym przykładem.
    • Klienci HTTP, którzy mogą różnić się w zależności od platformy.
    • Systemy rejestrowania różnią się w zależności od platformy.
  • Systemy wtyczek, które mają konfigurowalne wtyczki, które można zdefiniować w kodzie konfiguracji w twoim frameworku i które są automatycznie wykrywane podczas uruchamiania i dynamicznie ładowane / ładowane ponownie podczas działania programu.


źródło
10
Aplikacja Killer dla DI to testy jednostkowe. Jak zauważyłeś, większość oprogramowania sama nie potrzebuje dużo DI. Ale testy są również klientem tego kodu, dlatego powinniśmy zaprojektować testowalność. W szczególności oznacza to umieszczenie szwów w naszym projekcie, w których testy mogą obserwować zachowanie. Nie wymaga to fantazyjnego frameworku DI, ale wymaga, aby odpowiednie zależności były jawne i zastępowalne.
amon
4
Gdybyśmy my, deweloperzy, wiedzieli z góry, co może się zmienić, to byłaby połowa sukcesu. Byłem w trakcie długiego rozwoju, w którym zmieniło się wiele rzekomo „naprawionych” rzeczy. Sprawdzanie przyszłości tu i tam za pomocą DI nie jest niczym złym. Używanie go wszędzie przez cały czas przypomina kultowe programowanie ładunku.
Robbie Dee,
nadmierna komplikacja testu oznacza po prostu, że stają się one nieaktualne i oznaczone jako ignorowane, a nie aktualizowane w przyszłości. DI w starszym kodzie to fałszywa ekonomia. Wszystko, co będą mogli zrobić, to uczynić z ciebie „złego faceta” za umieszczenie całego „niestandardowego, zbyt złożonego kodu, którego nikt nie rozumie” w bazie kodu. Zostałeś ostrzeżony.
4
@JarrodRoberson, to naprawdę toksyczne środowisko. Jedną z kluczowych cech dobrego programisty jest to, że spoglądają wstecz na kod, który napisali w przeszłości i myślą: „ugh, czy naprawdę to napisałem? To takie złe!” To dlatego, że wyrosły jako programista i nauczyły się nowych rzeczy. Ktoś, kto patrzy na kod, który napisali pięć lat temu i nie widzi w tym nic złego, nie nauczył się niczego przez te pięć lat. Więc jeśli powiedzenie ludziom, że robili to źle w przeszłości, czyni cię wrogiem, to szybko uciekaj z tej kompanii, ponieważ są ślepą drużyną, która Cię powstrzyma i sprowadzi do niskiego poziomu.
David Arno,
1
Nie jestem pewien, czy zgadzam się z tymi złymi facetami, ale dla mnie kluczową kwestią jest to, że nie potrzebujesz DI do testów jednostkowych. Wdrożenie DI w celu zwiększenia możliwości testowania kodu jest po prostu łataniem problemu.
Ant P