Interfejs List -> metoda Add -> Barbara

0

Hejo
Mam dlemacik, ostatnio się zastanawiałem nad Interfejsem List i nad operacjami mutowalnymi.
Jeżeli do interfejsu list przypiszemy liste niemutowalną to oczekiwanym zachowaniem metody add będzie rzucenie wyjątku.
No i pytanie czy to łamie zasade liskov? Z jednej strony dokumentacja mówi że metoda add może rzucić wyjątkiem i jest to oczekiwany efekt.
Ale od razu nasuwa mi się pytanie, to po co jest metoda która może rzucać taki wyjątek. Jeżeli ktoś operuje na liście, to co ma zawsze się zabezpieczać przed tym że ktoś może wstawić niemutowalną kolekcje (wiadomo modyfikowanie listy jest bee, ale załóżmy taki przypadek).

2

Oczywiscie ze to lamie Liskov.
Po prostu porzuc java.util i przerzuc sie na Vavr

1

Zasada:

Funkcje które używają wskaźników lub referencji do klas bazowych, muszą być w stanie używać również obiektów klas dziedziczących po klasach bazowych, bez dokładnej znajomości tych obiektów.

Definicja w dokumentacji:

boolean add​(E e)

Appends the specified element to the end of this list (optional operation).

Lists that support this operation may place limitations on what elements may be added to this list. In particular, some lists will refuse to add null elements, and others will impose restrictions on the type of elements that may be added. List classes should clearly specify in their documentation any restrictions on what elements may be added.

Specified by:
add in interface Collection<e>
Parameters:
e - element to be appended to this list
Returns:
true (as specified by Collection.add(E))
Throws:
UnsupportedOperationException - if the add operation is not supported by this list
ClassCastException - if the class of the specified element prevents it from being added to this list
NullPointerException - if the specified element is null and this list does not permit null elements
IllegalArgumentException - if some property of this element prevents it from being added to this list

No i teraz tak - dokumentacja mówi Ci wprost co powinieneś obsłużyć, żeby mieć pewność, że każdy podtyp będzie elegancko obsłużony. @stivens mówi, że oczywiście łamie Liskov. Ja się nie zgadzam. Dostajesz wprost info co powinieneś zrobić, żeby wszystko śmigało. Nawet masz napisane, że dodawanie jest opcjonalne, tj. istnieje podzbiór podtypów, który nie będzie tego obsługiwał - stąd te wyjątki. To, że nie jest to tak oczywiste jakbyśmy chcieli nie powoduje, że zasada jest złamana. Tutaj bardziej łamiemy KISS, bo nie jest to intuicyjne.
Jeśli Ci to nie pasuje to powinieneś wybrać inny typ jako bazowy, tak aby samo sprawdzanie typów załatwiło Ci zgodność z akcjami, z których chcesz korzystać (o ile taki istnieje). Przerzucanie się na vavr jest jakąś opcją, to jest przyzwoita libka (choć sam nie miałem okazji stosować).

A i jeszcze jedna kwestia mówienie, że papier przyjmie wszystko moim zdaniem trochę nie do końca ma tutaj podstawy - tworzenie biblioteki standardowej nie jest łatwym zadaniem, zwłaszcza kiedy dbasz o kompatybilność wsteczną. Być może można to było zrobić lepiej (patrz -> Scala), być może nie, nie czuję się tutaj w żadnym wypadku upoważniony do oceny na podstawie tego co wyczytałem w necie. Wracając. nie bycie zgodnym z dokumentacją i narzekanie, że coś nie działa to trochę jak składanie mebli z Ikei bez instrukcji i denerwowanie się, że stół jest krzywy i czasami nam kawa spada

0

@Burdzi0: ten interfejs jest po prostu zaprojektowany przeciwko LSP i zadna dokumentacja tego nie zmieni.

Juz widze jak programisci obsluguja wyjatki przy List::add. Raczej nobody cares.

Gdyby List bylo niemutowalne a potem bylo jakies MutableList extends List to wszystko by bylo cacy.

1

@Burdzi0: ten interfejs jest po prostu zaprojektowany przeciwko LSP i zadna dokumentacja tego nie zmieni.

@stivens: udowodnij, że tak jest, bo jak na razie to tylko Twoja opinia xd
Podałem swoje argumenty i podparłem je oficjalną dokumentacją. Albo ja jestem w błędzie, albo Ty, dobrze by było wiedzieć xd

Juz widze jak programisci obsluguja wyjatki przy List::add. Raczej nobody cares.

Płakanie potem, że coś wybuchło jest tylko i wyłącznie ich winą.

Gdyby List bylo niemutowalne a potem bylo jakies MutableList extends List to wszystko by bylo cacy.

Zrobienie tego w obecnej bibliotece standardowej jest niemożliwe, jeśli chcesz mieć jakąkolwiek kompatybilność wsteczną.

0

Kazdy Pies jest Ssakiem ale nie kazdy Ssak jest Psem.
Kazda MutowalnaLista jest Lista ale nie kazda Lista jest MutowalnaLista.

Takie dziedziczenie jak proponuje biblioteka standardowa nie ma sensu.

3
Burdzi0 napisał(a):

No i teraz tak - dokumentacja mówi Ci wprost co powinieneś obsłużyć, żeby mieć pewność, że każdy podtyp będzie elegancko obsłużony. @stivens mówi, że oczywiście łamie Liskov. Ja się nie zgadzam. Dostajesz wprost info co powinieneś zrobić, żeby wszystko śmigało. Nawet masz napisane, że dodawanie jest opcjonalne, tj. istnieje podzbiór podtypów, który nie będzie tego obsługiwał - stąd te wyjątki. To, że nie jest to tak oczywiste jakbyśmy chcieli nie powoduje, że zasada jest złamana. Tutaj bardziej łamiemy KISS, bo nie jest to intuicyjne.
Jeśli Ci to nie pasuje to powinieneś wybrać inny typ jako bazowy, tak aby samo sprawdzanie typów załatwiło Ci zgodność z akcjami, z których chcesz korzystać (o ile taki istnieje). Przerzucanie się na vavr jest jakąś opcją, to jest przyzwoita libka (choć sam nie miałem okazji stosować).

No ja się nie zgodzę z Tobą, a zgodzę się ze @stivens - to, że ktoś sobie napisze w dokumentacji, albo w komentarzu w kodzie, albo na swoim blogu (będąc Chief Executive Guru Maintainerem czegoś co ma 150k gwiazdek na GH), że to jest big ball of mud by design nie sprawi, że to przestanie być kulą błota i przestanie łamać LSP.

Jak ja lub Ty popełnię coś takiego w swoim kodzie i będę próbował wcisnąć komuś interfejs z paroma implementacjami mówiąc

no, wiesz, wprawdzie AbstractDog ma metodę bark(), ale używaj jej tylko na LivingDog bo DogStatue i DeceasedDog rzucają wyjątkiem

To to cóś wyląduje na śmietniku historii szybciej, niż zdążysz wymówić vavr.

Jeśli jedna z implementacji wali beztrosko wyjątkiem, gdy spróbujesz wywołać na niej metodę z interfejsu lub klasy abstrakcyjnej, to z punktu widzenia LSP to jest po prostu ultra-bajzel i zdecydowanie nie powinno tak być. Nawet jeśli dopiszesz w javadocu tego interfejsiku, że rzucanie wyjątku jest by design.

Żeby to w ogóle miało szansę mieć ręce i nogi, to coś takiego jak MutableList powinno rozszerzać podstawowy interfejs List o mutowalność (prawdopodobnie implementując dodatkowy interfejs który wprowadza mutujące metody). Ewentualnie, mogłyby mieć wspólny interfejs public List<T> add(T element), gdzie MutableList dodawałby element do wewnętrznego stanu zwracając siebie, a ImmutableList zwracałby nową, zapewne również niemutowalną listę - przynajmniej nie rozwalasz zachowania w jednej z implementacji, choć wtedy jedna metoda ma side-effects a druga nie, co też jest słabawe.

4

Ja osobiście uważam ze to raczej złamanie ISP -> interfejs jest zbyt bogaty i stąd implementacje które sobie z nim nie radzą ;)

3

Hejo
Mam dlemacik, ostatnio się zastanawiałem nad Interfejsem List i nad operacjami mutowalnymi.
Jeżeli do interfejsu list przypiszemy liste niemutowalną to oczekiwanym zachowaniem metody add będzie rzucenie wyjątku.
No i pytanie czy to łamie zasade liskov? Z jednej strony dokumentacja mówi że metoda add może rzucić wyjątkiem i jest to oczekiwany efekt.

Łamie, ponieważ wg. zasady Liskov kod kliencki powinien działać poprawnie dla podtypów, a ten się po prostu wywali. Taką pułapką jest np. Arrays.asList(...), które zwraca niemodyfikowalną listę - nieraz się na to naciąłem.

Ale od razu nasuwa mi się pytanie, to po co jest metoda która może rzucać taki wyjątek. Jeżeli ktoś operuje na liście, to co ma zawsze się zabezpieczać przed tym że ktoś może wstawić niemutowalną kolekcje (wiadomo modyfikowanie listy jest bee, ale załóżmy taki przypadek).

Tak jest niestety w Javie, ale to nie jest argument, że to jest dobre podejście. Są na to 3 niewykluczające się wyjścia:

  1. piszesz testy, dzięki czemu masz wyspecyfikowane w jaki sposób kod jest używany (to zawsze)
  2. używasz lepszych kolekcji, które chronią nie w runtime, ale podczas kompilacji, np. tych z Vavra
  3. wszędzie robisz try/catcha (nie polecam)
1

Niniejszy temat dobitnie pokazuje, jak trudno jest określić, czy SOLID został złamany czy nie :) Tak jak pisałem tutaj:
Wydajność programisty

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