oznaczanie klas jako SEALED - nadmiermność?

0

Cześć, ostatnio podglądając kod współpracowników moją uwagę zwrócił fakt, że niektórzy z nich często oznaczają klasy jako sealed.
Oznaczenie klasy jako sealed oznacza, że nie można po niej dziedziczyć.
Trochę mnie to dziwi, bo w zasadzie ~70% klas można by było oznaczyć jako sealed.
We wcześniejszych pracach/projektach raczej rzadko spotykałem oznaczanie większośći klas (np. serwisów/managerów, czyli klas zawierających logikę biznesową) jako sealed - choć było jasne, że nigdy (a przynajmniej w najbliższym czasie) nikt nie będzie z nich dziedziczył - bo te serwisy/menadżery implementowały CRUD dla danej klasy (bądź klas powiązanej z nią bezpośrednio).
Czy według was oznaczanie wielu klas jako sealed jest dobre, czy może nadmiarowe?
Zawsze myslałem, że oznaczenie jako sealed klasy oznacza, żeby za żadne skarby z niej nie dziedziczyć (bo coś się stanie/bo ABSOLUTNIE nie powinno się z niej dziedziczyć).

7

Ja staram się oznaczać, zresztą uważam, że sealed był domyślnym modyfikatorem.
Ogólnie powinny być dwa rodzaje klas: abstract i sealed. Ten trzeci to jakieś nieporozumienie - skoro z czegoś dziedziczmy, to czemu jednocześnie mielibyśmy tworzyć tego instancje? Ewidentne łamanie SRP, zazwyczaj też OCP i praktycznie pewne LSP w którymś momencie.

1

Czy według was oznaczanie wielu klas jako sealed jest dobre, czy może nadmiarowe?

No pewnie ze nadmiarowe. W ogole najepiej napi...rdalac public class gdzie popadnie, a najlepiej to i bez public bo przeciez i tak jest domyslne. Niech wszystko bedzie publiczne i pozwala na dziedziczenie, po co sie szczypac w tancu. Dopiero wtedy mozna powiedziec ze kod jest naprawde "extensible" bo po wszystkim mozna sobie dziedziczyc.

1
heyyou napisał(a):

Zawsze myslałem, że oznaczenie jako sealed klasy oznacza, żeby za żadne skarby z niej nie dziedziczyć (bo coś się stanie/bo ABSOLUTNIE nie powinno się z niej dziedziczyć).

Tak to traktuję, nie widzę powodu, aby blokować dziedziczenie z automatu.

0

W literaturze piszą że klasy oznaczone jako sealed są szybciej przetwarzane przez środowisko . Dlatego warto oznaczać je jako sealed .

1
Zimny Krawiec napisał(a):

W literaturze piszą że klasy oznaczone jako sealed są szybciej przetwarzane przez środowisko . Dlatego warto oznaczać je jako sealed .

Tutaj nie chodzi o kompilator C# tylko o kompilator Just-in-time i środowisko CLR. Ten kompilator i środowisko robi wiele sztuczek żeby przyśpieszyć działanie programu.
Można o tym poczytać w necie i książkach .

0
Afish napisał(a):

Tak to traktuję, nie widzę powodu, aby blokować dziedziczenie z automatu.

Z każdej Twojej klasy jest sens dziedziczyć i zawsze to dziedziczenie wspierasz?

0

Nie, ale jaki to ma związek? Jak ktoś chce sobie gdzieś podziedziczyć po implementacji, rozwiązać swoje problemy łatwiej lub napytać sobie biedy, to jego sprawa.

1

A jaki sens umożliwiać dziedziczenie klasy, z której nie ma sensu dziedziczyć, albo w sytuacji, gdy wiadomo, że nikt nigdy nie będzie z niej dziedziczył?

0
somekind napisał(a):

A jaki sens umożliwiać dziedziczenie klasy, z której nie ma sensu dziedziczyć

Jak nie ma sensu dziedziczyć, to nikt nie będzie dziedziczył, więc szkoda mi czasu na dokładanie zbędnych słów.

somekind napisał(a):

albo w sytuacji, gdy wiadomo, że nikt nigdy nie będzie z niej dziedziczył?

Jak wyżej.

1

Czyli oszczędność kilku znaków ważniejsza od jakości. Nie mam więcej pytań.

0

Słaba sztuczka. Ja nie uważam się za boga programowania i nie próbuję na siłę ograniczać innym możliwości, bo potem kończy się to hakami, refleksją i jeszcze większym zamieszaniem.

0

Właśnie tu miałbym problem, bo zostawisz klasę otwartą na dziedziczenie, nikt nie spodziewa się, że będzie to kiedykolwiek potrzebne...
A później przyjdzie jakąś nieooradna sierota, zdziedziczy czajnik z mikrofalówki i za 3 miesiące będziesz się przebijał przez dziwne referencje w 30 kolejnych warstwach, 8 różnych klasach i całej reszcie syfu...

Okay, jak ktoś jest ociemniały, to i dokona modyfikacji tam, gdzie ewidentnie widać celowość pewnych decyzji, zamiast zapytać o sensowną drogę, ale jednak, jest to wyraźne wskazanie co do intencji autora.

1
Klojtex napisał(a):

Właśnie tu miałbym problem, bo zostawisz klasę otwartą na dziedziczenie, nikt nie spodziewa się, że będzie to kiedykolwiek potrzebne...
A później przyjdzie jakąś nieooradna sierota, zdziedziczy czajnik z mikrofalówki i za 3 miesiące będziesz się przebijał przez dziwne referencje w 30 kolejnych warstwach, 8 różnych klasach i całej reszcie syfu...

Jeżeli gość nie ma uprawnień do pisania w moim repozytorium, to muszę zaakceptować jego kod i na przeglądzie wyjdzie, że robi coś głupiego (w moim odczuciu). Jeżeli ma uprawnienia do pisania, to i tak zdejmie modyfikator sealed.

A jeżeli gość robi to gdzieś indziej, to najprawdopodobniej nie jest to moja sprawa.

1
Afish napisał(a):

Słaba sztuczka. Ja nie uważam się za boga programowania i nie próbuję na siłę ograniczać innym możliwości, bo potem kończy się to hakami, refleksją i jeszcze większym zamieszaniem.

To nie jest sztuczka. To wprost wynika z tego, co napisałeś - robisz coś brzydkiego z punktu widzenia designu w celu oszczędzenia znaków. Nie ma też mowy o byciu żadnym bóstwem, to czysty pragmatyzm. Łatwiej napisać metodę, która operuje na klasie sealed niż na otwartej na dziedziczenie.
A jeśli ktoś zamiast porozmawiać o tym, czemu klasa jest sealed i jakie jest poprawne rozwiązanie woli hakować i używać refleksji, to chyba niedobrze świadczy o Twoich współpracownikach.

0
somekind napisał(a):

To nie jest sztuczka.

Jeżeli próbujesz sugerować, że nie daję modyfikatora z powodu oszczędności znaków, to stosujesz sztuczki erystyczne. Szkoda mi czasu na dyskusję na takim poziomie.

somekind napisał(a):

Łatwiej napisać metodę, która operuje na klasie sealed niż na otwartej na dziedziczenie.

Tak samo często łatwiej jest napisać ifologię zamiast porządnej architektury.

somekind napisał(a):

A jeśli ktoś zamiast porozmawiać o tym, czemu klasa jest sealed i jakie jest poprawne rozwiązanie woli hakować i używać refleksji, to chyba niedobrze świadczy o Twoich współpracownikach.

Ciągle nie rozumiesz sprawy. Jak robi to w „moim” pakiecie (gdzie ma też uprawnienia), to przecież nie będzie hakował, tylko zdejmie sealed i powie o tym na standupie. Ale jak to robi gdzieś kompletnie indziej, to zdjęcie takiego modyfikatora już może nie być trywialne, zmiana kodu i wdrożenie go we wszystkie miejsca zajmuje czas. A ja szanuję czas swój i współpracowników, więc nie wprowadzam na siłę ograniczeń, z których nie ma w miarę bezspornych korzyści.

1
Afish napisał(a):

Jeżeli próbujesz sugerować, że nie daję modyfikatora z powodu oszczędności znaków, to stosujesz sztuczki erystyczne. Szkoda mi czasu na dyskusję na takim poziomie.

Ja nie sugeruję, ja cytuję. I faktycznie dyskusja może być ciężka, jeśli interlokutor już po 4 minutach zapomina, że napisał szkoda mi czasu na dokładanie zbędnych słów.

Tak czy siak - ja piszę nawet internal przy klasach, private przy metodach i o zgrozo static przy wszystkim, co nie korzysta ze stanu. To też można uznać za "stratę czasu na dokładanie zbędnych słów", ale jak dla mnie kod powinien wyrażać intencje jego autora i być dowodem tego, że autor działał świadomie. Jeśli tego brak, to mam podejrzenia co do profesjonalizmu autora - i zazwyczaj mam rację.

Tak samo często łatwiej jest napisać ifologię zamiast porządnej architektury.

Porównujesz napisanie metody operującej na znanej sobie nierozszerzalnej klasie do stosowania ifologii? Wg jakich kryteriów?

Ciągle nie rozumiesz sprawy. Jak robi to w „moim” pakiecie (gdzie ma też uprawnienia), to przecież nie będzie hakował, tylko zdejmie sealed i powie o tym na standupie.

No i właśnie o to chodzi. Żeby musiał to sealed zdjąć, albo lepiej - żeby musiał pomyśleć, czemu jest tak, a nie inaczej, i czy przypadkiem czegoś nie zepsuje tą zmianą. (A zepsuć można zarówno jakąś implementację jak i ogólną koncepcję.)

Ale jak to robi gdzieś kompletnie indziej, to zdjęcie takiego modyfikatora już może nie być trywialne, zmiana kodu i wdrożenie go we wszystkie miejsca zajmuje czas. A ja szanuję czas swój i współpracowników, więc nie wprowadzam na siłę ograniczeń, z których nie ma w miarę bezspornych korzyści.

Korzyść jest taka, że nieco trudniej zrobić kretyńską hierarchię klas. A kretyńskie hierarchie klas to największy problem w kodzie pisanym w obiektowych językach. Ludzie mają tendencje do dziedziczenia z klas nie dlatego, że występuje między nimi relacja A is B tylko dlatego, że przypadkiem znaleźli w klasie "bazowej" metodę, której potrzebują. To jest choroba, a chorobom należy zapobiegać - bo na leczenie zazwyczaj jest za późno.

0
somekind napisał(a):

Ja nie sugeruję, ja cytuję. I faktycznie dyskusja może być ciężka, jeśli interlokutor już po 4 minutach zapomina, że napisał szkoda mi czasu na dokładanie zbędnych słów.

Interlokutor napisał w swojej pierwszej wypowiedzi w tym temacie, że nie dokłada sealed bo nie widzi sensu w blokowaniu dziedziczenia z automatu. Kolejna słaba sztuczka.

somekind napisał(a):

Tak czy siak - ja piszę nawet internal przy klasach, private przy metodach i o zgrozo static przy wszystkim, co nie korzysta ze stanu. To też można uznać za "stratę czasu na dokładanie zbędnych słów", ale jak dla mnie kod powinien wyrażać intencje jego autora i być dowodem tego, że autor działał świadomie.

A ja świadomie nie dokładam modyfikatora sealed, bo taka jest moja intencja.

somekind napisał(a):

Porównujesz napisanie metody operującej na znanej sobie nierozszerzalnej klasie do stosowania ifologii? Wg jakich kryteriów?

Napisałeś, że trudniej jest napisać klasę bez modyfikatora sealed, a ja napisałem, że trudniej jest zrobić dobrą architekturę bez ifologii. Jak programiście nie chce się myśleć, to już jego problem.

somekind napisał(a):

No i właśnie o to chodzi. Żeby musiał to sealed zdjąć, albo lepiej - żeby musiał pomyśleć, czemu jest tak, a nie inaczej, i czy przypadkiem czegoś nie zepsuje tą zmianą. (A zepsuć można zarówno jakąś implementację jak i ogólną koncepcję.)

Programiści w moim zespole nie potrzebują dodatkowego modyfikatora, żeby włączyć myślenie.

somekind napisał(a):

Korzyść jest taka, że nieco trudniej zrobić kretyńską hierarchię klas. A kretyńskie hierarchie klas to największy problem w kodzie pisanym w obiektowych językach. Ludzie mają tendencje do dziedziczenia z klas nie dlatego, że występuje między nimi relacja A is B tylko dlatego, że przypadkiem znaleźli w klasie "bazowej" metodę, której potrzebują. To jest choroba, a chorobom należy zapobiegać - bo na leczenie zazwyczaj jest za późno.

Jeżeli robią to w moim pakiecie, to możemy pogadać o tym na standupie, przy przeglądzie kodu, przy obiedzie, jest masa okazji. A jak robią to zupełnie gdzieś indziej, to jest to ich sprawa, jak trzeba będzie, to zdejmą ten modyfikator na poziomie binarnym i nic na to nie poradzę.

5

Oznaczanie jako sealed nie tylko blokuje dziedziczenie na przyszłość. Widząc taką klasę z miejsca wiem, że właśnie teraz nikt po niej nigdzie nie dziedziczy. Nie muszę się tym scenariuszem martwić ani go uwzględniać. Jedno słowo kluczowe załatwia temat. W przeciwnym wypadku upewnienie się wymagałoby już pomocy IDE, a i tak niekoniecznie musi to dawać pewność, jeśli nie mam kontroli nad całością kodu. A mogę nie mieć, jeśli np. klasa ta jest publiczna w jakiejś współużywanej bibliotece.

Generalnie dobry design powinien raczej zniechęcać, niż zachęcać do dziedziczenia. Tutaj rację mają twórcy Kotlina, w którym każda klasa jest domyśłnie sealed (final), a jeśli chcemy dziedziczyć, musimy oznaczyć ją wprost jako open. Jest to logiczne, no bo ile procent klas doczekuje się ostatecznie potomstwa? :)

Fakt, że w C# tak nie jest, uważam za ślad Javowych zaszłości, które bywają widoczne w tym języku (dobrym, ale prawie dwudziestoletnim).

Podejścia pt. "programiści w moim zespole nie potrzebują dodatkowego modyfikatora, żeby włączyć myślenie" pozwolę sobie nie skomentować

0

Jeżeli ktoś zrobi sobie klasę B, która B : A, to jaki to ma wpływ na A? jaki bug może powstać z A itd.

1

Możesz mieć jakieś założenia, co do klasy A i używać jej w konkretny sposób. Jeżeli nie zaprojektowałeś klasy pod dziedziczenie i ktoś zmieni zachowanie A w klasie B albo o zapomni o czymś ważnym, to coś może przestać gdzieś działać, kiedy użyjesz B w miejscu A.

2

Albo zmienisz delikatnie lub mniej delikatnie zachowanie metody z A, na którym opierały się mechanizmy z B i masz z automatu X poprawek.
A, że zazwyczaj(no dobra, hiperbolizuję, ale wiarę już straciłem) w takim wypadku po B zdziedziczymy sobie C, później D, to na końcu łańcucha będziesz miał odkurzacz...

I kończy się tak, że DialogData dziedziczy z DialogButtons, który dziedziczy z DialogHeader, który dziedziczy z DialogStandard.
A Ty się zastanawiasz kto i jaki klej wąchał zanim musiałeś się tłuc z tym potworkiem, przeszukując osiem poziomów, żeby znaleźć miejsce, w którym zmienia Ci się wartość pola :D

Yay!

0

@Afish: a czy korzystasz z metod prywatnych w swoim kodzie?

0

Tak.

2

Po co w takim razie to robisz? Przeciez masz inteligentnych współpracownikow, sami powinni się domyśleć z jakich metod powinno się korzystać a z jakich nie?

4
WeiXiao napisał(a):

Jeżeli ktoś zrobi sobie klasę B, która B : A, to jaki to ma wpływ na A? jaki bug może powstać z A itd.

https://en.wikipedia.org/wiki/Yo-yo_problem

https://en.wikipedia.org/wiki/Fragile_base_class

Nie wszystkie problemy muszą od razu mieć charakter bugów.

Dziedziczenie generalnie osłabia enkapsulację.

Konfigurowanie różnic w zachowaniu za pomocą hierarchii dziedziczenia prowadzi też często do problemów z designem. Do klasy-rodzica dodajemy dwoje dzieci, bo różnią się od siebie pod jakimś względem, ale dzielą wspólną bazę. Potem jednak okazuje się, że musimy zróżnicować je pod kolejnym, niezależnym względem - co tworzy cztery różne kombinacje zachowań. A tego nie da się już upchnąć w hierarchię dziedziczenia nie robiąc przy tym syfu.

Gdybyśmy od początku stosowali kompozycję i delegację, nie byłby to duży problem. Teraz musimy całość zrefaktorować, ewentualnie tworzyć niezdrowe hiearchie: do ChildA i ChildB trzeba dodać GrandchildAC, GrandchildAD, GrandchildBC i GrandchildBD. Pojawiają się problemy z reużywalnością kodu - a przecież po to dziedziczyliśmy, żeby było z tym łatwo. Poza tym decyzje zaczynają być arbitralne i niejasne, bo czemu rozróżnienie między A i B ma być zaimplementowane na wyższym poziomie klasowej hierarchii, niż rozróżnienie między C i D? Dlaczego nie odwrotnie, skoro te rozróżnienia są równorzędne i niezależne od siebie?

W językach pozwalających na dziedziczenie wielokrotne jest to jeszcze do wykonania, ale dziedziczenie wielokrotne otwiera kolejną puszkę Pandory - to jest powód, dla którego zostało wyklęte w większości języków obiektowych.

1

@scibi92:
Oni się nie muszą domyślać, wszystko jest w kontrakcie, dopóki ktoś implementuje zgodnie z nim — co zazwyczaj sprowadza się do „programowania do interfejsu” — to modyfikator widoczności nie ma znaczenia. Ba, ja zasadniczo uważam, że wszystko mogłoby być publiczne, o ile tylko kontrakt jest dobrze określony, jeżeli ktoś chce iść na przekór (bo musi, bo tak mu wygodniej, bo inaczej nie umie, bo cokolwiek innego), to wtedy może sobie spokojnie korzystać z implementacji i mieć wsparcie kompilatora, a nie jechać przez refleksję i czekać, aż po aktualizacji wszystko się zepsuje. Niestety, rzadko udaje się tak zapisać kontrakt, żeby móc wszystko robić publiczne, więc trochę rzeczy jest prywatnych, ale to już kwestia technologii, zespołu, języka i takich tam.

2

No właśnie private to tez jest pewnego rodzaju kontrakt - możesz używać tego tylko w danej klasie. Sealed w C# to tez kontrakt mówiący że klasa nie jest przeznaczona do dziedziczenia. Ja wolę miec kolege który stworzy final klas w Javie (czyli to co sealed class w C#) i wtedy mam wszystko jasne. Dodanie jednej deklaracji z IDE to 2 sekundy, a czasu na domysły zaoszczędzone całe mnóstwo...

0

No jak nie chcę, żeby mi ktoś po klasie dziedziczył, to też daję sealed. Tylko ja staram się tak pisać kod, żeby ktoś mógł po tym dziedziczyć, wolę takie podejście, niż „daję sealed, bo tak jest łatwiej i mniej trzeba myśleć”.

2

Również oznaczam klasy jako sealed. Tak jak napisał @V-2 słowo to niesie dodatkową informację, np. wiem, że nie muszę używać Dispose Pattern.

Tylko ja staram się tak pisać kod, żeby ktoś mógł po tym dziedziczyć, wolę takie podejście, niż „daję sealed, bo tak jest łatwiej i mniej trzeba myśleć”

Wcześniej pisałeś, że dodanie sealed to strata czasu, ale pisanie klas tak, by być może kiedyś, ktoś, gdzieś mógł z nich skorzystać już stratą nie jest? Osobiście uważam, że ciężko jest tak napisać klasę, by bez modyfikacji można było z niej faktycznie w przyszłości skorzystać. Zazwyczaj kończy się tak, że klasa "bazowa" i tak zostanie zmieniona. I to w taki sposób, że np. połowa pól będzie private a druga protected.

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