Klasy typu helper

0

Cześć,
Co sądzicie o klasach typu helper, w której znajdują się statyczne metody wykorzystywane przez wiele innych klas.
Czy to jest to dobry pomysł, aby wszystkie metody, które są wykorzystywane przez więcej niż jedną klasę wrzucać do statycznej klasy z suffixem "Helper"?
Jak w inny sposób podejść do tego? Duplikowanie metod w klasach które jej potrzebują brzmi bardzo źle.

8

Jeśli te stateczne metody nie zawierają logiki biznesowej, są w miarę stabilne (nie zmieniają się często), to nie widzę przeszkód do wydzielenia ich do klas pomocniczych. Tak klas, a nie jednej boskiej klasy zbierającej wszystkie śmieci znaczy się helpery. I dobrze by każdy taki helper miał swoje testy jednostkowe. Przykład z prawdziwego projektu, helpery pogrupowane po typie:

screenshot-20200810161829.png

2

Helpery same w sobie są w miarę ok, zwłaszcza tak jak wyżej z extensionami w c# (które jednak prawdopodobnie będą wyparte przynajmniej częściowo przez domyślną implementację interfejsów).
Co jest złe to klasa która się nazywa "Helpers" i w której są losowo wrzucane niepowiązane ze sobą funkcje statyczne.
Jeszcze gorsza jest klasa "Constants" w której są pomieszane stałe z całego projektu. Polecam zwłaszcza gdy nagle zachodzi potrzeba wydzielenia jednego modułu do osobnej biblioteki

2
Kordoba napisał(a):

Czy to jest to dobry pomysł, aby wszystkie metody, które są wykorzystywane przez więcej niż jedną klasę wrzucać do statycznej klasy z suffixem "Helper"?

Bardzo dobry, po potem można ten jeden plik skasować i zrobić porządnie.

Jak w inny sposób podejść do tego? Duplikowanie metod w klasach które jej potrzebują brzmi bardzo źle.

Dlatego lepiej nie duplikować. Ale to nie znaczy, żeby mieć tylko jedną klasę pomocniczą. Czasami jest sens przenieść do klasy bazowej, czasami do helpera, czasami do metody rozszerzającej., a czasami po prostu trzeba zrobić normalną klasę i używać jej obiektów Wszystko w zależności od tego, czy to logika biznesowa czy infrastrukturalna, i jak bardzo uniwersalny jest ten kod.

obscurity napisał(a):

(które jednak prawdopodobnie będą wyparte przez domyślną implementację interfejsów).

Niby jakim cudem?

1
somekind napisał(a):
obscurity napisał(a):

(które jednak prawdopodobnie będą wyparte przez domyślną implementację interfejsów).

Niby jakim cudem?

przykładowo całe LINQ dla którego w ogóle powstały extension methods mogłoby być zastąpione metodami w interfejsie IEnumerable i IQueryable, w dodatku znacznie lepiej bo można we własnej klasie zaimplementować takie .Count() które będzie dużo lepiej wiedziało jak sprawnie wyliczyć liczbę własnych elementów - LINQ ma parę hacków że sprawdza coś typu if (enumerable is IList<>) ale to wszystko tylko obejścia problemu który może być lepiej rozwiązany.
Rozszerzanie własnych klas nie ma sensu, a rozszerzanie bibliotek 3rd party to moim zdaniem chybiony pomysł - jeśli nie pasują nam metody dostarczane przez bibliotekę to lepiej użyć wrappera na te obiekty - tak samo jak w javascripcie kiedyś była moda na modyfikowanie prototypów, powstał cały framework prototype.js który rozszerzał standardowe obiekty we frameworku. Okazało się że upiększanie w ten sposób kodu ma mało zalet a rodzi mnóstwo problemów - przede wszystkim javascript zaczął się gwałtownie rozwijać i dodane metody pojawiły się w specyfikacji - tak samo może się stać z biblioteką 3rd party co nagle zmieni zachowanie całego kodu (rzeczywiste metody w c# mają pierwszeństwo nad rozszerzającymi, a kompilator nawet o tym nie powiadomi). Drugi problem to zlokalizowanie kodu w którym jest implementacja metody - w IDE jest to łatwe, ale code reviewer przeglądający kod w pull requeście na githubie już będzie miał problem. Trzeci problem to oczywiście gryzące się metody - może się okazać że dodajemy bibliotekę która chce też dodać własne metody rozszerzające o tej samej nazwie - o ile w c# jeszcze da się to rozwiązać za pomocą using to w javscripcie już to jest niemożliwe. Oczywiście jest też problem z elastycznością takiego kodu - jeśli "rozszerzamy" jakieś klasy to gdy te klasy się zmienią lub zostaną zastąpione innymi, muszą się zmienić też nasze metody...

Ogólnie za to co było standardem 5 lat temu w javascript, dzisiaj można oberwać po głowie. Choć w c# nie zmienia się tak wszystko dynamicznie to moim zdaniem z przykładu javascripta płynie dobra nauka i warto nie powtarzać błędu

4
obscurity napisał(a):

przykładowo całe LINQ dla którego w ogóle powstały extension methods mogłoby być zastąpione metodami w interfejsie IEnumerable i IQueryable, w dodatku znacznie lepiej bo można we własnej klasie zaimplementować takie .Count() które będzie dużo lepiej wiedziało jak sprawnie wyliczyć liczbę własnych elementów - LINQ ma parę hacków że sprawdza coś typu if (enumerable is IList<>) ale to wszystko tylko obejścia problemu który może być lepiej rozwiązany.

No tak, jeżeli używasz wyłącznie extensionów dostarczonych przez Microsoft w formie LINQ, to faktycznie możesz mieć wszystko zastąpione.
Niestety realne życie zazwyczaj jest znacznie bardziej skomplikowane, bo na świecie istnieje wiele extensionów do klas, które nie pochodzą od Microsoftu, ich kod nie jest dostępny, i nawet niekoniecznie mają interfejsy.
Tak więc w ogólności ten myk się nie uda.
Ciekawe zresztą jak Microsoft dostarczy defaultowe implementacje do IServicesCollection, ale trzymam kciuki. :D

Rozszerzanie własnych klas nie ma sensu, a rozszerzanie bibliotek 3rd party to moim zdaniem chybiony pomysł

OK, spoko.
Wychodzi na to, że od początku extensiony były chybionym pomysłem.

  • jeśli nie pasują nam metody dostarczane przez bibliotekę to lepiej użyć wrappera na te obiekty

I napisać kilkadziesiąt metod opakowujących, które tylko przekażą parametry do owrapowanego obiektu.
No, faktycznie lepiej - jeśli lubimy małpią robotę i płacą nam od linijki.

może się okazać że dodajemy bibliotekę która chce też dodać własne metody rozszerzające o tej samej nazwie - o ile w c# jeszcze da się to rozwiązać za pomocą using to w javscripcie już to jest niemożliwe.

Czyli to, że coś jest niemożliwe w JS jest argumentem, żeby nie robić tego w C#?!
Ciekawe też, skąd w 3rd party mają się znaleźć metody operujące na obiektach z mojego projektu, ale może lepiej nie pytać, bo to jakiś grubszy spiseg.

Oczywiście jest też problem z elastycznością takiego kodu - jeśli "rozszerzamy" jakieś klasy to gdy te klasy się zmienią lub zostaną zastąpione innymi, muszą się zmienić też nasze metody...

No tak, bo jak napiszemy wrapper, to on się nie będzie musiał zmienić w takiej sytuacji.

Ogólnie za to co było standardem 5 lat temu w javascript, dzisiaj można oberwać po głowie. Choć w c# nie zmienia się tak wszystko dynamicznie to moim zdaniem z przykładu javascripta płynie dobra nauka i warto nie powtarzać błędu

Nauka płynie jedna - język trzeba najpierw zaprojektować, a potem go używać.

Owszem, są sytuacje, w których extensionów nie powinno się używać, bo faktycznie powodują problemy - np. gdy mamy jakąkolwiek zależność zewnętrzną. Nie ma też sensu robić jako extensiony kodu domenowego albo bardzo kontekstowego (a niektórzy jak się rzucą na extensiony, to w ogóle już potem normalnego kodu nie piszą). No ale nieumiejętność pisania nie jest argumentem przeciwko używaniu czegoś.

2
obscurity napisał(a):

przykładowo całe LINQ dla którego w ogóle powstały extension methods mogłoby być zastąpione metodami w interfejsie IEnumerable i IQueryable, w dodatku znacznie lepiej bo można we własnej klasie zaimplementować takie .Count() które będzie dużo lepiej wiedziało jak sprawnie wyliczyć liczbę własnych elementów - LINQ ma parę hacków że sprawdza coś typu if (enumerable is IList<>) ale to wszystko tylko obejścia problemu który może być lepiej rozwiązany.

Zgadza się, pytanie tylko, czy traity są dobrym podejściem, czy może lepsze byłoby higher kinded types + type classy emulowane przez extension methods.

Rozszerzanie własnych klas nie ma sensu

Ma, jeżeli bawimy się w type classy.

a rozszerzanie bibliotek 3rd party to moim zdaniem chybiony pomysł

To szkoda, bo właśnie po to projektowane były extension methods.

jeśli nie pasują nam metody dostarczane przez bibliotekę to lepiej użyć wrappera na te obiekty

No nie, bo to jest masa roboty, alokacji, wydajności. I ogólnie jest to quasi-adapter, który spokojnie może być lepiej wspierany przez język.

tak samo jak w javascripcie kiedyś była moda na modyfikowanie prototypów, powstał cały framework prototype.js który rozszerzał standardowe obiekty we frameworku. Okazało się że upiększanie w ten sposób kodu ma mało zalet a rodzi mnóstwo problemów - przede wszystkim javascript zaczął się gwałtownie rozwijać i dodane metody pojawiły się w specyfikacji - tak samo może się stać z biblioteką 3rd party co nagle zmieni zachowanie całego kodu (rzeczywiste metody w c# mają pierwszeństwo nad rozszerzającymi, a kompilator nawet o tym nie powiadomi).

Prototypy a extension methods to są dwie zupełnie inne sprawy. W przypadku prototypu nie wiesz, co będzie wykonane, w przypadku extension method dobrze wiesz, bo musisz explicite importować implementację.

Drugi problem to zlokalizowanie kodu w którym jest implementacja metody - w IDE jest to łatwe, ale code reviewer przeglądający kod w pull requeście na githubie już będzie miał problem.

Zgadza się, ale ten sam (a nawet trudniejszy) problem masz przy polimorfizmie.

Trzeci problem to oczywiście gryzące się metody - może się okazać że dodajemy bibliotekę która chce też dodać własne metody rozszerzające o tej samej nazwie - o ile w c# jeszcze da się to rozwiązać za pomocą using to w javscripcie już to jest niemożliwe.

A to już problem JS. Z drugiej strony, C# tu nie jest o wiele lepszy, bo ma problem diamentu (którego w przypadku zewnętrznej klasy w ogóle nie rozwiążesz bez modyfikacji jej kodu).

Oczywiście jest też problem z elastycznością takiego kodu - jeśli "rozszerzamy" jakieś klasy to gdy te klasy się zmienią lub zostaną zastąpione innymi, muszą się zmienić też nasze metody...

To jest ogólnie "problem" z oprogramowaniem, że gdy się zmienia w jednym miejscu, to w innych też trzeba poprawić, gdy te inne miejsca zostały zepsute. LSP, niezmienniki, kontrakty, to wszystko doskwiera tak samo (a może i bardziej) przy traitach.

2

Klasy typu Utils, Helper czy MyObjects - jeśli tylko są napisane poprawnie to są spoko ponieważ:

  • łatwo je przetestować
  • bardzo rzadko się zmieniają
  • można je izolować (tzn. mamy DomainUtils na obiekty domenowe).

Natomiast problem się zaczyna, gdy klasa utilsowa nie jest dobrze napisana, tj.

  • funkcja zawiera logikę biznesową która nie powinna wyjść poza serwis (wtedy lepiej zrobić jakiś private)
  • pojawiają się jakieś dziwne zależności od statycznych pól
  • stają się zbyt skomplikowane

1 użytkowników online, w tym zalogowanych: 0, gości: 1