Poinformuj mnie, że jestem wielkim fanem wstrzykiwania zależności (DI) i testów automatycznych. Mógłbym o tym rozmawiać cały dzień.
tło
Niedawno nasz zespół właśnie dostał ten duży projekt, który ma powstać od podstaw. Jest to strategiczna aplikacja o złożonych wymaganiach biznesowych. Oczywiście chciałem, żeby był ładny i czysty, co dla mnie oznaczało: łatwość konserwacji i testowania. Więc chciałem użyć DI.
Odporność
Problem był w naszym zespole, DI to tabu. Zostało to poruszone kilka razy, ale bogowie nie pochwalają. Ale to mnie nie zniechęciło.
Mój ruch
Może to zabrzmieć dziwnie, ale biblioteki innych firm zwykle nie są zatwierdzane przez nasz zespół architektów (pomyśl: „nie powinieneś mówić o Unity , Ninject , NHibernate , Moq lub NUnit , aby nie skaleczyć ci palca”). Zamiast więc używać sprawdzonego kontenera DI, napisałem niezwykle prosty kontener. Zasadniczo połączył wszystkie zależności podczas uruchamiania, wstrzykuje wszelkie zależności (konstruktor / właściwość) i usuwa wszelkie obiekty jednorazowe na końcu żądania WWW. Był bardzo lekki i po prostu zrobił to, czego potrzebowaliśmy. A potem poprosiłem ich o przejrzenie.
Odpowiedź
Krótko mówiąc. Spotkałem się z dużym oporem. Głównym argumentem było: „Nie musimy dodawać tej warstwy złożoności do już złożonego projektu”. Ponadto „To nie tak, że będziemy podłączać różne implementacje komponentów”. I „Chcemy, aby było to proste, jeśli to możliwe, po prostu umieść wszystko w jednym zestawie. DI to niepotrzebna złożoność bez korzyści”.
Wreszcie moje pytanie
Jak poradziłbyś sobie z moją sytuacją? Nie jestem dobry w prezentowaniu moich pomysłów i chciałbym wiedzieć, jak ludzie przedstawiliby swoje argumenty.
Oczywiście zakładam, że tak jak ja, wolisz używać DI. Jeśli się nie zgadzasz, powiedz dlaczego, abym mógł zobaczyć drugą stronę monety. Byłoby naprawdę interesujące zobaczyć punkt widzenia kogoś, kto się nie zgadza.
Aktualizacja
Dziękuję za odpowiedzi wszystkich. To naprawdę przedstawia rzeczy z perspektywy. Miło jest mieć inny zestaw oczu, aby przekazać ci opinię, piętnaście jest naprawdę niesamowite! To są naprawdę świetne odpowiedzi i pomogły mi dostrzec problem z różnych stron, ale mogę wybrać tylko jedną odpowiedź, więc wybiorę tę, która uzyskała najwyższy głos. Dziękujemy wszystkim za poświęcenie czasu na odpowiedź.
Zdecydowałem, że prawdopodobnie nie jest to najlepszy czas na wdrożenie DI i nie jesteśmy na to gotowi. Zamiast tego skoncentruję swoje wysiłki na przetestowaniu projektu i spróbuję przedstawić zautomatyzowane testy jednostkowe. Zdaję sobie sprawę, że pisanie testów jest dodatkowym kosztem i jeśli kiedykolwiek zostanie postanowione, że dodatkowy koszt nie jest tego wart, osobiście nadal postrzegałbym to jako sytuację wygraną, ponieważ projekt jest nadal testowalny. A jeśli kiedykolwiek testy lub DI będą wyborem w przyszłości, projekt z łatwością sobie z tym poradzi.
Odpowiedzi:
Biorąc kilka kontrargumentów:
Chcesz, aby system był testowalny. Być łatwo sprawdzalne trzeba patrzeć na wyśmianie różnych warstw projektu (bazy danych, komunikacji itd.), W tym przypadku będzie można podłączyć w różnych implementacjach komponentów.
Sprzedawaj DI na korzyściach testowania, jakie daje. Jeśli projekt jest złożony, będziesz potrzebować dobrych, solidnych testów jednostkowych.
Kolejną korzyścią jest to, że podczas kodowania interfejsów, jeśli masz lepszą implementację (szybszą, mniej wymagającą pamięci, cokolwiek) jednego ze swoich komponentów, użycie DI znacznie ułatwia wymianę starej implementacji na Nowy.
Mówię tu o tym, że musisz zająć się korzyściami, które przynosi DI, zamiast argumentować za DI ze względu na DI. Skłaniając ludzi do zaakceptowania oświadczenia:
Następnie przesuń problem. Musisz upewnić się, że DI jest odpowiedzią na to stwierdzenie. W ten sposób współpracownicy będą właścicielami rozwiązania, a nie poczują, że zostało im narzucone.
źródło
Z tego, co piszesz, nie sama DI jest tabu, ale użycie kontenerów DI, co jest inną rzeczą. Sądzę, że nie napotkasz żadnych problemów ze swoim zespołem, kiedy po prostu napiszesz niezależne komponenty, które otrzymują inne komponenty, których potrzebują, na przykład przez konstruktora. Tylko nie nazywaj tego „DI”.
Możesz pomyśleć, że takie komponenty nie używają kontenera DI są uciążliwe i masz rację, ale daj swojemu zespołowi czas, aby się o tym przekonał.
źródło
Odkąd zapytałeś, stanę po twojej stronie przeciwko DI.
Sprawa przeciwko wstrzyknięciu zależności
Istnieje reguła, którą nazywam „Zachowaniem złożoności”, która jest analogiczna do reguły w fizyce zwanej „Zachowaniem energii”. Stwierdza, że żadna ilość inżynierii nie może zmniejszyć całkowitej złożoności optymalnego rozwiązania problemu, wszystko, co może zrobić, to przesunąć tę złożoność. Produkty Apple nie są mniej złożone niż produkty Microsoftu, po prostu przenoszą złożoność z przestrzeni użytkownika do przestrzeni inżynierskiej. (Jest to jednak błędna analogia. Chociaż nie możesz wytwarzać energii, inżynierowie mają nieprzyjemny zwyczaj tworzenia złożoności na każdym kroku. W rzeczywistości wielu programistów wydaje się lubić zwiększanie złożoności i używanie magii, co jest z definicji złożonością, której programista nie do końca rozumie. Ta nadmierna złożoność może zostać zmniejszona.) Zwykle to, co wygląda na zmniejszenie złożoności, polega na przeniesieniu złożoności gdzie indziej, zwykle do biblioteki.
Podobnie DI nie zmniejsza złożoności łączenia obiektów klienckich z obiektami serwera. To, co robi, to wyjmuje go z Javy i przenosi do XML. To dobrze, ponieważ centralizuje wszystko w jednym (lub kilku) plikach XML, w których łatwo jest zobaczyć wszystko, co się dzieje i gdzie można to zmienić bez ponownej kompilacji kodu. Z drugiej strony jest to złe, ponieważ pliki XML nie są Java i dlatego nie są dostępne dla narzędzi Java. Nie można ich debugować w debuggerze, nie można używać analizy statycznej do śledzenia ich hierarchii połączeń itd. W szczególności błędy w środowisku DI stają się niezwykle trudne do wykrycia, zidentyfikowania i naprawy.
Dlatego o wiele trudniej jest udowodnić, że program działa poprawnie przy użyciu DI. Wiele osób twierdzi, że programy korzystające z DI są łatwiejsze do testowania , ale testowanie programów nigdy nie może udowodnić braku błędów . (Należy zauważyć, że powiązany dokument ma około 40 lat, a więc zawiera pewne tajemnicze odniesienia i przytacza niektóre nieaktualne praktyki, ale wiele podstawowych stwierdzeń nadal jest prawdą. W szczególności P <= p ^ n, gdzie P to prawdopodobieństwo, że system jest wolny od błędów, p to prawdopodobieństwo, że składnik systemu jest wolny od błędów, a n to liczba składników. Oczywiście to równanie jest nadmiernie uproszczone, ale wskazuje na ważną prawdę.) Awaria NASDAQ podczas IPO na Facebooku jest ostatnim przykładem, gdzietwierdzili, że spędzili 1000 godzin na testowaniu kodu i nadal nie odkryli błędów, które spowodowały awarię. 1000 godzin to pół osoby rocznie, więc to nie tak, że skakali podczas testów.
To prawda, że prawie nikt nigdy nie podejmuje (nie mówiąc już o zakończeniu) zadania formalnego udowodnienia, że dowolny kod jest poprawny, ale nawet słabe próby udowodnienia pokonały większość reżimów testowania w poszukiwaniu błędów. Silne próby udowodnienia, na przykład weryfikacja L4. , Niezmiennie odkrywają dużą liczbę błędów, których nie wykryły testy i prawdopodobnie nigdy ich nie wykryją, chyba że tester już znał dokładną naturę błędu. Wszystko, co sprawia, że kod jest trudniejszy do zweryfikowania przez inspekcję, może być postrzegane jako zmniejszające prawdopodobieństwo wykrycia błędów , nawet jeśli zwiększy to możliwość testowania.
Ponadto DI nie jest wymagany do testowania . Rzadko używam go do tego celu, ponieważ wolę testować system pod kątem prawdziwego oprogramowania w systemie, niż jakąś alternatywną wersję testową. Wszystko, co zmieniam w teście, jest (lub może równie dobrze) być przechowywane w plikach właściwości, które kierują program do zasobów w środowisku testowym, a nie do produkcji. Wolę spędzać czas na konfigurowaniu testowego serwera bazy danych z kopią produkcyjnej bazy danych, niż spędzać czas na kodowaniu próbnej bazy danych, ponieważ w ten sposób będę w stanie śledzić problemy z danymi produkcyjnymi (problemy z kodowaniem znaków to tylko jeden przykład ) i błędy integracji systemu, a także błędy w pozostałej części kodu.
Wstrzykiwanie zależności nie jest również wymagane w przypadku odwrócenia zależności . Możesz łatwo użyć statycznych metod fabrycznych, aby zaimplementować odwrócenie zależności. Tak właśnie było w latach 90.
W rzeczywistości, chociaż używam DI od dekady, jedyne zalety, które zapewnia, przekonuję, to możliwość skonfigurowania bibliotek stron trzecich do korzystania z bibliotek stron trzecich (np. Skonfigurowanie Springa do używania Hibernacji i obu do korzystania z Log4J ) oraz włączanie IoC za pośrednictwem serwerów proxy. Jeśli nie korzystasz z bibliotek stron trzecich i nie korzystasz z IoC, to nie ma to większego sensu i rzeczywiście dodaje nieuzasadnionej złożoności.
źródło
Z przykrością stwierdzam, że problem, który masz, biorąc pod uwagę to, co powiedzieli twoi współpracownicy w twoim pytaniu, ma raczej charakter polityczny niż techniczny. Należy pamiętać o dwóch mantrach:
Oczywiście, możesz przywołać wiele kontrargumentów z solidnymi przyczynami technicznymi i korzyściami, ale to postawi cię w złej sytuacji z resztą firmy. Mają już postawę, że tego nie chcą (ponieważ czują się swobodnie z tym, jak teraz wszystko działa). Może to nawet zaszkodzić twoim relacjom z kolegami z pracy, jeśli będziesz wytrwałość w tej sprawie. Zaufaj mi w tej sprawie, ponieważ stało się to ze mną i ostatecznie wyrzucono mnie kilka lat temu (młody i naiwny, że byłem); jest to sytuacja, w której nie chcesz się wciągać.
Chodzi o to, że musisz osiągnąć pewien poziom zaufania, zanim ludzie posuną się tak daleko, aby zmienić swój obecny sposób pracy. O ile nie masz zaufania i nie możesz decydować o takich rzeczach, takich jak pełnienie roli architekta lub kierownika technicznego, niewiele możesz zrobić, aby odwrócić kolegów. Przekonanie i wysiłek (podobnie jak podejmowanie drobnych kroków usprawniających) zajmie dużo czasu, aby zdobyć zaufanie w celu zmiany kultury firmy. Mówimy o okresie wielu miesięcy lub lat.
Jeśli okaże się, że obecna kultura firmy nie współpracuje z tobą, to przynajmniej wiesz jeszcze jedną rzecz, o którą należy zapytać podczas następnej rozmowy kwalifikacyjnej. ;-)
źródło
Nie używaj DI. Zapomnij o tym.
Utwórz implementację wzorca fabrycznego GoF, który „tworzy” lub „znajduje” określone zależności i przekazuje je do obiektu i pozostawia go przy tym, ale nie nazywaj go DI i nie twórz złożonej uogólnionej struktury. Zachowaj prostotę i aktualność. Upewnij się, że zależności i twoje klasy są takie, że możliwe jest przejście na DI; ale fabryka powinna pozostać mistrzem i mieć bardzo ograniczony zakres.
Z czasem możesz mieć nadzieję, że wzrośnie, a nawet pewnego dnia przejdzie na DI. Niech potencjalni klienci dojdą do wniosku we własnym czasie i nie naciskaj. Nawet jeśli nigdy się do tego nie dostaniesz, przynajmniej twój kod ma pewne zalety DI, na przykład kod testowany jednostkowo.
źródło
Widziałem projekty, które doprowadziły DI do skrajności, aby przeprowadzać testy jednostkowe i kpić z obiektów. Tak bardzo, że konfiguracja DI stała się językiem programowania sama w sobie z taką ilością konfiguracji potrzebną do uruchomienia systemu, jak rzeczywisty kod.
Czy system cierpi teraz z powodu braku odpowiednich wewnętrznych interfejsów API, których nie można przetestować? Czy masz nadzieję, że przejście systemu na model DI pozwoli lepiej przeprowadzić test jednostkowy? Nie potrzebujesz kontenera, aby przetestować kod w jednostce. Użycie modelu DI pozwoliłoby na łatwiejsze testowanie jednostkowe na obiektach próbnych, ale nie jest to wymagane.
Nie musisz jednocześnie przełączać całego systemu, aby był kompatybilny z DI. Nie powinieneś też arbitralnie pisać testów jednostkowych. Zmień kod, gdy znajdziesz go w swojej funkcji i zgłoszeń błędów. Następnie upewnij się, że dotknięty kod jest łatwy do przetestowania.
Pomyśl o CodeRot jako o chorobie. Nie chcesz zacząć arbitralnie hakować rzeczy. Masz pacjenta, widzisz obolałe miejsce, więc zaczynasz naprawiać to miejsce i otaczające go rzeczy. Po oczyszczeniu tego obszaru przejdź do następnego. Wcześniej czy później dotkniesz większości kodu i nie wymagało to tej „ogromnej” zmiany architektonicznej.
Nie podoba mi się, że testy DI i Mock wzajemnie się wykluczają. A po obejrzeniu projektu, który z braku lepszego słowa zwariował przy użyciu DI, byłbym zmęczony, nie widząc korzyści.
Istnieje kilka miejsc, w których warto korzystać z DI:
Wymaganie od każdego programisty, aby wiedział, gdzie jest plik konfiguracyjny DI (zdaję sobie sprawę, że kontenery mogą być tworzone za pomocą kodu, ale dlaczego miałbyś to zrobić). Gdy chcą przeprowadzić RegEx, jest frustrujące. Och, widzę, że jest IRegExCompiler, DefaultRegExCompiler i SuperAwesomeRegExCompiler. Jednak w żadnym miejscu w kodzie nie mogę przeprowadzić analizy, aby zobaczyć, gdzie używana jest opcja Domyślna i używana jest funkcja SuperAwesome.
Moja osobowość zareagowałaby lepiej, gdyby ktoś pokazał mi coś lepszego, a następnie próbował mnie przekonać swoimi słowami. Jest coś do powiedzenia na temat kogoś, kto poświęciłby czas i wysiłek, aby ulepszyć system. Nie uważam, że wielu programistów dba o to, aby produkt był naprawdę lepszy. Jeśli możesz udać się do nich z prawdziwymi zmianami w systemie i pokazać im, w jaki sposób uczyniło to dla ciebie lepszym i łatwiejszym, jest to warte więcej niż jakiekolwiek badania online.
Podsumowując, najlepszym sposobem na zmianę w systemie jest powolne ulepszanie kodu, którego aktualnie dotykasz, z każdym przejściem. Wcześniej czy później będziesz w stanie przekształcić system w coś lepszego.
źródło
DI nie wymaga inwazyjnej inwersji kontenerów kontrolnych ani ramek próbnych. Możesz oddzielić kod i zezwolić na DI za pomocą prostej konstrukcji. Testy jednostkowe można przeprowadzać za pomocą wbudowanych funkcji programu Visual Studio.
Szczerze mówiąc, twoi współpracownicy mają rację. Niewłaściwe korzystanie z tych bibliotek (a ponieważ nie są z nimi zaznajomione, wystąpią problemy z nauką) spowoduje problemy. Ostatecznie kod ma znaczenie. Nie psuj swojego produktu do testów.
Pamiętaj tylko, że w tych scenariuszach niezbędny jest kompromis.
źródło
Nie sądzę, abyś mógł już sprzedawać DI, ponieważ jest to decyzja dogmatyczna (lub, jak @Spoike nazwał to grzeczniej, politycznie ).
W tej sytuacji sprzeczanie się z powszechnie uznanymi najlepszymi praktykami, takimi jak zasady projektowania SOLID, nie jest już możliwe.
Ktoś, kto ma większą władzę polityczną niż ty, podjął (być może złą) decyzję, aby nie używać DI.
Po drugiej stronie sprzeciwiłeś się tej decyzji, wdrażając własny kontener, ponieważ używanie ustalonego kontenera było zabronione. Ta polityka faktów dokonanych , którą próbowałeś, mogła pogorszyć sytuację.
Analiza
Moim zdaniem, DI bez testdriven development (TDD) i testowanie jednostkowe nie ma znaczących korzyści, które przewyższają początkowe dodatkowe koszty.
Czy to naprawdę problem?
czy o to bardziej chodzi
[Aktualizacja]
Jeśli widzisz swój projekt na podstawie klasycznych błędów w widoku zarządzania projektem , widzisz
podczas gdy inni widzą
źródło
Jeśli okaże się, że musisz „sprzedać” zasadę swojemu zespołowi, prawdopodobnie jesteś już kilka kilometrów na niewłaściwej ścieżce.
Nikt nie chce wykonywać dodatkowej pracy, więc jeśli to, co próbujesz sprzedać, wygląda dla nich jak dodatkowa praca, to nie przekonasz ich poprzez Powtarzane Wywołanie Przełożonego. Muszą wiedzieć, że pokazujesz im sposób na zmniejszenie obciążenia pracą, a nie jej zwiększenie.
DI jest często postrzegane jako dodatkowa złożoność z obietnicą potencjalnej zmniejszonej złożoności później , która ma wartość, ale nie tyle dla kogoś, kto próbuje zmniejszyć swoje wstępne obciążenie. W wielu przypadkach DI jest tylko kolejną warstwą YAGNI . Koszt tej złożoności nie jest równy zero, w rzeczywistości jest dość wysoki dla zespołu, który nie jest przyzwyczajony do tego typu wzorców. I to są prawdziwe dolary, które są wrzucane do wiadra, które może nigdy nie przynieść żadnych korzyści.
Połóż to na swoim miejscu
Najlepszym rozwiązaniem jest użycie DI tam, gdzie jest rażąco użyteczne, a nie tylko tam, gdzie jest to powszechne. Na przykład wiele aplikacji używa DI do wybierania i konfigurowania baz danych. A jeśli twoja aplikacja jest dostarczana z warstwą bazy danych, to kuszące miejsce na jej umieszczenie. Ale jeśli aplikacja jest dostarczana z wbudowaną niewidoczną dla użytkownika bazą danych, która w przeciwnym razie jest ściśle zintegrowana z kodem, wówczas szanse na konieczność zastąpienia bazy danych w czasie wykonywania zbliżają się do zera. A sprzedaż DI, mówiąc: „słuchaj, możemy łatwo zrobić coś, czego nigdy, nigdy, nigdy nie będziemy chcieli robić”, prawdopodobnie sprawi, że znajdziesz się na liście „ten facet ma złe pomysły” z szefem.
Ale powiedz zamiast tego, że masz wyjście debugowania, które normalnie dostaje IFDEF w wydaniu. I za każdym razem, gdy użytkownik ma problem, zespół pomocy technicznej musi wysłać mu kompilację debugowania, aby mógł uzyskać dane wyjściowe dziennika. Możesz użyć DI tutaj, aby pozwolić klientowi przełączać się między sterownikami dziennika -
LogConsole
dla programistów,LogNull
klientów iLogNetwork
klientów z problemami. Jedna zunifikowana kompilacja, konfigurowalna w czasie wykonywania.Wtedy nie musisz nawet nazywać go DI, zamiast tego po prostu nazywa się to „naprawdę dobry pomysł Mela” i jesteś teraz na liście dobrych pomysłów zamiast złego. Ludzie zobaczą, jak dobrze działa wzór i zaczną go używać tam, gdzie ma to sens w kodzie.
Kiedy właściwie sprzedasz pomysł, ludzie nie będą wiedzieli, że ich przekonałeś.
źródło
Oczywiście Inversion of control (IoC) ma wiele zalet: upraszcza kod, dodaje magii, która wykonuje typowe zadania itp.
Jednak:
Dodaje wiele zależności. Prosta aplikacja hello world napisana przy użyciu Springa może ważyć kilkanaście MB, a widziałem, że Spring dodał tylko, aby uniknąć kilku linii konstruktora i seterów.
Dodaje wyżej wspomnianą magię , która jest trudna do zrozumienia dla osób postronnych (na przykład deweloperów GUI, którzy muszą wprowadzić pewne zmiany w warstwie biznesowej). Zobacz świetny artykuł Innowacyjne umysły nie myślą podobnie - New York Times , w którym opisano, w jaki sposób eksperci nie doceniają tego efektu .
Utrudnia śledzenie użycia klas. IDE, takie jak Eclipse, ma ogromne możliwości śledzenia wykorzystania danych wywołań klas lub metod. Ale ten ślad ginie, gdy wywoływany jest zastrzyk zależności i odbicie.
Wykorzystanie IoC zwykle nie kończy się na wstrzykiwaniu zależności, kończy się niezliczonymi serwerami proxy, a otrzymujesz ślad stosu liczący setki linii, z których 90% to proxy i wywoływane przez odbicie. To znacznie komplikuje analizę śladu stosu.
Tak więc, będąc wielkim fanem Spring i IoC, ostatnio musiałem przemyśleć powyższe pytania. Niektóre z moich współpracowników to programiści internetowi zaczerpnięci ze świata Java ze świata rządzonego przez Pythona i PHP, a rzeczy oczywiste dla javaistów są dla nich czymś oczywistym. Niektóre z moich innych kolegów robią sztuczki (oczywiście nieudokumentowane), co bardzo utrudnia znalezienie błędu. Oczywiście niewłaściwe użycie IoC sprawia, że są dla nich lżejsze;)
Moja rada, jeśli narzucisz użycie IoC w swojej pracy, będziesz odpowiedzialny za wszelkie nadużycia, które popełni ktoś;)
źródło
Myślę, że ChrisF ma coś dobrego. Nie sprzedawaj DI jako takiego. Ale sprzedaj pomysł testowania kodu przez jednostkę.
Jednym z podejść do umieszczenia kontenera DI w architekturze może być powolne pozwalanie testom na wprowadzenie implementacji w coś, co dobrze pasuje do tej architektury. Np. Powiedzmy, że pracujesz na kontrolerze w aplikacji ASP.NET MVC. Powiedz, że piszesz klasę w ten sposób:
To pozwala pisać testy jednostkowe niezwiązane z faktyczną implementacją repozytorium użytkowników. Ale klasa nadal działa bez kontenera DI, aby go utworzyć. Konstruktor bez parametrów utworzy go z domyślnym repozytorium.
Jeśli współpracownicy zauważą, że prowadzi to do znacznie czystszych testów, być może zdadzą sobie sprawę, że jest to lepszy sposób. Może uda ci się je nakłonić do takiego kodowania.
Kiedy zdadzą sobie sprawę, że jest to lepszy projekt niż UserController, który wie o konkretnej implementacji UserRepository, możesz zrobić następny ruch i pokazać im, że możesz mieć komponent, kontener DI, który może automatycznie zapewnić wymagane wystąpienia.
źródło
Być może najlepiej jest cofnąć się i zadać sobie pytanie, dlaczego chcesz wprowadzić pojemniki DI? Jeśli ma to poprawić testowalność, Twoim pierwszym zadaniem jest skłonienie zespołu do kupienia testów jednostkowych. Osobiście bardziej martwi mnie brak testów jednostkowych niż brak pojemnika DI.
Uzbrojony w kompleksowy pakiet lub zestawy testów jednostkowych, możesz śmiało rozpocząć ewolucję swojego projektu w miarę upływu czasu. Bez nich zmagasz się z coraz bardziej wymagającą walką, aby projekt był dostosowany do zmieniających się wymagań, która ostatecznie przegrywa wiele zespołów.
Tak więc moja rada to najpierw przetestowanie i refaktoryzacja jednostek mistrzowskich, a następnie wprowadzenie pojemników DI i wreszcie pojemników DI.
źródło
Słyszę tutaj kilka dużych głosów nr z twojego zespołu:
„Brak bibliotek innych firm” - AKA „Not Invented Here” lub „NIH”. Istnieje obawa, że wdrażając bibliotekę strony trzeciej, a tym samym uzależniając bazę kodów od niej, jeśli w bibliotece znajdzie się błąd lub luka w zabezpieczeniach, a nawet ograniczenie, które trzeba pokonać, uzależnia się od tej trzeciej strony, aby naprawić swoją bibliotekę, aby Twój kod działał. W międzyczasie, ponieważ jego „twój” program, który zawiódł spektakularnie, został zhakowany lub nie robi tego, o co prosili, nadal ponosisz odpowiedzialność (a twórcy stron trzecich powiedzą, że ich narzędzie jest „takie, jakie jest” ", używaj na własne ryzyko).
Kontrargumentem jest to, że kod rozwiązujący problem już istnieje w bibliotece innej firmy. Czy budujesz komputery od podstaw? Czy napisałeś Visual Studio? Czy unikasz wbudowanych bibliotek w .NET? Nie. Masz zaufanie do Intel / AMD i Microsoft, a oni cały czas znajdują błędy (i nie zawsze je szybko naprawiają). Dodanie biblioteki innej firmy pozwala szybko uzyskać funkcjonalną bazę kodu, bez konieczności ponownego wymyślania koła. A armia prawników Microsoftu będzie się śmiać z twojej twarzy, gdy powiesz im, że błąd w bibliotekach kryptograficznych .NET powoduje, że są oni odpowiedzialni za incydent włamania się do twojego klienta podczas korzystania z programu .NET. Podczas gdy Microsoft z pewnością skoczy do naprawy luk w zabezpieczeniach „zerowej godziny” w dowolnym ze swoich produktów,
„Chcemy upchnąć wszystko w jednym zestawie” - w rzeczywistości nie. Przynajmniej w .NET, jeśli zmienisz jeden wiersz kodu, cały zestaw zawierający ten wiersz kodu musi zostać odbudowany. Dobry projekt kodu opiera się na wielu zestawach, które można odbudować tak niezależnie, jak to możliwe (lub przynajmniej odbudować zależności, a nie zależności). Jeśli NAPRAWDĘ chcą wydać plik EXE, który jest tylko plikiem EXE (który w niektórych okolicznościach ma wartość), jest na to odpowiednia aplikacja; ILMerge.
„DI to tabu” - WTF? DI jest przyjętą najlepszą praktyką w dowolnym języku O / O z konstrukcją kodu „interfejs”. W jaki sposób Twój zespół przestrzega metodologii projektowania GRASP lub SOLID, jeśli nie luźno łączą zależności między obiektami? Jeśli nie zawracają sobie głowy, to utrwalają fabrykę spaghetti, która sprawia, że jest to skomplikowane bagno. Teraz DI zwiększa złożoność, zwiększając liczbę obiektów, i chociaż ułatwia podstawienie innej implementacji, utrudnia zmianę samego interfejsu (przez dodanie dodatkowej warstwy obiektu kodu, który musi się zmienić).
„Nie musimy dodawać tej warstwy złożoności do już złożonego projektu” - mogą mieć rację. Kluczową rzeczą do rozważenia przy opracowywaniu kodu jest YAGNI; „Nie będziesz go potrzebować”. Projekt, który jest SOLIDNIE konstruowany od samego początku, jest projektem SOLIDNYM „na wiarę”; nie wiesz, czy będziesz potrzebować łatwości zmiany, jaką zapewniają projekty SOLID, ale masz przeczucie. Chociaż możesz mieć rację, możesz również bardzo się mylić; bardzo SOLIDNY projekt może być postrzegany jako „kod lasagna” lub „kod ravioli”, mający tak wiele warstw lub kawałków wielkości kęsa, że dokonywanie pozornie błahych zmian staje się trudne, ponieważ musisz przekopać się przez tak wiele warstw i / lub prześledzić taki zawiły stos wywołań.
Popieram zasadę trzech ostrzeżeń: spraw, by działała, czyściła, uczyniła SOLIDNYM. Kiedy po raz pierwszy piszesz wiersz kodu, musi on po prostu działać i nie dostajesz punktów za projekty wieży z kości słoniowej. Załóżmy, że jest to jednorazowe i rób to, co musisz zrobić. Za drugim razem, gdy spojrzysz na ten kod, prawdopodobnie chcesz go rozwinąć lub ponownie użyć i nie jest to jednorazowy pomysł. W tym momencie uczyń go bardziej eleganckim i zrozumiałym, zmieniając go; uprościć skomplikowaną logikę, wyodrębnić powtarzający się kod do pętli i / lub wywołań metod, dodać kilka komentarzy tu i tam dla każdego kludge, który musisz zostawić na miejscu itp. Za trzecim razem, gdy spojrzysz na ten kod, jest to prawdopodobnie wielka sprawa. Teraz powinieneś przestrzegać reguł SOLID; wstrzykiwać zależności zamiast je konstruować, wyodrębniać klasy, aby przechowywać metody, które są mniej spójne z „mięsem” kodu,
„To nie tak, że będziemy podłączać różne implementacje” - Pan, proszę pana, ma zespół, który nie wierzy w testy jednostkowe na waszych rękach. Uważam, że nie jest możliwe napisanie testu jednostkowego, który działa w izolacji i nie ma żadnych skutków ubocznych, bez możliwości „kpienia” z obiektów, które mają te skutki uboczne. Każdy nietrywialny program ma skutki uboczne; jeśli twój program odczytuje lub zapisuje do bazy danych, otwiera lub zapisuje pliki, komunikuje się przez gniazdo sieciowe lub peryferyjne, uruchamia inny program, rysuje okna, wyświetla dane wyjściowe na konsoli lub w inny sposób wykorzystuje pamięć lub zasoby poza „piaskownicą” swojego procesu, ma efekt uboczny. Jeśli nie robi żadnej z tych rzeczy, co robi i dlaczego powinienem to kupić?
Szczerze mówiąc, testy jednostkowe nie są jedynym sposobem na udowodnienie, że program działa; możesz pisać testy integracji, kod do algorytmów sprawdzonych matematycznie itp. Jednak testy jednostkowe bardzo szybko pokazują, że przy danych wejściowych A, B i C ten fragment kodu generuje oczekiwany X (lub nie), a tym samym, że kod działa poprawnie (lub nie działa) w danym przypadku. Zestawy testów jednostkowych mogą wykazać z dużym stopniem pewności, że kod działa we wszystkich przypadkach zgodnie z oczekiwaniami, a także zapewniają coś, czego nie jest w stanie udowodnić matematycznie; demonstracja, że program STILL działa tak, jak powinien. Matematyczny dowód algorytmu nie dowodzi, że został on poprawnie zaimplementowany, ani nie dowodzi, że wprowadzone zmiany zostały poprawnie zaimplementowane i go nie złamały.
Krótko mówiąc, jeśli ten zespół nie widzi żadnej wartości w tym, co zrobiłby DI, to nie będzie go wspierać, a wszyscy (przynajmniej wszyscy starsi faceci) muszą coś kupić, aby dokonać prawdziwej zmiany wydarzyć się. Kładą duży nacisk na YAGNI. Kładziesz duży nacisk na SOLID i automatyczne testy. Gdzieś pośrodku leży prawda.
źródło
Zachowaj ostrożność, dodając dodatkowe funkcje (takie jak DI) do projektu, gdy nie zostaną zatwierdzone. Jeśli masz plan projektu z ograniczeniami czasowymi, możesz wpaść w pułapkę braku czasu na ukończenie zatwierdzonych funkcji.
Nawet jeśli nie korzystasz teraz z DI, weź udział w testach, abyś dokładnie wiedział, jakie są oczekiwania na testy w Twojej firmie. Będzie inny projekt i będziesz mógł porównać problemy z testowaniem bieżącego projektu w porównaniu z DI.
Jeśli nie używasz DI, możesz stać się kreatywny i sprawić, że kod DI będzie „gotowy”. Użyj konstruktora bez parametrów, aby wywołać inne konstruktory:
źródło
Myślę, że jedna z ich największych obaw jest wręcz przeciwna: DI nie komplikuje projektu. W większości przypadków zmniejsza to złożoność.
Pomyśl tylko bez DI, co uzyskasz zależny obiekt / komponent, którego potrzebujesz? Zwykle odbywa się to przez wyszukiwanie w repozytorium, uzyskanie singletona lub utworzenie instancji.
Pierwsze 2 zawiera dodatkowy kod w zlokalizowaniu zależnego komponentu, w świecie DI nie zrobiłeś nic, aby wykonać wyszukiwanie. Złożoność komponentów jest w rzeczywistości zmniejszona.
Jeśli tworzysz się w niektórych przypadkach, co jest nieodpowiednie, powoduje to nawet większą złożoność. Musisz wiedzieć, jak poprawnie utworzyć obiekt, musisz wiedzieć, jakie wartości zainicjować obiekt do stanu, w którym można go uruchomić, wszystkie te powodują dodatkową złożoność kodu.
Zatem DI nie sprawia, że projekt jest skomplikowany, ale upraszcza go, upraszczając komponenty.
Innym ważnym argumentem jest to, że nie zamierzasz podłączać różnych implementacji. Tak, to prawda, że w kodzie produkcyjnym często nie ma wielu różnych implementacji w prawdziwym kodzie produkcyjnym. Jest jednak jedno ważne miejsce, które wymaga innej implementacji: Mock / Stub / Test Doubles in Tests Unit. Aby DI działało, w większości przypadków opiera się na modelu programistycznym opartym na interfejsie, z kolei umożliwia kpienie / kasowanie w testach jednostkowych. Oprócz zastąpienia implementacji „projektowanie zorientowane na interfejs” zapewnia również czystsze i lepsze projektowanie. Oczywiście nadal możesz projektować z interfejsem bez DI. Jednak testy jednostkowe i DI zmuszają cię do przyjęcia takich praktyk.
O ile twoi „bogowie” w towarzystwie nie uważają, że: „prostszy kod”, „testy jednostkowe”, „modularyzacja”, „pojedyncza odpowiedzialność” itp. Są czymś przeciwko czemu, nie powinno być powodu, aby odrzucali używanie DI.
źródło
„Chcemy, aby było to proste, jeśli to możliwe, po prostu umieść wszystko w jednym zestawie. DI to niepotrzebna złożoność bez korzyści”.
Korzyścią jest to, że promuje projektowanie interfejsów i umożliwia łatwe kpiny. Co w konsekwencji ułatwia testowanie jednostkowe. Testy jednostkowe zapewniają niezawodny, modułowy i łatwy w utrzymaniu kod. Jeśli twój szef nadal utrzymuje zły pomysł, cóż, nic nie możesz zrobić - zaakceptowana najlepsza praktyka branżowa, przeciwko której się kłócą.
Poproś, aby spróbowali napisać test jednostkowy w środowisku DI i kpiącą bibliotekę, a wkrótce nauczą się go kochać.
źródło
new
wszędzie i pisania klas fabrycznych, ponieważ wszystkie te komponenty są na swoim miejscu.Czy pracujesz z tymi ludźmi przy jakichkolwiek mniejszych projektach, czy tylko jednym dużym? Łatwiej jest przekonać ludzi do wypróbowania czegoś nowego w projekcie drugorzędnym / wyższym niż zespół / firmy z chleba i masła. Głównymi powodami są to, że jeśli się nie powiedzie, koszt usunięcia / wymiany jest znacznie mniejszy i jest o wiele mniej pracy, aby uzyskać go wszędzie w małym projekcie. W dużym istniejącym masz albo duży początkowy koszt wprowadzenia zmiany wszędzie na raz, albo dłuższy okres, w którym kod jest mieszanką starego / nowego podejścia zwiększającego tarcie, ponieważ musisz pamiętać, w jaki sposób każda klasa jest zaprojektowana pracować.
źródło
Chociaż jedną z rzeczy, które promują DI, są testy jednostkowe, ale uważam, że w niektórych przypadkach dodaje ono złożoności.
Lepiej mieć samochód trudny w testowaniu, ale łatwy w naprawie niż samochód łatwy w testowaniu i trudny w naprawie.
Wydaje mi się również, że grupa działająca na rzecz DI przedstawia swoje pomysły tylko poprzez wpływanie na to, że niektóre istniejące podejścia projektowe są złe.
jeśli mamy metodę wykonującą 5 różnych funkcji
DI-People mówi:
Uważam, że lepiej jest stworzyć nasz własny wzór projektowy, który bardziej odpowiada potrzebom biznesowym, niż być pod wpływem DI.
na korzyść DI, może to pomóc w niektórych sytuacjach, na przykład, jeśli masz setki implementacji dla tego samego interfejsu zależy od selektora, a implementacje te różnią się znacznie logiką, w tym przypadku wybrałbym DI lub DI podobne techniki. ale jeśli mamy kilka implementacji dla tego samego interfejsu, nie potrzebujemy DI.
źródło