Ile zastrzyków jest dopuszczalnych w jednej klasie, gdy stosuje się wstrzykiwanie zależności


9

Używam Unity w języku C # do wstrzykiwania zależności, ale pytanie powinno mieć zastosowanie do każdego języka i struktury używającej wstrzykiwania zależności.

Staram się przestrzegać zasady SOLID, dlatego otrzymałem wiele abstrakcji. Ale teraz zastanawiam się, czy istnieje najlepsza praktyka dla liczby zastrzyków, które jedna klasa powinna wstrzyknąć?

Na przykład mam repozytorium z 9 zastrzykami. Czy będzie to trudne do odczytania dla innego programisty?

Zastrzyki mają następujące obowiązki:

  • IDbContextFactory - tworzenie kontekstu dla bazy danych.
  • IMapper - Mapowanie od encji do modeli domen.
  • IClock - Abstracts DateTime.Now, aby pomóc w testach jednostkowych.
  • IPerformanceFactory - mierzy czas wykonania dla określonych metod.
  • ILog - Log4net do logowania.
  • ICollectionWrapperFactory - Tworzy kolekcje (które rozszerzają IEnumerable).
  • IQueryFilterFactory - Generuje zapytania na podstawie danych wejściowych, które będą wykonywać zapytania do bazy danych db.
  • IIdentityHelper - pobiera zalogowanego użytkownika.
  • IFaultFactory - Utwórz różne wyjątki błędów (używam WCF).

Tak naprawdę nie jestem zawiedziony tym, jak przekazałem obowiązki, ale zaczynam się martwić o czytelność.

Więc moje pytania:

Czy istnieje limit liczby zastrzyków, które klasa powinna mieć? A jeśli tak, jak tego uniknąć?

Czy wiele zastrzyków ogranicza czytelność, czy faktycznie to poprawia?


2
Mierzenie jakości za pomocą liczb jest zwykle tak samo złe, jak płacenie liniami kodu, które piszesz miesięcznie. Podałeś jednak rzeczywisty przykład zależności, co jest doskonałym pomysłem. Gdybym był tobą, przeformułowałbym twoje pytanie, aby usunąć koncepcję liczenia i ścisłego limitu, i skupić się zamiast jakości per se. Podczas przeformułowywania należy uważać, aby nie sprecyzować pytania.
Arseni Mourzenko

3
Ile argumentów konstruktora jest dopuszczalnych? IoC tego nie zmienia .
Telastyn

Dlaczego ludzie zawsze chcą granic bezwzględnych?
Marjan Venema

1
@Telastyn: niekoniecznie. Zmiany w IoC polegają na tym, że zamiast polegać na klasach statycznych / singletonach / zmiennych globalnych, wszystkie zależności są bardziej scentralizowane i bardziej „widoczne”.
Arseni Mourzenko

@MarjanVenema: ponieważ znacznie ułatwia życie. Jeśli znasz dokładnie maksymalny LOC na metodę lub maksymalną liczbę metod w klasie lub maksymalną liczbę zmiennych w metodzie, a to jest jedyna rzecz, która ma znaczenie, łatwo jest zakwalifikować kod jako dobry lub zły, ponieważ a także „naprawić” zły kod. To niefortunne, że prawdziwe życie jest znacznie bardziej skomplikowane, a wiele wskaźników jest w większości nieistotnych.
Arseni Mourzenko

Odpowiedzi:


11

Zbyt wiele zależności może wskazywać, że sama klasa robi zbyt wiele. Aby ustalić, czy nie robi to zbyt wiele:

  • Spójrz na samą klasę. Czy miałoby sens podzielenie go na dwa, trzy, cztery? Czy ma to sens jako całość?

  • Spójrz na typy zależności. Które są specyficzne dla domeny, a które „globalne”? Na przykład nie zastanawiałbym się ILogna tym samym poziomie co IQueryFilterFactory: pierwszy z nich byłby dostępny w większości klas biznesowych, jeśli używają rejestrowania. Z drugiej strony, jeśli znajdziesz wiele zależności specyficznych dla domeny, może to oznaczać, że klasa robi za dużo.

  • Spójrz na zależności, które można zastąpić wartościami.

    IClock - Abstracts DateTime.Now, aby pomóc w testach jednostkowych.

    Można to łatwo zastąpić DateTime.Nowprzekazaniem bezpośrednio do metod, które wymagają znajomości aktualnego czasu.

Patrząc na rzeczywiste zależności, nie widzę niczego, co wskazywałoby na złe rzeczy:

  • IDbContextFactory - tworzenie kontekstu dla bazy danych.

    OK, prawdopodobnie jesteśmy w warstwie biznesowej, w której klasy współdziałają z warstwą dostępu do danych. Wygląda w porządku.

  • IMapper - Mapowanie od encji do modeli domen.

    Trudno powiedzieć cokolwiek bez ogólnego obrazu. Może się zdarzyć, że architektura jest zła i mapowanie powinno być wykonane bezpośrednio przez warstwę dostępu do danych lub architektura może być w porządku. We wszystkich przypadkach sensowne jest posiadanie tej zależności tutaj.

    Innym wyborem byłoby podzielenie klasy na dwie części: jedna dotyczy mapowania, a druga logiki biznesowej. Stworzyłoby to de facto warstwę, która oddzieli dalej BL od DAL. Jeśli odwzorowania są złożone, może to być dobry pomysł. W większości przypadków po prostu zwiększyłoby to niepotrzebną złożoność.

  • IClock - Abstracts DateTime.Now, aby pomóc w testach jednostkowych.

    Prawdopodobnie nie jest bardzo użyteczny mieć osobny interfejs (i klasę) tylko po to, aby uzyskać aktualny czas. Po prostu przekazałbym DateTime.Nowmetody, które wymagają aktualnego czasu.

    Oddzielna klasa może mieć sens, jeśli istnieją inne informacje, takie jak strefy czasowe lub zakresy dat itp.

  • IPerformanceFactory - mierzy czas wykonania dla określonych metod.

    Zobacz następny punkt.

  • ILog - Log4net do logowania.

    Taka transcendentalna funkcjonalność powinna należeć do frameworka, a rzeczywiste biblioteki powinny być wymienne i konfigurowalne w czasie wykonywania (na przykład poprzez app.config w .NET).

    Niestety, nie jest to (jeszcze) przypadek, który pozwala albo wybrać bibliotekę i pozostać przy niej, albo utworzyć warstwę abstrakcji, aby móc później zamienić biblioteki w razie potrzeby. Jeśli masz zamiar być niezależnym od wyboru biblioteki, idź do niego. Jeśli masz pewność, że będziesz nadal korzystać z biblioteki przez lata, nie dodawaj abstrakcji.

    Jeśli biblioteka jest zbyt złożona, aby ją użyć, wzór elewacji ma sens.

  • ICollectionWrapperFactory - Tworzy kolekcje (które rozszerzają IEnumerable).

    Zakładam, że tworzy to bardzo specyficzne struktury danych, które są używane przez logikę domeny. Wygląda jak klasa użyteczności. Zamiast tego użyj jednej klasy na strukturę danych z odpowiednimi konstruktorami. Jeśli logika inicjalizacji jest nieco skomplikowana w celu dopasowania do konstruktora, użyj statycznych metod fabrycznych. Jeśli logika jest jeszcze bardziej złożona, użyj wzorca fabrycznego lub konstruktora.

  • IQueryFilterFactory - Generuje zapytania na podstawie danych wejściowych, które będą wykonywać zapytania do bazy danych db.

    Dlaczego nie ma tego w warstwie dostępu do danych? Dlaczego jest Filterw nazwie?

  • IIdentityHelper - pobiera zalogowanego użytkownika.

    Nie jestem pewien, dlaczego istnieje Helperprzyrostek. We wszystkich przypadkach inne sufiksy też nie będą szczególnie wyraźne ( IIdentityManager?)

    W każdym razie sensowne jest posiadanie tej zależności tutaj.

  • IFaultFactory - Utwórz różne wyjątki błędów (używam WCF).

    Czy logika jest tak złożona, że ​​wymaga użycia wzorca fabrycznego? Dlaczego do tego celu stosuje się wstrzykiwanie zależności? Czy zamieniłbyś tworzenie wyjątków między kodem produkcyjnym a testami? Dlaczego?

    Spróbowałbym zmienić to na proste throw new FaultException(...). Jeśli jakieś globalne informacje powinny zostać dodane do wszystkich wyjątków przed propagacją ich do klienta, WCF prawdopodobnie ma mechanizm, w którym wychwytujesz nieobsługiwany wyjątek i możesz go zmienić i ponownie przekazać klientowi.

Czy istnieje limit liczby zastrzyków, które klasa powinna mieć? A jeśli tak, jak tego uniknąć?

Mierzenie jakości za pomocą liczb jest zwykle tak samo złe, jak płacenie liniami kodu, które piszesz miesięcznie. Możesz mieć dużą liczbę zależności w dobrze zaprojektowanej klasie, ponieważ możesz mieć głupią klasę, używając kilku zależności.

Czy wiele zastrzyków ogranicza czytelność, czy faktycznie to poprawia?

Wiele zależności utrudnia podążanie za logiką. Jeśli logika jest trudna do przestrzegania, klasa prawdopodobnie robi zbyt wiele i powinna zostać podzielona.


Dziękuję za komentarze i poświęcony czas. Głównie o FaultFactory, który zamiast tego zostanie przeniesiony do logiki WCF. Zachowam IClock, ponieważ oszczędza życie, gdy TDD uruchamia aplikację. Kilka razy chcesz się upewnić, że określona wartość została ustawiona na określony czas. W takim razie DateTime.Now nie zawsze wystarczy, ponieważ nie można go wyśmiewać.
smoksnes

7

Jest to klasyczny przykład DI informujący, że Twoja klasa prawdopodobnie staje się zbyt duża, aby być pojedynczą klasą. Jest to często interpretowane jako „wow, DI czyni moich konstruktorów większymi od Jowisza, ta technika jest okropna”, ale RZECZYWISTO mówi ci, że „twoja klasa ma mnóstwo zależności”. Wiedząc o tym, możemy albo

  • Zamiataj problem pod dywan, zaczynając zamiast tego nowe zależności up
  • Ponownie rozważ nasz projekt. Być może niektóre zależności zawsze łączą się i powinny być ukryte za jakąś inną abstrakcją. Może twoja klasa powinna być podzielona na 2. Może powinna składać się z kilku klas, z których każda wymaga niewielkiego podzbioru zależności.

Istnieje niezliczona ilość sposobów zarządzania zależnościami i nie można powiedzieć, co działa najlepiej w twoim przypadku bez znajomości kodu i aplikacji.

Aby odpowiedzieć na ostatnie pytania:

  • Czy istnieje górna granica liczby zależności, które powinna mieć klasa?

Tak, górny limit to „za dużo”. Ile to za dużo"? „Zbyt wielu” ma miejsce, gdy spójność klasy staje się „zbyt niska”. To wszystko zależy. Zwykle jeśli twoja reakcja na klasę brzmi „wow, ta sprawa ma wiele zależności”, to za dużo.

  • Czy wstrzykiwanie zależności poprawia lub szkodzi czytelności?

Myślę, że to pytanie jest mylące. Odpowiedź może brzmieć tak lub nie. Ale to nie jest najciekawsza część. Istotą wstrzykiwania zależności jest uczynienie ich WIDOCZNYMI. Chodzi o tworzenie interfejsu API, który nie kłamie. Chodzi o zapobieganie globalnemu stanowi. Chodzi o umożliwienie testowania kodu. Chodzi o zmniejszenie sprzężenia.

Dobrze zaprojektowane klasy z rozsądnie zaprojektowanymi i nazwanymi metodami są bardziej czytelne niż źle zaprojektowane. DI tak naprawdę nie poprawia ani nie szkodzi czytelności per se, po prostu wyróżnia twoje wybory projektowe, a jeśli są złe, to szczypie w oczy. Nie oznacza to, że DI sprawił, że Twój kod był mniej czytelny, po prostu pokazał, że twój kod był już bałaganem, właśnie go ukryłeś.


1
Dobre opinie Dziękuję Ci. Tak, górny limit to „za dużo”. - Fantastyczne i takie prawdziwe.
smoksnes

3

Według Steve'a McConnela, autora wszechobecnego „Code Complete”, podstawową zasadą jest to, że więcej niż 7 to zapach kodu, który szkodzi w utrzymaniu. Osobiście uważam, że liczba jest niższa w większości przypadków, ale prawidłowe wykonanie DI spowoduje powstanie wielu zależności, gdy będziesz bardzo blisko do Korzenia Kompozycji. Jest to normalne i oczekiwane. Jest to jeden z powodów, dla których kontenery IoC są przydatne i warte złożoności, jaką dodają do projektu.

Jeśli więc jesteś bardzo blisko punktu wejścia do programu, jest to normalne i dopuszczalne. Jeśli zagłębisz się w logikę programu, prawdopodobnie jest to zapach, którym należy się zająć.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.