Czy tak zwane „problemy przekrojowe” są uzasadnioną wymówką do przełamania SOLID / DI / IoC?

43

Moi koledzy lubią mówić „logowanie / buforowanie / itd. To problem przekrojowy”, a następnie wszędzie używają odpowiedniego singletonu. A jednak uwielbiają IoC i DI.

Czy to naprawdę słuszna wymówka, by złamać zasadę SOLI D.

Legowisko
źródło
27
Przekonałem się, że zasady SOLID są w większości przydatne tylko dla tych, którzy mają już doświadczenie niezbędne do nieświadomego ich przestrzegania.
svidgen
1
Uważam, że termin „problem przekrojowy” jest dość konkretnym terminem (powiązanym z aspektami) i niekoniecznie musi być synonimem singletonu. Rejestrowanie może stanowić problem przekrojowy w tym sensie, że czasami chcesz zapisać ogólną wiadomość w wielu miejscach w ten sam sposób (np. Rejestruj każde połączenie do metody usługi, z której wykonano połączenie, lub innego rodzaju rejestrowania typu kontroli). Argumentowałbym jednak, że rejestrowanie (w ogólnym znaczeniu) nie stanowi problemu przekrojowego tylko dlatego, że jest używane wszędzie. Myślę, że jest to bardziej „wszechobecny problem”, chociaż tak naprawdę nie jest to standardowy termin.
Tempo
4
@svidgen zasady SOLID są również przydatne, gdy ci, którzy ich nie znają, sprawdzają Twój kod i pytają, dlaczego to zrobiłeś. Miło jest móc wskazać na zasadę i powiedzieć „właśnie dlatego”
candied_orange
1
Czy masz jakieś przykłady, dlaczego Twoim zdaniem rejestrowanie jest szczególnym przypadkiem? Jest to dość uproszczony kanał do przekierowywania niektórych danych, zwykle stanowiący część dowolnego używanego frameworka X.
ksiimson

Odpowiedzi:

42

Nie.

SOLID istnieje jako wytyczne uwzględniające nieuniknioną zmianę. Czy naprawdę nigdy nie zamierzasz zmieniać biblioteki rejestrowania, celowania, filtrowania, formatowania lub ...? Czy naprawdę nie zamierzasz zmieniać swojej biblioteki buforowania, celu, strategii, zakresu, czy ...?

Oczywiście, że jesteś. Na każdym razie , będziesz chciał drwić te rzeczy w sposób rozsądny, aby odizolować je do testowania. A jeśli chcesz izolować je do testów, prawdopodobnie natkniesz się na powód biznesowy, w którym chcesz je izolować z prawdziwych powodów.

Otrzymasz argument, że sam rejestrator poradzi sobie ze zmianą. „Och, jeśli zmieni się cel / filtrowanie / formatowanie / strategia, po prostu zmienimy konfigurację!” To śmieci Nie tylko masz teraz Boski Obiekt, który obsługuje wszystkie te rzeczy, piszesz swój kod w XML (lub podobny), gdzie nie dostajesz analizy statycznej, nie dostajesz błędów czasu kompilacji i nie „ to naprawdę skuteczne testy jednostkowe.

Czy istnieją przypadki naruszenia wytycznych SOLID? Absolutnie. Czasami rzeczy się nie zmienią (i tak nie będą wymagały pełnego przepisania). Czasami lekkie naruszenie LSP jest najczystszym rozwiązaniem. Czasami utworzenie izolowanego interfejsu nie przynosi żadnej wartości.

Ale rejestrowanie i buforowanie (i inne wszechobecne problemy przekrojowe) nie są tymi przypadkami. Są to zwykle świetne przykłady problemów ze sprzężeniem i projektowaniem, które występują, gdy ignoruje się wytyczne.

Telastyn
źródło
22
Jaką masz alternatywę? Wstrzykiwanie ILoggera do każdej klasy, którą piszesz? Pisanie dekoratora dla każdej klasy, która potrzebuje rejestratora?
Robert Harvey
16
Tak. Jeśli coś jest zależnością, uczyń to zależnością . A jeśli to oznacza zbyt wiele zależności lub jeśli gdzieś podajesz rejestrator, nie powinno go to iść - dobrze , teraz możesz naprawić swój projekt.
Telastyn
20
Prawdziwy problem z dogmatycznym IoC polega na tym, że prawie cały współczesny kod zależy od czegoś , co nie jest ani wstrzykiwane, ani abstrakcyjne. Jeśli piszesz C #, na przykład, jesteś często nie zamierza abstrakcyjne Stringlub Int32nawet Listz modułu. Jest tylko taki stopień, w jakim rozsądne i rozsądne jest zaplanowanie zmiany. Poza najbardziej oczywistymi typami „rdzenia”, rozeznanie, co możesz zmienić, jest naprawdę kwestią doświadczenia i osądu.
svidgen
6
@svidgen - Mam nadzieję, że nie przeczytałeś mojej odpowiedzi jako rzecznika dogmatycznego IoC. I tylko dlatego, że nie wyodrębniamy tych podstawowych typów (i innych rzeczy, które są ostrożne) nie oznacza, że ​​posiadanie globalnej listy / łańcucha / int / etc jest w porządku.
Telastyn
38

tak

Jest to cały sens terminu „problem przekrojowy” - oznacza to coś, co nie pasuje dokładnie do zasady SOLID.

To tutaj idealizm spotyka się z rzeczywistością.

Ludzie częściowo nowi w SOLID i przekrojowi często spotykają się z tym mentalnym wyzwaniem. Jest OK, nie wariuj. Staraj się umieścić wszystko w kategoriach SOLID, ale jest kilka miejsc, takich jak rejestrowanie i buforowanie, w których SOLID po prostu nie ma sensu. Przekrój jest bratem SOLID, idą ze sobą w parze.

Klom Dark
źródło
9
Uzasadniona, praktyczna, nie dogmatyczna odpowiedź.
user1936
11
Czy możesz nam powiedzieć, dlaczego wszystkie pięć zasad SOLID jest łamanych przez przekrojowe obawy? Mam problemy ze zrozumieniem tego.
Doc Brown
4
Tak. Mógłbym potencjalnie wymyślić dobry powód, by „złamać” każdą zasadę indywidualnie; ale ani jednego powodu, by złamać wszystkie 5!
svidgen
1
Idealnie byłoby dla mnie nie musieć uderzać głową o ścianę za każdym razem, gdy muszę wyśmiewać rejestrator / pamięć podręczną singletona do testu jednostkowego. Wyobraź sobie, jak wyglądałoby testowanie jednostkowe aplikacji sieciowej bez HttpContextBase(która została wprowadzona właśnie z tego powodu). Wiem na pewno, że moja rzeczywistość byłaby naprawdę kwaśna bez tej klasy.
devnull
2
@devnull może to jest problem, a nie przekrój dotyczy ich samych ...
Boris the Spider
25

Myślę, że do logowania. Rejestrowanie jest wszechobecne i zasadniczo niezwiązane z funkcjonalnością usługi. Powszechne i dobrze zrozumiałe jest stosowanie wzorców singletonu w ramach rejestrowania. Jeśli nie, tworzysz i wstrzykujesz rejestratory wszędzie i nie chcesz tego.

Jednym z powyższych problemów jest to, że ktoś powie „ale jak mogę przetestować rejestrowanie?” . Moje myśli są takie, że zwykle nie testuję rejestrowania, poza stwierdzeniem, że rzeczywiście mogę odczytać pliki dziennika i je zrozumieć. Kiedy widziałem sprawdzanie rejestrowania, dzieje się tak zwykle dlatego, że ktoś musi stwierdzić, że klasa rzeczywiście coś zrobiła i używa komunikatów w dzienniku, aby uzyskać informację zwrotną. Wolałbym raczej zarejestrować jakiegoś słuchacza / obserwatora w tej klasie i zapewnić w moich testach, że zostanie on wywołany. Następnie możesz umieścić rejestrowanie zdarzeń w tym obserwatorze.

Myślę jednak, że buforowanie to zupełnie inny scenariusz.

Brian Agnew
źródło
3
Po tym, jak trochę zagłębiłem się w FP, zacząłem wykazywać duże zainteresowanie wykorzystaniem (być może monadycznym) składu funkcji do osadzania takich rzeczy, jak rejestrowanie, obsługa błędów itp. W twoich przypadkach użycia bez umieszczania tego w podstawowej logice biznesowej ( patrz fsharpforfunandprofit.com/rop )
sara
5
Rejestrowanie nie powinno być wszechobecne. Jeśli tak, to logujesz się o wiele za dużo i po prostu generujesz dla siebie hałas, kiedy faktycznie musisz zajrzeć do tych dzienników. Wprowadzając zależność programu rejestrującego, musisz zastanowić się, czy rzeczywiście musisz coś zalogować.
RubberDuck
12
@ RubberDuck - Powiedz mi, że „rejestrowanie nie powinno być wszechobecne”, kiedy otrzymuję raport o błędzie z pola i jedyną możliwością debugowania, którą muszę dowiedzieć się, co się stało, jest plik dziennika, którego nie chciałem rozpowszechniać. Wyciągnięte wnioski, rejestrowanie powinno być „wszechobecne”, bardzo wszechobecne.
Dunk
15
@RubberDuck: Przy oprogramowaniu serwera logowanie jest jedynym sposobem na przetrwanie. To jedyny sposób, aby dowiedzieć się o błędzie, który miał miejsce 12 godzin temu zamiast teraz na własnym laptopie. Nie martw się hałasem. Skorzystaj z oprogramowania do zarządzania dziennikami, aby móc wyszukiwać dane w dzienniku (najlepiej skonfigurować alarmy, które będą wysyłać Ci wiadomości e-mail w dziennikach błędów)
Slebetman
6
Nasz personel wsparcia zadzwoń do mnie i powiedz „klient klika niektóre strony i pojawia się błąd”. Pytam ich, co powiedział błąd, nie wiedzą. Pytam, co konkretnie kliknął klient, a nie wie. Pytam, czy mogą się rozmnażać, nie mogą. Zgadzam się, że rejestrowanie można osiągnąć głównie za pomocą dobrze rozmieszczonych rejestratorów w kilku kluczowych punktach zawiasowych, ale małe dziwne komunikaty tutaj i tam są również nieocenione. Strach przed logowaniem prowadzi do obniżenia jakości oprogramowania. Jeśli skończy się zbyt dużo danych, przycinaj ponownie. Nie optymalizuj przedwcześnie.
Tempo
12

Moje 2 centy ...

Tak i nie.

Nigdy nie powinieneś tak naprawdę naruszać przyjętych zasad; ale wasze zasady powinny być zawsze dopracowane i przyjęte w służbie wyższego celu. Zatem przy odpowiednio uwarunkowanym zrozumieniu niektóre pozorne naruszenia mogą nie być faktycznymi naruszeniami „ducha” lub „zbioru zasad jako całości”.

W szczególności zasady SOLID, poza wymaganiem wielu niuansów, są ostatecznie podporządkowane celowi „dostarczania działającego, możliwego do utrzymania oprogramowania”. Tak więc przestrzeganie jakiejkolwiek konkretnej zasady SOLID jest samowystarczalne i wewnętrznie sprzeczne, gdy jest to sprzeczne z celami SOLID. I tutaj często zauważam, że zapewnianie atutów w utrzymaniu .

A co z D w SOLID ? Cóż, przyczynia się do zwiększenia łatwości konserwacji, czyniąc moduł wielokrotnego użytku stosunkowo agnostycznym w stosunku do jego kontekstu. Możemy zdefiniować „moduł wielokrotnego użytku” jako „zbiór kodu, którego planujesz używać w innym odrębnym kontekście”. Dotyczy to pojedynczych funkcji, klas, zestawów klas i programów.

I tak, zmiana implementacji programu rejestrującego prawdopodobnie umieszcza moduł w „innym odrębnym kontekście”.

Pozwólcie, że przedstawię moje dwa duże zastrzeżenia :

Po pierwsze: narysowanie linii wokół bloków kodu, które stanowią „moduł wielokrotnego użytku”, jest kwestią profesjonalnego osądu. Twój osąd jest koniecznie ograniczony do twojego doświadczenia.

Jeśli nie planujesz obecnie używać modułu w innym kontekście, prawdopodobnie bezradne jest od niego uzależnienie. Zastrzeżenie do zastrzeżenia: Twoje plany są prawdopodobnie błędne - ale to również OK. Im dłużej piszesz moduł po module, tym bardziej intuicyjne i dokładne jest poczucie, czy „kiedyś będę tego znowu potrzebować”. Ale prawdopodobnie nigdy nie będziesz w stanie z mocą wsteczną powiedzieć: „Zmodularyzowałem i odsprzęgłem wszystko w możliwie największym stopniu, ale bez nadmiaru ”.

Jeśli czujesz się winny z powodu błędów w ocenie, idź do spowiedzi i przejdź dalej ...

Po drugie: Kontrola odwracania nie równa się wstrzykiwaniu zależności .

Jest to szczególnie prawdziwe, gdy zaczynasz wstrzykiwać zależności ad nauseam . Wstrzykiwanie zależności jest użyteczną taktyką dla nadrzędnej strategii IoC. Ale twierdzę, że DI ma mniejszą skuteczność niż niektóre inne taktyki - takie jak używanie interfejsów i adapterów - pojedyncze punkty ekspozycji na kontekst z poziomu modułu.

I skoncentrujmy się na tym przez chwilę. Ponieważ nawet jeśli wstrzykujesz Logger reklamę na nudę , musisz napisać kod przeciwko Loggerinterfejsowi. Nie można rozpocząć korzystania z nowego Loggerod innego dostawcy, który przyjmuje parametry w innej kolejności. Ta zdolność wynika z kodowania w module na interfejsie, który istnieje w module i który ma jeden podmoduł (Adapter) w celu zarządzania zależnością.

A jeśli piszesz przeciwko Adapterowi, to czy Loggerjest on wstrzykiwany do tego Adaptera, czy też wykrywany przez Adapter, jest ogólnie dość nieistotny dla ogólnego celu w zakresie konserwacji. A co ważniejsze, jeśli masz adapter na poziomie modułu, prawdopodobnie absurdem jest wstrzyknięcie go w cokolwiek. Jest napisany dla modułu.

tl; dr - Przestań się niepokoić o zasady, nie zastanawiając się, dlaczego je stosujesz. I, bardziej praktycznie, wystarczy zbudować Adapterdla każdego modułu. Skorzystaj z własnego osądu, decydując, gdzie narysujesz granice „modułu”. Z każdego modułu przejdź bezpośrednio do Adapter. I oczywiście , wstrzyknij prawdziwego loggera do Adapter- ale nie do każdego drobiazgu, który może go potrzebować.

svidgen
źródło
4
Człowieku ... udzieliłbym krótszej odpowiedzi, ale nie miałem czasu.
svidgen
2
+1 za korzystanie z adaptera. Musisz odizolować zależności od komponentów innych firm, aby umożliwić ich wymianę w miarę możliwości. Rejestrowanie jest w tym celu łatwym celem - istnieje wiele implementacji rejestrowania i przeważnie mają one podobne interfejsy API, więc prosta implementacja adaptera pozwala bardzo łatwo je zmienić. I ludziom, którzy twierdzą, że nigdy nie zmienisz dostawcy rejestrowania: musiałem to zrobić, a to spowodowało prawdziwy ból, ponieważ nie korzystałem z adaptera.
Jules
„W szczególności zasady SOLID, poza tym, że wymagają wiele niuansów, są ostatecznie podporządkowane celowi„ dostarczania działającego, możliwego do utrzymania oprogramowania ”.” - To jedno z najlepszych stwierdzeń, jakie widziałem. Jako koder o łagodnym OCD miałem swój udział w zmaganiach z idealizmem a wydajnością.
DVK,
Za każdym razem, gdy widzę +1 lub komentarz do starej odpowiedzi, ponownie czytam swoją odpowiedź i ponownie odkrywam, że jestem strasznie zdezorganizowanym i niejasnym pisarzem ... Doceniam jednak komentarz @DVK.
svidgen
Adaptery oszczędzają życie, nie kosztują dużo i znacznie ułatwiają życie, szczególnie gdy chcesz zastosować SOLID. Testowanie jest dziecinnie proste.
Alan
8

Pomysł, że rejestrowanie powinno być zawsze wdrażane jako singleton, jest jednym z tych kłamstw, które tak często opowiadano, że zyskały na popularności.

Dopóki chodziło o nowoczesne systemy operacyjne, uznano, że możesz chcieć zalogować się do wielu miejsc, w zależności od charakteru danych wyjściowych .

Projektanci systemów powinni stale kwestionować skuteczność wcześniejszych rozwiązań, zanim ślepo włączą je do nowych. Jeśli nie przeprowadzają takiej staranności, to nie wykonują swojej pracy.

Robbie Dee
źródło
2
Jakie jest twoje proponowane rozwiązanie? Czy to wstrzykuje ILogger do każdej klasy, którą piszesz? Co z użyciem wzoru dekoratora?
Robert Harvey
4
Naprawdę nie odpowiadam teraz na moje pytanie, prawda? Podałem wzory tylko jako przykłady ... Nie muszą to być te, jeśli masz na myśli coś lepszego.
Robert Harvey
8
Naprawdę nie rozumiem tej odpowiedzi w kategoriach konkretnych propozycji
Brian Agnew
3
@RobbieDee - Czyli twierdzisz, że rejestrowanie powinno być realizowane w najbardziej zawiły i niewygodny sposób dzięki „przypadkowemu wypadkowi”, który możemy chcieć zalogować do wielu miejsc? Nawet jeśli taka zmiana wystąpiła, czy naprawdę uważasz, że dodanie tej funkcji do istniejącej instancji Loggera będzie wymagało więcej pracy niż wysiłek związany z przekazywaniem Loggera między klasami i zmianą interfejsów, kiedy zdecydujesz, że chcesz Loggera, czy nie dziesiątki projektów, w których rejestrowanie wielu miejsc nigdy nie nastąpi?
Dunk
2
Re: „możesz logować się do wielu miejsc w zależności od charakteru danych wyjściowych”: Jasne, ale najlepszym sposobem na radzenie sobie z tym jest w strukturze rejestrowania , zamiast próbować wstrzyknąć wiele różnych zależności rejestrowania. (Wspólne ramy rejestrowania już to obsługują.)
ruakh
4

Prawdziwe logowanie jest szczególnym przypadkiem.

@Telastyn pisze:

Czy naprawdę nigdy nie zamierzasz zmieniać biblioteki rejestrowania, celowania, filtrowania, formatowania lub ...?

Jeśli spodziewasz się, że konieczna może być zmiana biblioteki logowania, powinieneś użyć fasady; tj. SLF4J, jeśli jesteś w świecie Java.

Jeśli chodzi o resztę, porządna biblioteka rejestrowania zajmuje się zmianą miejsca, w którym odbywa się rejestrowanie, które zdarzenia są filtrowane, jak formatowanie zdarzeń dziennika jest przeprowadzane za pomocą plików konfiguracyjnych rejestratora i (jeśli to konieczne) niestandardowych klas wtyczek. Istnieje wiele gotowych alternatyw.

Krótko mówiąc, są to rozwiązane problemy ... do logowania ... i dlatego nie ma potrzeby używania Dependency Injection do ich rozwiązania.

Jedyny przypadek, w którym DI może być korzystny (w porównaniu ze standardowymi metodami rejestrowania), polega na tym, że chcesz poddać rejestrowanie aplikacji testom jednostkowym. Podejrzewam jednak, że większość programistów powiedziałaby, że rejestrowanie nie jest częścią funkcjonalności klas i nie jest czymś, co wymaga przetestowania.

@Telastyn pisze:

Otrzymasz argument, że sam rejestrator poradzi sobie ze zmianą. „Och, jeśli zmieni się cel / filtrowanie / formatowanie / strategia, po prostu zmienimy konfigurację!” To śmieci Nie tylko masz teraz Boski Obiekt, który obsługuje wszystkie te rzeczy, piszesz swój kod w XML (lub podobny), gdzie nie dostajesz analizy statycznej, nie dostajesz błędów czasu kompilacji i nie „ to naprawdę skuteczne testy jednostkowe.

Obawiam się, że to bardzo teoretyczny ripost. W praktyce większość programistów i integratorów systemów PODOBA fakt, że można skonfigurować rejestrowanie za pomocą pliku konfiguracyjnego. I podoba im się to, że nie oczekuje się od nich jednostkowego testowania rejestrowania modułu.

Oczywiście, jeśli upchniesz konfiguracje rejestrowania, możesz mieć problemy, ale pojawią się one albo jako awaria aplikacji podczas uruchamiania, albo za dużo / za mało rejestrowania. 1) Problemy te można łatwo naprawić, naprawiając błąd w pliku konfiguracyjnym. 2) Alternatywą jest pełny cykl kompilacji / analizy / testowania / wdrażania przy każdej zmianie poziomów rejestrowania. To nie do przyjęcia.

Stephen C.
źródło
3

Tak i nie !

Tak: Sądzę, że uzasadnione jest, aby różne podsystemy (lub warstwy semantyczne lub biblioteki lub inne pojęcia pakietowania modułowego) akceptują (ten sam lub) potencjalnie inny program rejestrujący podczas inicjalizacji, a nie wszystkie podsystemy oparte na tym samym wspólnym wspólnym singletonie .

Jednak,

Nie: sparametryzowanie logowania w każdym małym obiekcie (przy użyciu metody konstruktora lub instancji) jest nieuzasadnione. Aby uniknąć niepotrzebnego i bezcelowego wzdęcia, mniejsze jednostki powinny używać rejestratora singletonów w swoim otaczającym kontekście.


Jest to jeden z wielu powodów, dla których należy myśleć o modułowości poziomów: metody są pogrupowane w klasy, a klasy w podsystemy i / lub warstwy semantyczne. Te większe pakiety są cennymi narzędziami abstrakcji; powinniśmy rozważyć inne rozważania w granicach modułowych niż przy ich przekraczaniu.

Erik Eidt
źródło
3

Najpierw zaczyna się od silnej pamięci podręcznej singletonów, następnie widać silne singletony dla warstwy bazy danych wprowadzające stan globalny, classnieopisowe interfejsy API es i niestabilny kod.

Jeśli zdecydujesz się nie mieć singletonu dla bazy danych, prawdopodobnie nie jest dobrym pomysłem posiadanie singletona dla pamięci podręcznej, w końcu reprezentują one bardzo podobną koncepcję, przechowywanie danych, tylko przy użyciu różnych mechanizmów.

Korzystanie z singletonu w klasie zmienia klasę o określonej liczbie zależności w klasę, która teoretycznie ma ich nieskończoną liczbę, ponieważ nigdy nie wiadomo, co tak naprawdę kryje się za metodą statyczną.

W ostatnim dziesięcioleciu spędziłem programowanie, był tylko jeden przypadek, w którym byłem świadkiem wysiłku zmiany logiki rejestrowania (która została wówczas napisana jako singleton). Więc chociaż uwielbiam zastrzyki z zależności, rejestrowanie nie jest tak wielkim problemem. Z drugiej strony pamięć podręczna z pewnością zawsze byłaby zależna.

Andy
źródło
Tak, logowanie rzadko się zmienia i zwykle nie musi być modułem wymiennym. Jednak kiedyś próbowałem przetestować jednostkowo klasę pomocniczą rejestrowania, która miała statyczną zależność od systemu rejestrowania. Zdecydowałem, że najłatwiejszym mechanizmem umożliwiającym przetestowanie byłoby uruchomienie testowanej klasy w osobnym procesie, skonfigurowanie rejestratora do zapisu w STDOUT i przeanalizowanie danych wyjściowych w moim przypadku testowym ಠ_ಠ Mam podobne doświadczenia z zegarami, w których „ d oczywiście nigdy nie chcę niczego oprócz czasu rzeczywistego, prawda? Z wyjątkiem, gdy testuje strefę czasową / DST przypadki krawędzi, oczywiście ...
Amon
@amon: Zegary są jak logowanie, ponieważ istnieje już inny mechanizm, który służy temu samemu celowi co DI, a mianowicie Joda-Time i jego wiele portów. (Nie dlatego, że zamiast tego używa się DI; ale łatwiej jest używać Joda-Time bezpośrednio niż pisać niestandardowy adapter do wstrzykiwań i nigdy nie widziałem, żeby ktoś tego żałował.)
ruakh
@amon „Mam podobne doświadczenia z zegarami, w których oczywiście nie chciałbyś niczego oprócz czasu rzeczywistego, prawda? Z wyjątkiem testowania przypadków strefy czasowej / krawędzi czasu letniego, oczywiście…” - lub gdy zdajesz sobie sprawę, że błąd ma uszkodził bazę danych i jedyną nadzieją na jej odzyskanie jest parsowanie dziennika zdarzeń i odtworzenie go od ostatniej kopii zapasowej ... ale nagle potrzebujesz całego kodu do działania na podstawie sygnatury czasowej bieżącego wpisu dziennika zamiast bieżącego czas.
Jules
3

Tak i nie, ale głównie nie

Zakładam, że większość konwersacji oparta jest na instancji statycznej vs. Nikt nie proponuje, aby rejestrowanie złamało SRP, które zakładam? Mówimy głównie o „zasadzie inwersji zależności”. Tbh W większości zgadzam się z brakiem odpowiedzi Telastyna.

Kiedy można używać statyki? Ponieważ najwyraźniej czasami jest to w porządku. Odpowiedź „tak” dla zalet abstrakcji, a odpowiedź „nie” wskazuje, że są to coś, za co płacisz. Jednym z powodów, dla których praca jest trudna, jest to, że nie ma jednej odpowiedzi, którą można zapisać i zastosować we wszystkich sytuacjach.

Brać: Convert.ToInt32("1")

Wolę to:

private readonly IConverter _converter;

public MyClass(IConverter converter)
{
   Guard.NotNull(converter)
   _converter = conveter
}

.... 
var foo = _converter.ToInt32("1");

Dlaczego? Zgadzam się, że będę musiał przefakturować kod, jeśli potrzebuję elastyczności, aby zamienić kod konwersji. Akceptuję, że nie będę w stanie wyśmiewać tego. Uważam, że prostota i zwięzłość jest warta tego handlu.

Patrząc na drugim końcu spektrum, jeśli IConverterbyło SqlConnection, to byłoby dość przerażony widząc, że jako statyczny rozmowy. Powody, dla których są oczywiste. Zwracam uwagę, że a SQLConnectionmoże być dość „przekrojowe” w aplacji, więc nie użyłbym tych dokładnych słów.

Czy rejestrowanie bardziej przypomina „ SQLConnectionlub” Convert.ToInt32? Powiedziałbym bardziej jak „SQLConnection”.

Powinieneś kpić z Logowania . Mówi do świata zewnętrznego. Pisząc metodę za pomocą Convert.ToIn32, używam jej jako narzędzia do obliczania innych osobno testowalnych danych wyjściowych klasy. Nie muszę sprawdzać, czy Convertzostał poprawnie wywołany podczas sprawdzania, że ​​„1” + „2” == „3”. Logowanie jest inne, jest to całkowicie niezależne wyjście klasy. Zakładam, że jest to wynik, który ma wartość dla Ciebie, zespołu wsparcia i firmy. Twoja klasa nie działa, jeśli rejestrowanie jest nieprawidłowe, więc testy jednostkowe nie powinny przejść pomyślnie. Powinieneś testować, co rejestruje Twoja klasa. Myślę, że to argument zabójcy, naprawdę mógłbym się tutaj zatrzymać.

Myślę też, że jest to coś, co może się zmienić. Dobre rejestrowanie nie tylko drukuje ciągi, ale pokazuje, co robi twoja aplikacja (jestem wielkim fanem rejestrowania na podstawie zdarzeń). Widziałem, jak podstawowe logowanie zmienia się w dość skomplikowane interfejsy raportowania. Oczywiście o wiele łatwiej jest podążać w tym kierunku, jeśli rejestrowanie wygląda tak, _logger.Log(new ApplicationStartingEvent())a mniej podobnie Logger.Log("Application has started"). Można argumentować, że tworzy to ekwipunek na przyszłość, która może się nigdy nie wydarzyć, to jest apel sądowy i wydaje mi się, że warto.

W rzeczywistości w moim osobistym projekcie stworzyłem interfejs użytkownika, który nie loguje się, używając wyłącznie _loggerdo sprawdzenia, co robi aplikacja. Oznaczało to, że nie musiałem pisać kodu, aby dowiedzieć się, co robi aplikacja, i skończyło się na solidnym logowaniu. Wydaje mi się, że gdyby moje podejście do logowania było proste i niezmienne, ten pomysł by mi się nie przydarzył.

Zgadzam się więc z Telastyn w sprawie logowania.

Nathan Cooper
źródło
Tak się przypadkowo loguję . Chciałbym zamieścić link do artykułu lub czegoś, ale nie mogę go znaleźć. Jeśli jesteś w świecie .NET i szukasz rejestrowania zdarzeń, znajdziesz Semantic Logging Application Block. Nie używaj go, jak większość kodu utworzonego przez zespół MS „wzorce i praktyka”, jak na ironię, jest to antypattern.
Nathan Cooper
3

Po pierwsze, problemy przekrojowe nie są głównymi elementami składowymi i nie powinny być traktowane jako zależności w systemie. System powinien działać, jeśli np. Logger nie został zainicjowany lub pamięć podręczna nie działa. Jak sprawisz, że system będzie mniej sprzężony i spójny? Właśnie tam pojawia się SOLID w projektowaniu systemu OO.

Utrzymywanie obiektu jako singletonu nie ma nic wspólnego z SOLID. To jest cykl życia twojego obiektu, jak długo ma on żyć w pamięci.

Klasa, która wymaga zależności do zainicjowania, nie powinna wiedzieć, czy podana instancja klasy jest singletonowa czy przejściowa. Ale tldr; jeśli piszesz Logger.Instance.Log () w każdej metodzie lub klasie, to jest to problematyczny kod (zapach kodu / twarde sprzężenie), to naprawdę bałagan. To jest moment, kiedy ludzie zaczynają nadużywać SOLID. Inni programiści tacy jak OP zaczynają zadawać takie prawdziwe pytania.

vendettamit
źródło
2

Rozwiązałem ten problem, używając kombinacji dziedziczenia i cech (w niektórych językach nazywanych także mixinami). Cechy są bardzo przydatne do rozwiązania tego rodzaju problemów przekrojowych. Zazwyczaj jest to jednak funkcja językowa, więc myślę, że prawdziwą odpowiedzią jest to, że zależy od funkcji językowych.

RibaldEddie
źródło
Ciekawy. Nigdy nie wykonałem żadnej znaczącej pracy przy użyciu języka, który obsługuje cechy, ale rozważałem użycie takich języków w niektórych przyszłych projektach, więc byłoby dla mnie pomocne, gdybyś mógł rozwinąć ten temat i pokazać, w jaki sposób cechy rozwiązują problem.
Jules