Interfejs List -> metoda Add -> Barbara

Interfejs List -> metoda Add -> Barbara
krancki
  • Rejestracja:prawie 7 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:74.7261832, -41.7409518
  • Postów:151
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).

edytowany 1x, ostatnio: krancki
stivens
  • Rejestracja:ponad 8 lat
  • Ostatnio:około godziny
2

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


λλλ
Zobacz pozostałe 13 komentarzy
krancki
no to niemutowalne nie powinny implementować listy. Powinny implementować coś typu UnmutableList
stivens
No bo java.util to jakies glupoty sa...
krancki
Zgadza się 😋
stivens
Ogolnie wystarczyloby zeby List bylo niemutowalne a nizej w hierarchii byla mutowalna lista. Mutowalne i niemutowalne listy maja czesciowo wspolny interfejs
krancki
A dam ci okejke
Burdzi0
  • Rejestracja:prawie 9 lat
  • Ostatnio:5 miesięcy
  • Lokalizacja:Futurama
  • Postów:887
2

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


Bite my shiny metal ass!
Life throws you an error code like that, you don't have the luxury of a ZnVja2luZw== pop-up explanation *Robię projekty studenckie, pisz priv ;) *
edytowany 2x, ostatnio: Burdzi0
stivens
  • Rejestracja:ponad 8 lat
  • Ostatnio:około godziny
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.


λλλ
edytowany 3x, ostatnio: stivens
Burdzi0
  • Rejestracja:prawie 9 lat
  • Ostatnio:5 miesięcy
  • Lokalizacja:Futurama
  • Postów:887
2

@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ą.


Bite my shiny metal ass!
Life throws you an error code like that, you don't have the luxury of a ZnVja2luZw== pop-up explanation *Robię projekty studenckie, pisz priv ;) *
edytowany 4x, ostatnio: Burdzi0
Zobacz pozostałe 3 komentarze
Burdzi0
@stivens: To chcesz, żeby dodawali feature'y do Javy czy nie? xD
stivens
Ja nigdzie o dodawaniu featurow nie pisalem. Skrytykowalem tylko zle zaprojektowany interfejs. W czym problem?
Burdzi0
@stivens: odlatujesz od tematu trochę. Mówiliśmy o Liskov. Zawołam @superdurszlak niech coś podpowie xd
stivens
No jesli nie kazda podklasa List wspiera add to znaczy, ze albo to nie jest podklasa List albo add nigdy nie powinno znalezc sie w tym interfejsie :) tak to wyglada z perspektywy LSP
superdurszlak
słucham, co się stało, w czym mam pomóc? xd
stivens
  • Rejestracja:ponad 8 lat
  • Ostatnio:około godziny
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.


λλλ
edytowany 1x, ostatnio: stivens
Zobacz pozostałe 32 komentarze
stivens
To jest po prostu zle zrobione i tyle
Wibowit
Źle zrobione != łamie LSP. Kwadrat != prostokąt. To nie są synonimy.
stivens
Jak dla mnie komentarz w dokumentacji nie usprawiedliwia i nie "znika" LSP-violation. Jesli nie kazda Lista obsluguje add to znaczy, ze add nie nalezy do tego interfejsu
Wibowit
Do niewłaściwych metod w interfejsie jest ISP, a nie LSP.
stivens
W gruncie rzeczy mozna wziac dowolna "dobra" praktyce, napisac kod ktory ja lamie i napisac w dokumentacji ze to celowe i tak ma byc
superdurszlak
  • Rejestracja:prawie 7 lat
  • Ostatnio:dzień
  • Lokalizacja:Kraków
  • Postów:1999
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.


edytowany 1x, ostatnio: superdurszlak
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
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ą ;)


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
Zobacz pozostały 1 komentarz
Shalom
@stivens: niejako jedno z drugiego wynika ;) Jak zrobisz zły interfejs, to potem może się nie dać nie złamać LSP, więc dla mnie to interfejs jest tu źródłem problemu.
superdurszlak
@Shalom: z drugiej strony możesz mieć zwyczajny / ubogi interfejs i mimo to złamać LSP, także niekoniecznie jest tak że LSP jest złamane if and only if złamiesz ISP. Choć w tym przypadku tak chyba jest, że najpierw w interfejsie List pojawiło się add bo tak, a potem pojawiła się niemutowalna lista i nagle hmmm no tutaj prawda implementacje mają prawda prawo rzucać wyjątek, to jest by design
Shalom
A czy ktoś twierdził że tak jest? Gdyby tak było to po co robić 2 osobne zasady? :P Oczywiście że można łamać LSP bez łamania ISP. Ale w tym konkretnych przypadku moim zdaniem powiązanie istnieje :)
stivens
Jak najbardziej jest to powiazane i pewnie nawet przyczynowo-skutkowe.
Charles_Ray
  • Rejestracja:około 17 lat
  • Ostatnio:3 dni
  • Postów:1874
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)

”Engineering is easy. People are hard.” Bill Coughran
edytowany 2x, ostatnio: Charles_Ray
PI
  • Rejestracja:ponad 9 lat
  • Ostatnio:4 miesiące
  • Postów:2787
1

Niniejszy temat dobitnie pokazuje, jak trudno jest określić, czy SOLID został złamany czy nie :) Tak jak pisałem tutaj:
https://4programmers.net/Forum/Kariera/344443-wydajnosc_programisty?p=1710837#id1710837

edytowany 1x, ostatnio: Pinek
Zobacz pozostałe 2 komentarze
Shalom
@stivens: można by mieć interfejs ImmutableList z samymi aksesorami oraz MutableList które ma dodatkowo metody mutujące i problem by się rozwiązał? ;)
stivens
MutableList mogloby sie nawet nazywac "List" jesli ktos uwaza, ze to jest domyslny interfejs ale na litosc boska niech to mutable dziedziczy po immutable a nie odwrotnie ;)
stivens
Swoja droga, MutableList to takie ImmutableList tylko ze z metodami mutujacymi tez brzmi absurdalnie ale nie rodzi problemow.
KamilAdam
Kotlin udowadnia że jednak czasem taka definicja rodzi problemy. W kotlinie możesz wziąć mutowalną listę, zrzutować na niemutwalną, przekazać do drugiego wątku i potem ją zmieniać a drugi wątek będzie zakładać że to lista niezmienna i jest zonk. No w kotlinie niemutowalna lista oznacza tylko że samemu nie można jej zmieniać (bo nie mamy setterów) , ale czasem kto inny może (bo ma settery) XD
Wibowit
@KamilAdam: w takim razie brzmi to jak read-only view, a nie immutable list i jest to analogiczne do java.util.Collections.unmodifiableList(). @stivens MutableList dziedziczące po ImmutableList jest równie bezsensowne co ImmutableList dziedziczące po MutableList. Scala ma to dobrze rozwiązane - są abstrakcyjne typy w paczce scala.collection oraz specyficzne implementacje w paczkach scala.collection.mutable i scala.collection.immutable. Wtedy to ma sens.
W0
  • Rejestracja:ponad 12 lat
  • Ostatnio:około 8 godzin
  • Postów:3563
1
krancki napisał(a):

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.

To łamie zasadę podstawienia Liskovej i jest głupie. Przy czym od razu zaznaczę, że nie każde złamanie zasady SOLID czy DRY jest głupie - chociaż muszę przyznać, że zdecydowana większość takich przypadków do czegoś co nazywam "świadomym łamaniem zasad" nie należy.

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).

Jest dokładnie w drugą stronę. To ludzie implementujący interfejs List powinni się zatroszczyć o to, żeby zachować zasadę podstawienia. Ludzie korzystający z tego interfejsu nie muszą się zabezpieczać bo przyjmują, że ta zasada jest spełniona. W praktyce oznacza to tyle, że nie wypycha się takich "lewych" instancji poza jakiś konkretny scope.

I tak już poza dla tych wszystkich, którzy twierdzą że zasada podstawienia nie jest złamana - taki teścik:

Kopiuj
    void clearAndAdd(final List<Object> list) {
        list.clear();
        list.add(new Object());
        
        Assert.assertEquals(1, list.size());
    }

w zależności od implementacji się wykrzaczy - ergo zasada LSP jest złamana.

edytowany 1x, ostatnio: wartek01
W0
Faktycznie nie pasuje tutaj, trochę tak stockowo podszedłem. Chodziło o "less is more"
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:32 minuty
2

Gdybyście mieli interfejs:

Kopiuj
interface Thrower {
  void maybeThrow();
}

którego implementacje w 99.999% przypadków nie rzucają exceptionami to stwierdzilibyście, że implementacja, która faktycznie rzuca wyjątkiem, łamie zasadę LSP?

Jeśli chodzi o hierarchię typów zawierającą zarówno kolekcje mutowalne jak i niemutowalne to jedynym językiem, którego znam (ale w stosunkowo niewielu językach to drążyłem) gdzie to jest sensownie rozwiązane jest Scala gdzie mamy:


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 3x, ostatnio: Wibowit
superdurszlak
  • Rejestracja:prawie 7 lat
  • Ostatnio:dzień
  • Lokalizacja:Kraków
  • Postów:1999
0
Wibowit napisał(a):

Gdybyście mieli interfejs:

Kopiuj
interface Thrower {
  void maybeThrow();
}

którego implementacje w 99.999% przypadków nie rzucają exceptionami to stwierdzilibyście, że implementacja, która faktycznie rzuca wyjątkiem, łamie zasadę LSP?

Jeśli pozostałe implementacje by design mają nie rzucać wyjątkami, a jedna z nich będzie by design rzucać wyjątkiem bo na niej nie wolno akurat tej metody wywołać, to jak najbardziej tak.


Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:32 minuty
3

Zasada LSP jest relacją rodzic-dziecko, a nie grupa-element:

Subtype Requirement: Let {\displaystyle \phi (x)}\phi (x) be a property provable about objects {\displaystyle x}x of type T. Then {\displaystyle \phi (y)}{\displaystyle \phi (y)} should be true for objects {\displaystyle y}y of type S where S is a subtype of T.

W skrócie: jeśli coś da się udowodnić dla nadtypu to powinno zachodzić dla podtypu. To, że 99.999% implementacji zachowuje się w dany sposób nie ma żadnego wpływu na zachodzenie LSP. Analizując typ T i jego podtyp S wszystkie inne podtypy T nie mają znaczenia. Kodowanie do interfejsu polega na kodowaniu do kontraktu zawartego w interfejsie, a nie w 99.999% jego implementacji.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
KamilAdam
Ładny podstęp
stivens
  • Rejestracja:ponad 8 lat
  • Ostatnio:około godziny
1

Ale to jest wlasnie to o czym mowilem. Ktos napisal kod, ktory najpierw zlamal ISP a w efekcie tego poszlo jeszcze LSP, wiec sobie dopisal do dokumentacji ze niektore implementacje moga rzucic wyjatkiem. No i takim o to sposobem "nie ma problemu bo wszystko udokumentowane".

Tylko ze kazdy szajs mozna udokumentowac.


λλλ
edytowany 1x, ostatnio: stivens
superdurszlak
  • Rejestracja:prawie 7 lat
  • Ostatnio:dzień
  • Lokalizacja:Kraków
  • Postów:1999
0
stivens napisał(a):

Ale to jest wlasnie to o czym mowilem. Ktos napisal kod, ktory najpierw zlamal ISP a w efekcie tego poszlo jeszcze LSP, wiec sobie dopisal do dokumentacji ze niektore implementacje moga rzucic wyjatkiem.

Notabene, nie znam zaszłości historycznych także mogę się mylić, ale jeśli metoda j.u.List#add wcześniej nie przewidywała rzucenia wyjątku, a następnie po pojawieniu się niemutowalnych list zaczęła go przewidywać, to strzelam że dodatkowo złamane zostało też OCP. Nawet jeśli uznamy, że dokumentacją możemy "odłamać" LSP.

No i takim o to sposobem "nie ma problemu bo wszystko udokumentowane".

Tylko ze kazdy szajs mozna udokumentowac.

To. Jeśli docsy mają robić za perfumę maskującą dowolny code smell, to praktycznie jakiekolwiek regułki i zasady można wyrzucić do kosza - jako, że każde złamanie można i tak uzasadnić i unieważnić :P


Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:32 minuty
2

Ktos napisal kod, ktory najpierw zlamal ISP a w efekcie tego poszlo jeszcze LSP

Gdyby jedna reguła implikowała drugą to po co mieć dwie reguły? Reguły są ortogonalne jak parzystość i dodatniość.

wiec sobie dopisal do dokumentacji ze niektore implementacje moga rzucic wyjatkiem

List.add od początku było opcjonalne i miało wyspecyfikowane @throws UnsupportedOperationException if the <tt>add</tt> method is not supported by this list. Widać to np na: https://github.com/fanhongtao/JDK/blob/jdk_1.2.1/src/java/util/List.java#L147-L169 Później doszło nawet Throws: NullPointerException - if the specified element is null and this list does not permit null elements.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
KamilAdam
Reguły są ortogonalne jak parzystość i dodatniość. Gorzej jest, niektóre reguły są sprzeczne :( Pisał o tym Uncle Bob chyba w czystej architekturze
stivens
  • Rejestracja:ponad 8 lat
  • Ostatnio:około godziny
2

Ja nie napisalem o logicznej implikacji tylko o relacji przyczynowo-skutkowej.

Mogloby byc i od poczatku. Ktos sie zorientowal, ze nie kazda lista bedzie to obslugiwac ale zamiast wywalic metode na etapie definicji to dorzucil dokumentacje.


λλλ
edytowany 2x, ostatnio: stivens
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:32 minuty
0

Mogloby byc i od poczatku. Ktos sie zorientowal, ze nie kazda lista bedzie to obslugiwac ale zamiast wywalic metode na etapie definicji to dorzucil dokumentacje.

Obstawiam, że to nie było łatanie po fakcie, tylko zamierzony efekt. Prawdopodobnie jeden człowiek projektował zarówno metodę List.add jak i implementacje, w których metoda add "nie działa":

Taki C# skopiował ten design nawet do generycznych kolekcji mimo, iż MS zdecydował się na zerwanie kompatybilności wstecznej podczas wprowadzania genericsów:
https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.icollection-1.add?view=netcore-3.1

Kopiuj
Exceptions
NotSupportedException
The ICollection<T> is read-only.

https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.idictionary-2.add?view=netcore-3.1

Kopiuj
Exceptions
NotSupportedException
The IDictionary<TKey,TValue> is read-only.

https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.ilist-1.insert?view=netcore-3.1

Kopiuj
Exceptions
NotSupportedException
The IList<T> is read-only.

Ale już np dla ISet<T>.add https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.iset-1.add?view=netcore-3.1 nie rzuca wyjątkiem.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
superdurszlak
Urzekła mnie ta dyskretna zmiana tematu z Javy na .NET :]
Wibowit
.NET to tylko przykład, aczkolwiek bardzo konkretny.
stivens
  • Rejestracja:ponad 8 lat
  • Ostatnio:około godziny
0

Obstawiam, że to nie było łatanie po fakcie, tylko zamierzony efekt. Prawdopodobnie jeden człowiek projektował zarówno metodę List.add jak i implementacje, w których metoda add "nie działa":

A w jaki sposob jest to sprzeczne? Bo mnie to nawet nie dziwi, ze ktos sam sobie takiego hot-fixa wymyslil :)

Ktos sie zorientowal, ze nie kazda lista bedzie to obslugiwac ale zamiast wywalic metode na etapie definicji to dorzucil dokumentacje.


Kopiowanie glupich rozwiazan przez MS juz mnie bardziej dziwi


λλλ
edytowany 3x, ostatnio: stivens
W0
  • Rejestracja:ponad 12 lat
  • Ostatnio:około 8 godzin
  • Postów:3563
0
Wibowit napisał(a):

Zasada LSP jest relacją rodzic-dziecko, a nie grupa-element:

Subtype Requirement: Let {\displaystyle \phi (x)}\phi (x) be a property provable about objects {\displaystyle x}x of type T. Then {\displaystyle \phi (y)}{\displaystyle \phi (y)} should be true for objects {\displaystyle y}y of type S where S is a subtype of T.

W skrócie: jeśli coś da się udowodnić dla nadtypu to powinno zachodzić dla podtypu. To, że 99.999% implementacji zachowuje się w dany sposób nie ma żadnego wpływu na zachodzenie LSP. Analizując typ T i jego podtyp S wszystkie inne podtypy T nie mają znaczenia.

W tym konkretnym przypadku zasada LSP jest złamana. Na początek - rozważmy List.add(index, element) dla standardowej biblioteki Javy.

W Javie istnieje klasa AbstractList której ta metoda add wygląda tak:

Kopiuj
    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }

Natomiast ArrayList (lub choćby) która dziedziczy po AbstractList już tego wyjątku nie rzuca. Więc podtyp ArrayList zachowa się inaczej niż AbstractList. Sam interfejs List nijak się oczywiście ma do LSP ponieważ metoda add(index, element) nie jest nawet zdefiniowana dla interfejsu List. Przy czym przyznaję, że od razu przeskoczyłem do rozmowy o konkretnych implementacjach w Javie, trzeba było to napisać jaśniej.

edytowany 2x, ostatnio: wartek01
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:32 minuty
1

https://docs.oracle.com/javase/8/docs/api/java/util/AbstractList.html#add-E-

Note that this implementation throws an UnsupportedOperationException unless add(int, E) is overridden.

Co możesz udowodnić dla AbstractList.add co nie zachodzi dla podtypów AbstractList?

W ogólności budowanie własnej wizji kontraktu przez analizowanie szczegółów implementacyjnych czegoś co ma jasny kontrakt jest programistycznym rakiem. Dla przykładu programiści Javy polegają na konkretnym porządku iteracji w HashMapach, co jest absurdem. Jeśli przez 10 lat HashMapa iterowała obiekty w takiej a takiej kolejności to jeśli w nowej wersji Javy kolejność iteracji się zmieni to można traktować to jako błąd? Niestety mimo iż oficjalny kontrakt niczego nie gwarantuje to jednak oczekiwania Javowców, którzy nie potrafią kodować do interfejsu sprawia, że de facto Java jest związana na stałe z czymś co hamuje optymalizacje wydajnościowe. Szczegóły od momentu 42:54 do ok 47:00:

Z hashCode zepsuli jeszcze bardziej bo szczegóły implementacyjne wstawili w dokumentację (np dla kolekcji czy stringów) i już nawet z tego powodu nie da się poprawić hashCode'ów. Stąd pomysł nowej metody Object.longHashCode, której implementacje w standardowych typach już nie będą miały w kontrakcie szczegółów implementacyjnych, a wręcz współczynniki hashowania będą się zmieniać co uruchomienie JVMki: https://openjdk.java.net/jeps/8201462


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
KamilAdam
Słyszałem że między Javą 6 a Javą 8 zmieniono implementację HashMapy i values() zwraca wartości w innej kolejności. W zespole obok mieli przez to problemy bo źle napisane testy przestały przechodzić
Wibowit
Hmm, nie dotrwałem jako Javowiec do premiery Javy 8 to nawet nie miałem powodu, by to drążyć. Wtedy to już byłem Scalowcem na pełen etat :]
W0
  • Rejestracja:ponad 12 lat
  • Ostatnio:około 8 godzin
  • Postów:3563
0
Wibowit napisał(a):

https://docs.oracle.com/javase/8/docs/api/java/util/AbstractList.html#add-E-

Note that this implementation throws an UnsupportedOperationException unless add(int, E) is overridden.

Co możesz udowodnić dla AbstractList.add co nie zachodzi dla podtypów AbstractList?

Tak jak napisałeś - to jest relacja rodzic-dziecko. Czyli konkretne dziecko (ArrayList) ma konkretny konflikt z rodzicem (AbstractList) i w tym momencie LSP jest złamane. W tym konkretnym przypadku:

  • dla dowolnej pary {index, element} AbstractList.add(index, element) zawsze zwraca UnsupportedOperationException
  • dla dowolnej pary {index, element} ArrayList.add(index, element) nigdy nie zwraca UnsupportedOperationException

W ogólności budowanie własnej wizji kontraktu przez analizowanie szczegółów implementacyjnych czegoś co ma jasny kontrakt jest programistycznym rakiem.

Pełna zgoda. Ale kontrakt, a LSP to nie są pojęcia tożsame.

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:32 minuty
1

Pełna zgoda. Ale kontrakt, a LSP to nie są pojęcia tożsame.

Jeśli LSP mówi o tym, by kodować do implementacji, a nie interfejsów to jest to reguła do wyrzucenia.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
AF
  • Rejestracja:prawie 18 lat
  • Ostatnio:29 dni
2

Nie łamie. LSP mówi o kontrakcie, nie o implementacji, a "sensowność" kontraktu nie ma znaczenia. To nie znaczy, że List.add jest dobrze zaprojektowaną metodą, ale nie jest to argument, że łapie to LSP.

Wibowit napisał(a):

Co możesz udowodnić dla AbstractList.add co nie zachodzi dla podtypów AbstractList?

Bardzo dobre, ale i odrobinkę podchwytliwe pytanie, co można udowodnić dla X i co nie zachodzi dla Y. Ogólnie zasada LSP w literalnym znaczeniu nie ma za dużo sensu. W swojej podstawowej formie mówi ona, że jeżeli da się udowodnić jakieś p dla typu bazowego, to p musi być spełnione dla typu pochodnego, ale zawsze mogę przyjąć, że moim p dla typu bazowego (o ile ma on implementację) jest dokładnie ta implementacja, co wyklucza jakiekolwiek modyfikowanie zachowania w podtypie.

Oczywistym jest, że nie o takie rozumienie LSP chodzi, więc trzeba uważać, jakie p rozważamy, a ten zbiór rozważanych p, to właśnie dobrze określony kontrakt.

edytowany 1x, ostatnio: Afish
W0
  • Rejestracja:ponad 12 lat
  • Ostatnio:około 8 godzin
  • Postów:3563
0
Wibowit napisał(a):

Pełna zgoda. Ale kontrakt, a LSP to nie są pojęcia tożsame.

Jeśli LSP mówi o tym, by kodować do implementacji, a nie interfejsów to jest to reguła do wyrzucenia.

LSP nie wypowiada się na ten temat kontraktów i nie ma nic wspólnego z kontraktami. Jeśli metoda jest abstrakcyjna to LSP się jej nie tyczy bo takiej metody po prostu nie ma.

Tak jak napisałeś:

W skrócie: jeśli coś da się udowodnić dla nadtypu to powinno zachodzić dla podtypu

W tym konkretnym przypadku w Javie wyszła słabość języka, które łatano klasami AbstractXXX i błędnie to zrealizowano . Natomiast sama zasada LSP broni przed takimi potworkami (zdarzają się częściej niż mogłoby się wydawać):

Kopiuj
    class Product {
        private Integer id;
        private String name;

        Integer getId() {
            return id;
        }

        String getName() {
            return name;
        }
    }

    interface ProductSorter {

        /**
         * Returns list of sorted products
         * @param products
         * @return
         */
        Collection<Product> sort(Collection<Product> products);
    }

    class ByNameProductSorter implements ProductSorter {

        /**
         * Returns collection of products sorted by name
         * @param products
         * @return
         */
        @Override
        public Collection<Product> sort(Collection<Product> products) {
            return products.stream()
                    .sorted(Comparator.comparing(Product::getName))
                    .collect(Collectors.toList());
        }
    }

    class ByIdProductSorter extends ByNameProductSorter {

        /**
         * Returns collection of products sorted by id
         * @param products
         * @return
         */

        @Override
        public Collection<Product> sort(Collection<Product> products) {
            return products.stream()
                    .sorted(Comparator.comparing(Product::getId))
                    .collect(Collectors.toList());
        }
    }

Zgadzam się z tym, że przy poprawnie definiowanych kontraktach LSP pozostanie zachowane. Natomiast problemy zaczynają się, gdy chcemy zdefiniować bardzo ogólny kontrakt - wtedy jest bardzo trudno przewidzieć wszystkie możliwe sytuacje.

jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około godziny
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
1
Wibowit napisał(a):

Z hashCode zepsuli jeszcze bardziej bo szczegóły implementacyjne wstawili w dokumentację (np dla kolekcji czy stringów) i już nawet z tego powodu nie da się poprawić hashCode'ów. Stąd pomysł nowej metody Object.longHashCode, której implementacje w standardowych typach już nie będą miały w kontrakcie szczegółów implementacyjnych, a wręcz współczynniki hashowania będą się zmieniać co uruchomienie JVMki: https://openjdk.java.net/jeps/8201462

Sorry za off-topic, ale nie mogę jaki paździerz. Nie wiedziałem o tym.
Otóż największym IMO problemem hashCode jest to, że należy on do Object.
To powinnien być osobny interfejs - podobnie jak Comparator.
Nie ma tak, że dany typ ma jeden idealny hashCode - bo to zależy jakie obiekty wkładamy do np. konkretnego HashMap.
Raz będzie ich mało i będzie zależeć nam na szybkiej implementacji, raz będzie mnóstwo i będziemy chcieli mniej kolizji..
czasem wystarczy w stringu pierwsza litera jako hashCode, czasem pierwsze 3 - a czasem trzeba brać implementację po wszystkim (jak obecnie).
Dla tego sameto typu możemy chcieć miec w jednym programie kilka różnych hashCode implementacji... podobnie jak z Comparator.
To oczywiscie mniej częsty case niż z Comparatorem, bo raczej będzie dotyczył programów, gdzie walczymy o cykle - ale jednak.

Przy okazji osobną wadą obecnego podejścia- implicit hashCode, jest fakt, że wiele osób nie ma pojęcia o istnieniu problemu i mamy potem dziwne bugi na produkcji.
Albo mamy durne kłótnie o implementacje hashCode na etapie tworzenia klasy. Jeśli masz klasę i wszystkie pola .. to nadal nie wiadomo jaki hahsCode jest optymalny, bo to nie od klasy zależy, ale od tego gdzie hashCode będzie używany.

W tym kontekście nowy hashCode, który powtarza największy IMO błąd poprzedniego designu to niezła $#23@#.


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 2x, ostatnio: jarekr000000
Zobacz pozostały 1 komentarz
jarekr000000
@Wibowit: i to by właśnie było IMO dobre rozwiązanie, trochę może więcej pisania, ale za to problem explicite widoczny. Ewentualnie para HasherAndEqualiser + Hashable - jeśli ktoś ma typ, gdzie można zrobić dość dobry standardowy hashCode. Wtedy do typów standardowych i record jest dostępny Hashable. A jak chcemy coś zrobić nietypowego to zawsze można zapodać własną implementację.
Wibowit
Scala ma podobnie. Jest Ordering dla TreeMapy, ale nie ma żadnego Hashingera dla HashMapy. Zawsze można to obejść opakowując klucze w obiekt liczący equalsa i hashCode'a tak jak chcemy, aczkolwiek jest to pewien narzut.
AF
Ja bym tu zadał inne pytanie — jak często przejmujemy się "optymalnością" hash code'a gdy już go implementujemy? Dla mnie zawsze celem był inny equals, nie zdarzyło mi się klepać własnego hasha dla wydajności. Albo jeszcze inaczej — czy zdarzyło Wam się mieć dwa hash code'y dla jednego typu i używać ich obu? Nie mówię tu o nowym hash codzie dla inta/stringa, bo to się robi przez osobny wrapper w OOP.
jarekr000000
@Afish - masz rację, że to jest takie coś co się wydarza raz na kilka lat w typowej aplikacji enterprise. Pamiętam przypadek, że aplikacja rozwalała sie na "unique" zrobionym po stronie klienta( i HashSet). Wszyscy myśleli, że problem leży w bazie danych..., bo problem pojawiał się w serwisie, który faktycznie czytał tony wierszy. Problem był taki, że zespół nie był w stanie dojśc co się dzieje. 1) troche brak doświadczenia w profilowaniu, 2) trochę przez to, ze ten hashCode jest "niewidoczny" w kodzie, to nikt go nie podejrzewał.
AF
To raczej nie odpowiada na moje pytanie. Jak już znaleźliście problem, to jak wiele roboty było z napisaniem lepszego hasha? Ja rozumiem, że fajnie jest mieć elastyczną architekturę na każdą okazję, ale znowu kombinowanie z jakimiś magicznymi interfejsami też by problemu nie rozwiązało, bo ludzie braliby DefaultHasher, pierwszą wklejkę ze stacka, albo kod wygenerowany przez iJ, bo w końcu zadziała w 99,99% przypadków. Największą zaletą takiego interfejsu byłoby limitowanie zasięgu zmian, ale możliwość używania wielu hashy nie wydaje się często przydatna w korpokodzie.
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:32 minuty
1

Jeśli metoda jest abstrakcyjna to LSP się jej nie tyczy bo takiej metody po prostu nie ma.

List.add jest abstrakcyjna. Czy w takim razie LSP jej nie dotyczy? Hmm, to po co cały ten wątek?


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
W0
  • Rejestracja:ponad 12 lat
  • Ostatnio:około 8 godzin
  • Postów:3563
0
Wibowit napisał(a):

Jeśli metoda jest abstrakcyjna to LSP się jej nie tyczy bo takiej metody po prostu nie ma.

List.add jest abstrakcyjna. Czy w takim razie LSP jej nie dotyczy? Hmm, to po co cały ten wątek?

Tak jak napisałem wcześniej:

  1. Bardzo niejawnie przeszedłem z rozmowy o interfejsie na temat rozmowy o konkretnych implementacjach
  2. Nie dotyczy. Metoda abstrakcyjna nie jest stricte własnością obiektu, jedynie znacznikiem, że każdy obiekt implementujący coś takiego powinien taką własność mieć.
  3. Wątek jest w celu wyjaśnienia wątpliwości.
AF
  • Rejestracja:prawie 18 lat
  • Ostatnio:29 dni
0
wartek01 napisał(a):
  1. Nie dotyczy. Metoda abstrakcyjna nie jest stricte własnością obiektu, jedynie znacznikiem, że każdy obiekt implementujący coś takiego powinien taką własność mieć.

Oczywiście, że dotyczy. LSP mówi o kontrakcie, w tym przypadku o sygnaturze metody, konwencji wywołania, parametrach i całej reszcie części technicznej. To jest świetny przykład, bo tutaj kompilator broni programisty przed złamaniem LSP, ale w innych językach tak nie musi być — metoda może nie istnieć, metoda może mieć inną konwencję wywołania i rzuci segfault, metoda może zostać inaczej "zmanglowana" przez kompilator i w efekcie będzie miała inną nazwę symboliczną.

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)