jaka powinna być pozycja rejestratora na liście parametrów [zamknięte]

12

W moim kodzie wstrzykuję program rejestrujący do wielu moich klas za pomocą listy parametrów ich konstruktora

Zauważyłem, że umieszczam to losowo: czasami jest to pierwszy na liście, czasem ostatni, a czasem pomiędzy

Czy masz jakieś preferencje? Moja intuicja mówi, że spójność jest pomocna w tym przypadku, a moje osobiste preferencje to stawianie jej na pierwszym miejscu, aby łatwiej było ją zauważyć, gdy jej brakuje, i łatwiej ją pominąć, gdy tam jest.

Ezdrasz
źródło

Odpowiedzi:

31

Rejestratory nazywamy „problemem przekrojowym”. Ulegają technikom takim jak programowanie zorientowane na aspekt; jeśli masz sposób ozdobić swoje klasy atrybutem lub wykonać trochę tkania kodu, to jest to dobry sposób na uzyskanie możliwości rejestrowania przy jednoczesnym zachowaniu czystości obiektów i list parametrów.

Jedynym powodem, dla którego możesz chcieć przekazać program rejestrujący jest to, że chcesz określić różne implementacje rejestrowania, ale większość platform rejestrowania ma elastyczność pozwalającą na konfigurowanie ich, powiedzmy, dla różnych celów rejestrowania. (plik dziennika, Menedżer zdarzeń systemu Windows itp.)

Z tych powodów wolę, aby logowanie było naturalną częścią systemu, niż przekazywanie programu rejestrującego do każdej klasy w celu logowania. Więc ogólnie robię to odwoływanie się do odpowiedniej przestrzeni nazw rejestrowania i po prostu używanie rejestratora w moich klasach.

Jeśli nadal chcesz przekazać rejestrator, preferuję, aby uczynić go ostatnim parametrem na liście parametrów (jeśli to możliwe, ustaw parametr opcjonalny). Posiadanie pierwszego parametru nie ma większego sensu; pierwszy parametr powinien być najważniejszy, najbardziej odpowiedni dla działania klasy.

Robert Harvey
źródło
11
+! trzymaj program rejestrujący z dala od konstruktorów; Wolę rejestrator statyczny, więc wiem, że wszyscy używają tego samego
Steven A. Lowe
1
@ StevenA.Lowe Pamiętaj jednak, że używanie statycznych programów rejestrujących może prowadzić do innych problemów w dalszej kolejności, jeśli nie będziesz ostrożny (np. Fiasko kolejności inicjalizacji statycznej w C ++). Zgadzam się, że posiadanie rejestratora jako globalnie dostępnej jednostki ma swoje uroki, ale należy dokładnie ocenić, czy i jak taki projekt pasuje do ogólnej architektury.
ComicSansMS
@ComicSansMS: oczywiście plus problemy z wątkami itp. Tylko osobiste preferencje - „tak proste, jak to możliwe, ale nie prostsze”;)
Steven A. Lowe
Posiadanie statycznego rejestratora może stanowić problem. Utrudnia to wstrzykiwanie zależności (chyba że chodzi o rejestrator singletonów utworzony przez kontener DI), a jeśli kiedykolwiek chcesz zmienić architekturę, może to być bolesne. Na przykład teraz używam funkcji platformy Azure, które przekazują rejestrator jako parametr do wykonania każdej funkcji, dlatego żałuję, że przekazałem mój rejestrator przez konstruktor.
Slothario,
@ Slothario: To piękno statycznego rejestratora. Nie jest wymagany żaden zastrzyk.
Robert Harvey,
7

W językach z przeciążeniem funkcji argumentowałbym, że im bardziej prawdopodobne jest, że argument będzie opcjonalny, tym bardziej powinno być. Zapewnia to spójność podczas tworzenia przeciążeń tam, gdzie ich brakuje:

foo(mandatory);
foo(mandatory, optional);
foo(mandatory, optional, evenMoreOptional);

W językach funkcjonalnych odwrotność jest bardziej przydatna - im bardziej prawdopodobne jest, że wybierzesz jakieś domyślne, tym bardziej powinno być. Ułatwia to specjalizację funkcji, po prostu stosując do niej argumenty:

addThreeToList = map (+3)

Jednak, jak wspomniano w innych odpowiedziach, prawdopodobnie nie chcesz jawnie przekazywać programu rejestrującego na liście argumentów każdej klasy w systemie.

Doval
źródło
3

Z pewnością możesz poświęcić dużo czasu na nadmierne zaprojektowanie tego problemu.

W przypadku języków z implementacjami rejestrowania kanonicznego wystarczy utworzyć instancję rejestratora kanonicznego bezpośrednio w każdej klasie.

W przypadku języków bez implementacji kanonicznej spróbuj znaleźć strukturę elewacji rejestrującej i trzymaj się jej. slf4j to dobry wybór w Javie.

Osobiście wolałbym trzymać się jednej konkretnej implementacji rejestrowania i wysyłać wszystko do syslog. Wszystkie dobre narzędzia do analizy dzienników są w stanie połączyć dzienniki sysout z wielu serwerów aplikacji w kompleksowy raport.

Gdy podpis funkcji zawiera jedną lub dwie usługi zależności, a także niektóre „rzeczywiste” argumenty, umieszczam zależności na końcu:

int calculateFooBarSum(int foo, int bar, IntegerSummationService svc)

Ponieważ moje systemy mają zwykle tylko pięć lub mniej takich usług, zawsze upewniam się, że usługi są zawarte w tej samej kolejności we wszystkich podpisach funkcji. Porządek alfabetyczny jest tak dobry, jak każdy inny. (Poza tym: utrzymanie tego metodologicznego podejścia do obsługi muteksów również zmniejszy twoje szanse na zakleszczenie).

Jeśli stwierdzisz, że wstrzykujesz kilkanaście zależności w swojej aplikacji, prawdopodobnie system musi zostać podzielony na osobne podsystemy (czy mogę powiedzieć, że mikrousług?).

Christian Willman
źródło
Wydaje mi się niewygodne korzystanie z zastrzyków właściwości do wywoływania funkcji replaceFooBarSum.
phu
2

Rejestratory są trochę szczególnym przypadkiem, ponieważ muszą być dostępne dosłownie wszędzie.

Jeśli zdecydowałeś, że chcesz przekazać program rejestrujący do konstruktora każdej klasy, zdecydowanie powinieneś ustalić spójną konwencję dotyczącą tego, jak to robisz (np. Zawsze pierwszy parametr, zawsze przekazywany przez referencję, zawsze zaczyna się lista inicjująca konstruktora z m_logger (theLogger) itp.). Wszystko, co będzie używane w całej bazie kodu, kiedyś zyska na spójności.

Alternatywnie, możesz kazać każdej klasie utworzyć własny obiekt rejestratora, bez potrzeby przekazywania czegokolwiek. Rejestrator może potrzebować kilku „magicznych” informacji, aby zadziałało, ale zakodowanie ścieżki pliku w definicji klasy jest potencjalnie o wiele łatwiejsze w utrzymaniu i mniej żmudne niż przekazywanie go poprawnie setkom różnych klas, i zapewne o wiele mniej złego niż używanie globalnej zmiennej do ominięcia tego nudy. (Trzeba przyznać, że rejestratory należą do nielicznych uzasadnionych przypadków użycia zmiennych globalnych)

Ixrec
źródło
1

Zgadzam się z tymi, którzy sugerują, że dostęp do programu rejestrującego powinien być statyczny, a nie przekazywany do klas. Jednakże jeśli istnieje silny powód chcesz przekazać go w (być może różne instancje chcą zalogować się do różnych miejsc, czy coś), to proponuję nie zdać go przy użyciu konstruktora ale raczej zrobić osobną rozmowę, aby to zrobić, np Class* C = new C(); C->SetLogger(logger);raczej niżClass* C = new C(logger);

Powodem preferowania tej metody jest to, że program rejestrujący nie jest zasadniczo częścią klasy, ale raczej funkcją wstrzykiwaną wykorzystywaną do innych celów. Umieszczenie go na liście konstruktorów powoduje, że jest to wymóg klasy i implikuje, że jest on częścią faktycznego stanu logicznego klasy. Uzasadnione jest na przykład oczekiwanie (w przypadku większości klas, choć nie wszystkich), że jeśli X != Ytak, C(X) != C(Y)to jest mało prawdopodobne, aby przetestować nierówność programu rejestrującego, jeśli porównujesz zbyt instancje tej samej klasy.

Jack Aidley
źródło
1
Ma to oczywiście tę wadę, że program rejestrujący jest niedostępny dla konstruktora.
Ben Voigt,
1
Naprawdę podoba mi się ta odpowiedź. To sprawia, że ​​rejestrowanie jest kwestią uboczną klasy, o którą musisz dbać osobno, niż tylko jej używanie. Możliwe, że jeśli dodajesz program rejestrujący do konstruktora dużej liczby klas, prawdopodobnie używasz wstrzykiwania zależności. Nie mówię we wszystkich językach, ale wiem w języku C #, niektóre implementacje DI będą również wstrzykiwać bezpośrednio do właściwości (getter / setter).
jpmc26
@BenVoigt: To prawda i może to być zabójczy powód, aby nie robić tego w ten sposób, ale zwykle możesz wykonać rejestrowanie, które w innym przypadku wykonałbyś w konstruktorze w odpowiedzi na ustawienie rejestratora.
Jack Aidley,
0

Warto wspomnieć, że nie widziałem tutaj innych odpowiedzi, że zmuszenie rejestratora do wstrzykiwania za pośrednictwem właściwości lub właściwości statycznych utrudnia (e) jednostkowe przetestowanie klasy. Na przykład, jeśli twój rejestrator jest wstrzykiwany poprzez właściwość, będziesz musiał wstrzykiwać ten rejestrator za każdym razem, gdy testujesz metodę wykorzystującą rejestrator. Oznacza to, że równie dobrze możesz ustawić to jako zależność konstruktora, ponieważ klasa tego wymaga.

Static nadaje się do tego samego problemu; jeśli program rejestrujący nie działa, oznacza to, że cała klasa nie działa (jeśli klasa korzysta z programu rejestrującego) - nawet jeśli program rejestrujący niekoniecznie jest „częścią” odpowiedzialności klasy - chociaż nie jest tak zły jak zastrzyk własności, ponieważ przynajmniej wiedz, że rejestrator jest zawsze „tam” w pewnym sensie.

Tylko trochę do przemyślenia, zwłaszcza jeśli stosujesz TDD. Moim zdaniem program rejestrujący naprawdę nie powinien być częścią testowalnej części klasy (podczas testowania klasy nie powinieneś również testować logowania).

Dan Pantry
źródło
1
hmmm ... więc chcesz, aby klasa przeprowadzała rejestrowanie (rejestrowanie powinno być w specyfikacji), ale nie chcesz testować za pomocą rejestratora. czy to możliwe? Myślę, że twój punkt widzenia jest non-go. Oczywiście, jeśli twoje narzędzia testowe są zepsute, nie możesz przetestować - zaprojektowanie w taki sposób, aby nie polegać na narzędziu testowym, jest trochę nadmierną inżynierią IMHO
Hogan
1
Chodzi mi o to, że jeśli użyję konstruktora i wywołam metodę na klasie, a ona nadal zawiedzie, ponieważ nie ustawiłem właściwości, to projektant klasy źle zrozumiał koncepcję konstruktora. Jeśli klasa wymaga rejestratora, należy go wstrzyknąć do konstruktora - po to jest konstruktor.
Dan Pantry
umm .. nie, nie bardzo. Jeśli weźmiesz pod uwagę system rejestrowania jako część „frameworka”, nie ma to sensu jako część konstruktora. Ale zostało to wyraźnie stwierdzone w innych odpowiedziach.
Hogan,
1
Jestem przeciwny zastrzykowi nieruchomości. Niekoniecznie opowiadam się za użyciem wstrzykiwania go do konstruktora. Mówię tylko, że moim zdaniem jest to lepsze niż zastrzyk nieruchomości.
Dan Pantry
"Czy to możliwe?" także tak, tkanie IL jest rzeczą, która istnieje i została wspomniana w najwyższej odpowiedzi ... mono-project.com/docs/tools+libraries/libraries/Mono.Cecil
Dan Pantry
0

Jestem zbyt leniwy, aby przekazywać obiekt rejestrujący do każdej instancji klasy. W moim kodzie tego rodzaju rzeczy albo siedzą w polu statycznym, albo w zmiennej lokalnej wątku w polu statycznym. To drugie jest całkiem fajne i pozwala na użycie innego rejestratora dla każdego wątku oraz pozwala dodawać metody włączania i wyłączania logowania, które robią coś sensownego i oczekiwanego w aplikacji wielowątkowej.

Atsby
źródło