SOLID w praktyce

2

Wątek na podstawie tematu Jak precyzyjnie zdefiniować pojedynczą odpowiedzialność w kontekście zasady SRP z S.O.L.I.D
Powiem szczerze że o ile zasada pojedyńczej odpowiedzialności jest nieco trudna do zrozumiania, to nie widze problemow związanych z innymi literkami.
Czyli po kolei:

  • Single Responsibility Principle - jak pisałem, najtrudniejsza część SOLID i sam mam problemy żeby wiedziec czy kod spełnia tą zasadę
  • Open/Closed Principle
  • Liskov Substition Principle
  • Interface Segregation Principle
  • Dependency inversion principle

Open/Closed rozumiem w ten sposób, że jeśli na przykład mamy odtwarzacz muzyczny, i mamy komponent odpowiedzialny za wczytanie muzycznych metadanych z plików, to jeśli chcemy dodać obsługę nowego formatu np. WAV oprócz istniejących , to powinniśmy to zrobić tak by nie naruszać kodu odpowiedzialnego za pozostałe formaty. W OOP głównie stosuje się od tego interfejsy i polimorfizm, czyli w takiej Javie mielibyśmy AudioFileMetadataProvider i klasy typu Mp3AudioFileMetadataProvider

Liskov Substition Principle - oznacza to że podczas implementacji poliormizmu powinniśmy zastosować się do określonego kontraktu.Przykładowo jeśli jest Comparator<T> z metodą

int	compare(T o1, T o2)

i powinna ona zwrócić ujemną liczbę, zero, lub liczbę dodadnią jeśli odpowiednio t1<t2/t1==t2/t1>t2 to implementacje powinniśmy tak napisac żeby przestrzegrać tej reguły.

Co do ISP i DIP myślę że to prostsze niż pierwsze 3 litery. Dodatkowo sądzę że zasady z SOLID też przynajmniej częściowo pasują do FP - tam sa funkcje wyższego rzędu czy polimorfizm, tylko że oparty na czyms nieco innym

O jawnym łamaniu LSP przez klasy sealed nawet nie wspominam.

@piotrpo: co masz na myśli?

0

O to też problem, bo kiedy kod jest tag naprawdę open? Logikę można wyciągnąć do interfesju/implementacji dla pojedynczej linijki kodu i możemy jechać tak rekursywnie aż nam się nie znudzi. O ile przesadzanie z S zazwyczaj nie jest problemem tak O może tworzyć niepotrzebne abstrakcje, które komplikują kod. Chyba dobrą metryką jest liczba linii potrzebnych do stworzenia/poskładania tych obiektów vs. liczba linii czystej logiki, którą dostarczają. Jeśli to pierwsze jest kilkukrotnie wyższe to pewnie mamy przeinżynierowany kod. Dobrym przykład to https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition

8

Zarówno S jak i O sprowadzają się w praktyce do tego, aby nie umieszczać całego kodu w jednej metodzie albo wszystkich metod w jednej klasie, a i rozszerza to na interfejsy, wszystkie trzy razem sprowadzają się do unikania god-objectów.
S i D mówią o tym, żeby nie pisać kodu spaghetti, tylko na nieco innym poziomie.
A L dotyczy tych wyjątkowych przypadków, gdy się dziedziczy, i też mówi w sumie o tym, żeby wtedy nie robić spaghetti.

slsy napisał(a):

O to też problem, bo kiedy kod jest tag naprawdę open?

Wtedy, kiedy dodanie nowego ficzera nie prowadzi do zmian w istniejącym kodzie. W praktyce np. strategia, chain of responsibility, CQS zamiast drabinki ifów w ośmiotysięczniku.

0
scibi_92 napisał(a):

O jawnym łamaniu LSP przez klasy sealed nawet nie wspominam.

@piotrpo: co masz na myśli?

LSP oznacza, że jeżeli mam metodę doSomething(Car car), to moge jej przekazać cokolwiek, to rozszerza/implementuje Parent. Czyli nie mogę tam mieć np.

String sound  = switch(car){
  case Corvette -> "bruuuum"
  case Honda -> "wziuuu"
  case Syrenka -> "brymbrbyrbyr"
  default -> null
}

Bo mogę sobie rozszerzyć klasę Car tak, że ten dźwięk nie będzie dla niej działać. Czyli moim zdaniem LSP zostaje tutaj złamane.
Jeżeli Car będzie sealed, to moim zdaniem, ten kod nadal będzie naruszać LSP. Nie będzie to tak groźne i przestanie być paskudne.
W moim rozumieniu, sealed class właściwie "wymusza" zachowania takie jak wyżej widać (może nie widzę zastosowania sealed w innych przypadkach).
Z mojego punktu widzenia sealed właściwie głównie ostrzega o tym, że LSP w przypadku tej konkretnej klasy jest łamane.

I dla wyjaśnienia, nie uważam, że to zło - zwyczajnie w pewnych warunkach chowamy sobie LSP do szuflady, bo mamy jakiś inny, ważniejszy.

6
somekind napisał(a):
slsy napisał(a):

O to też problem, bo kiedy kod jest tag naprawdę open?

Wtedy, kiedy dodanie nowego ficzera nie prowadzi do zmian w istniejącym kodzie. W praktyce np. strategia, chain of responsibility, CQS zamiast drabinki ifów w ośmiotysięczniku.

Zawsze znajdzie się taki ficzer, którego dodanie będzie wymagało modyfikacji istniejącego kodu.

I niby dlaczego zmiana istniejącego kodu miałaby być gorsza niż dopisanie nowego kodu? Zwłaszcza jeśli z powodu chęci zachowania otwartości skomplikowany został istniejący kod. Wyglada to trochę jak promocja w markecie. Zapłać więcej teraz za dwie sztuki, z których jednej teraz nie potrzebujesz, ale może przyda się później.

Dla mnie O stoi w jawnej sprzeczności z YAGNI, więc jest podobna sytuacja co z S. Musisz przewidywać co się prawdopodobnie zmieni i jakie nowe ficzery będą na pewno potrzebne. Bo robienie na zapas wcale nie jest dobre i może się właśnie skończyć Enterprise FizzBuzz. Rośnie poziom skomplikowania, spada poziom czytelności kodu, a dodanie tych rzeczy których nie przewidziałeś staje się trudniejsze.

3
somekind napisał(a):

Zarówno S jak i O sprowadzają się w praktyce do tego, aby nie umieszczać całego kodu w jednej metodzie albo wszystkich metod w jednej klasie, a i rozszerza to na interfejsy, wszystkie trzy razem sprowadzają się do unikania god-objectów.

Proponuję w takim razie:
DLID
czyli Do not put all code in a single method + L I D . I będzię dużo jaśniejsze niż jakieś mętne SRP czy OC.

Kiedyś też mi się SOLID wydawał dość oczywisty, ale obecnie większość z tych reguł jest dla mnie mętna (I i D w miarę ok).
Nad samym LSP można by zrobić kilkugodzinny wykład - co to w zasadzie znaczy i przede wszystkim - jak tą zasadę sensownie sformułować. Liskowa w którymś tam podejściu to zrobiła z tego co pamiętam, ale to było ileś iteracji. Popularne w internecie wytłumaczenie Simply put, the Liskov Substitution Principle (LSP) states that objects of a superclass should be replaceable with objects of its subclasses without breaking the application. to kompletna bzdura. Sensowna definicja LSP jest dość skomplikowana i bazuje na "kontraktach", tylko teraz trzeba przyjąć definicję kontraktu... (ale powiedzmy, że się da)

@piotrpo:
jeśli masz sealed class to poniekąd unikasz LSP i OC - sealed class ( instancje) można traktować jako wewnętrzny kod, który normalnie znajdowałby się np. w metodzie, prywatnych klasach i służy realizacji konkretnego algorytmu. Tyle, że kod zamiast być zapisany na ifach i flagach jest zapisany częściowo na "klasach" / ADT.

2

@Krolik: Zmiana istniejącego kodu jest "gorsza" bo może mieć wpływ wpływ na już istniejące w aplikacji powiązania z innym kodem. Piszesz aplikację w pewnym momencie dochodzisz do momentu, kiedy potrzebujesz zmienić implementację listy (tak, przykład z czapy), skutek tej zmiany może walnąć wszędzie (nawet nie wiesz gdzie). Jeżeli sobie dopiszesz własną, nową implementację listy to zakres zmiany jest ograniczony do konkretnego kawałka, który napisałeś. Jeżeli jej gdzieś użyjesz, to tez wiesz, o tym, że to zrobiłeś. Masz nawet przykład z Java - była sobie klasa Vector, która miała jakieś tam wady. Postanowiono zrobić coś o prawie identycznej funkcjonalności (ArrayList), ale ponieważ Vector był już wszędzie, nie wiadomo gdzie, to postanowiono dodać nową klasę, bo dawało to pewność, że nie wpłynie na kawałki kodu, które ktoś już napisał. Oczywiście jest to duże uproszczenie.

YAGNI nie stoi i nie może stać w sprzeczności z SOLID. Jeżeli piszesz rzeczy, których potrzebujesz tu i teraz, to zakładasz, że za chwilę będziesz musiał to zmienić, więc powinno ci zależeć na łatwości wprowadzania tych zmian. Przestrzeganie SOLID ma na celu właśnie uzyskanie tej łatwości, poprzez ograniczenie wpływu zmian do poziomu, który jesteś w stanie kontrolować. Nie musisz "przewidywać" co będzie zawierał kod, który kiedyś napiszesz, ale powinieneś poczynić jakiś wysiłek, żeby dało się ten kod (o którym jeszcze nie wiesz), łatwo napisać i zintegrować z całością.

3

dla mnie SOLID
S - pisz małe klasy(funkcje/moduły) zamiast dużych
O - nie pisz na pałę, tylko pozwól na jakąś konfigurację
L - uważaj, żebyś nic nie zepsuł po drodze
I - izoluj poszczególne części aplikacji od siebie, korzystaj (w środku funkcji/klasy/modułu) tylko z tego, co potrzebujesz.
D - architektura oparta na wtyczkach

ew. skrótowo:
S, O, I, D mówią o decouplingu/enkapsulacji/izolacji/dzieleniu aplikacji na wiele niezależnych części/, a L jest z innej parafii, ale wrzucone, żeby wyszło słowo SOLID.

a poza tym SOLID to bełkot przez to w jaki sposób został zdefiniowany. Na siłę utworzony akronim. A rozwinięcia skrótów takie, że trzeba się zastanawiać, co poeta miał na myśli.

2

@Krolik:

Dla mnie O stoi w jawnej sprzeczności z YAGNI, więc jest podobna sytuacja co z S

O ile sam SOLID traktowałbym bardziej jako takie hinty, dość uznaniowe, to te wszystkie KISSy, YAGNI czy jeszcze ten nieszczęsny DRY stawiałbym jeszcze poziom niżej. Nadal jako hinty i coś mocno uznaniowego, ale niżej niż SOLID.

0
scibi_92 napisał(a):

Wątek na podstawie tematu Jak precyzyjnie zdefiniować pojedynczą odpowiedzialność w kontekście zasady SRP z S.O.L.I.D
Powiem szczerze że o ile zasada pojedyńczej odpowiedzialności jest nieco trudna do zrozumiania, to nie widze problemow związanych z innymi literkami.
Czyli po kolei:

  • Single Responsibility Principle - jak pisałem, najtrudniejsza część SOLID i sam mam problemy żeby wiedziec czy kod spełnia tą zasadę
  • Open/Closed Principle
  • Liskov Substition Principle
  • Interface Segregation Principle
  • Dependency inversion principle

Open/Closed rozumiem w ten sposób, że jeśli na przykład mamy odtwarzacz muzyczny, i mamy komponent odpowiedzialny za wczytanie muzycznych metadanych z plików, to jeśli chcemy dodać obsługę nowego formatu np. WAV oprócz istniejących , to powinniśmy to zrobić tak by nie naruszać kodu odpowiedzialnego za pozostałe formaty. W OOP głównie stosuje się od tego interfejsy i polimorfizm, czyli w takiej Javie mielibyśmy AudioFileMetadataProvider i klasy typu Mp3AudioFileMetadataProvider

Liskov Substition Principle - oznacza to że podczas implementacji poliormizmu powinniśmy zastosować się do określonego kontraktu.Przykładowo jeśli jest Comparator<T> z metodą

int	compare(T o1, T o2)

i powinna ona zwrócić ujemną liczbę, zero, lub liczbę dodadnią jeśli odpowiednio t1<t2/t1==t2/t1>t2 to implementacje powinniśmy tak napisac żeby przestrzegrać tej reguły.

Co do ISP i DIP myślę że to prostsze niż pierwsze 3 litery. Dodatkowo sądzę że zasady z SOLID też przynajmniej częściowo pasują do FP - tam sa funkcje wyższego rzędu czy polimorfizm, tylko że oparty na czyms nieco innym

O jawnym łamaniu LSP przez klasy sealed nawet nie wspominam.

@piotrpo: co masz na myśli?

The way I see it:

  • SRP - Jak pisałem w poście tutaj Więc z uwagi na to że programy są kompozytami....

    Riddle napisał(a):

    Tak na prawdę w SRP nie chodzi o żadną "pojedynczą odpowiedzialność", "robienie jednej rzeczy", "powody do zmiany", to są brednie [...]. W SRP tak na prawdę chodzi o ograniczenie scope'u i poziomów abstrakcji jednostki (klasy, moduł lub funkcji).

  • O/C - Dla mnie to znaczy: Jeśli masz wybór, zaprojektować podobnym kosztem rozszerzalne rozwiązanie, albo nierozszerzalne - wybierz rozszerzalne. Typu, jeśli masz wybór - polimorfizm vs. switch, to wybierz polimorfizm.

    Od siebie dodam, że nie ma sensu wpychać tej zasady, jeśli koszt jej dodania jest duży. Typu, masz rozwiązanie które żywcem nie będzie rozszerzalne, chyba że zrobisz refaktor, to wiadomo - nie dodawaj extendability po nic. Ale jeśli masz wybór, jedno albo drugie - wybierz to które jest "otwarte na rozszerzenie, zamknięte na modyfikację".

  • LSP - Dla mnie to po prostu oznacza, że tam gdzie można użyć jakiegoś rozwiązania (klasy, funkcji, obiektu, modułu), tam powinno się też móc użyć jego "pod-dziecka". Tak na prawdę to to się wpisuje w "Least Astonishment principle". Większość ludzi się spodziewa, że jeśli rozszerzysz jakieś rozwiązanie, to możesz go użyć wszędzie tam gdzie działało orginalne.

  • ISP - To w zasadzie jest to samo co SRP, czyli ograniczenie poziomów abstrakcji w interfejsach i kontraktach.

  • DI - Tutaj chodzi o to, żeby wysokopoziomowe moduły nie polegały na niskopoziomowych szczegółach, banalne do implementacji przy użyciu polimorfizmu (oczywiście jeśli się wie co się robi).
    Jeśli gdzieś w logice biznesowej albo innym wysoko poziomowym module masz import na powiedzmy coś z bazą, plikiem albo czymś innym niskopoziomowym to wiesz że złamałeś DI.
    Ma to wiele zalet, jedną z nich jest to co napisał @LukeJL że wtedy tak na prawdę każdy moduł jest wtyczką.

0

czy jeszcze ten nieszczęsny DRY stawiałbym jeszcze poziom niżej

Co jest złego w dry, chyba, dobrze nie programować copy/paste, tylko pisać reużyteczny kod, bez jego powielania w kilku miejscach?

0
omenomn2 napisał(a):

czy jeszcze ten nieszczęsny DRY stawiałbym jeszcze poziom niżej

Co jest złego w dry, chyba, dobrze nie programować copy/paste, tylko pisać reużyteczny kod, bez jego powielania w kilku miejscach?

To, że czasem dwa kawałki kodu mogą wyglądać podobnie, ale tak na prawdę nie mieć ze sobą nic wspólnego, i wyciąganie i łączenie tego może mieć negatywne skutki.

Chodzi po prostu o zminimalizowanie zmian w przyszłości. Czasem redundancja jest zwiększa, czasem zmniejsza.

0

Wszystko zależy od przypadku. Prosty przykład dry.
Zamiast w każdym miejscu w którym wyświetla się cenę formatować ją przy użyciu przekazywania jakichś tam parametrów, robisz funkcję, która robi to raz, nazywasz ją price() i używasz wszędzie gdzie potrzebujesz.
O to chodzi w dry i to jest dobre.

0
omenomn2 napisał(a):

Wszystko zależy od przypadku. Prosty przykład dry.
Zamiast w każdym miejscu w którym wyświetla się cenę formatować ją przy użyciu przekazywania jakichś tam parametrów, robisz funkcję, która robi to raz, nazywasz ją price() i używasz wszędzie gdzie potrzebujesz.
O to chodzi w dry i to jest dobre.

No tak. I?

Mówiłem, minimalizacja przyszłych zmian w kodzie.

Poza tym jest durga strona monety, usuwanie wielu wystąpień "\n" w treści pliku, oraz usuwanie "\n" w XML'u to są inne operacje, i nie powinno się ich uwspólniać, bo zmiana jednego nie konieczne musi znaczyć zmianę drugiego.

Nie zawsze dwa kawałki kodu które przypadkiem wyglądają podobnie/identycznie powinny być uwspólnione.

1

@omenomn2:

Bo przy DRY nierzadko ludzie skupiają się dosłownie na kodzie, a nie na tym co on robi.

random ass example

public void LogToFile(string txt)
{
  File.AppendText("log.txt", txt);
}

public void LogToFileIfCzwartek(string txt)
{
  if (DateTime.Now.Day == czwartek)
  {
    File.AppendText("czwartek.txt", txt);
  }
}

i naiwne DRY = łączenie kodu + parametry, flagi, etc.

public void LogToFile(string txt, bool isCzwartek)
{
  var logFile = isCzwartek ? "log.txt" : "czwartek.txt";
  File.AppendText(logFile, txt);
}

A to że kod jest taki sam jeszcze nie oznacza że warto go łączyć

0

No tak. I?

Mówiłem, minimalizacja przyszłych zmian w kodzie.

No i to, że w dry nie ma nic złego i to jest podstawowa i bardzo dobra zasada, tylko, też trzeba ją stosować z głową.

Poza tym jest durga strona monety, usuwanie wielu wystąpień "\n" w treści pliku, oraz usuwanie "\n" w XML'u to są inne operacje, i nie powinno się ich uwspólniać, bo zmiana jednego nie konieczne musi znaczyć zmianę drugiego.

No jeżeli funkcja ma inne działanie i nie nadaje się do zastosowania dla xmla, to logiczne, że napisze się inną, aby działała pod xml.
Chociaż jak wyciągniesz wartości z xmla, to już z tych stringów taka funkcja zadziała.
To jest szukanie wyjątków, które potwierdzają regułę.

0

Bo przy DRY nierzadko ludzie skupiają się dosłownie na kodzie, a nie na tym co on robi.

Tak, można z dry przegiąć, ale to nie oznacza, że jest złe, tylko, że ktoś za bardzo przesadza w drugą stronę, wtedy taki kod jest niezgodny z innymi zasadami, chociażby z kiss, bo ma milion flag i pierdół, które go komplikują.

0
omenomn2 napisał(a):

Bo przy DRY nierzadko ludzie skupiają się dosłownie na kodzie, a nie na tym co on robi.

Tak, można z dry przegiąć, ale to nie oznacza, że jest złe, tylko, że ktoś za bardzo przesadza w drugą stronę, wtedy taki kod jest niezgodny z innymi zasadami, chociażby z kiss, bo ma milion flag i pierdół, które go komplikują.

SOLID też może wejść za mocno. Potem się rozkminia wzorzec odwiedzającego w wyjątkach. :D

0

@omenomn2:

Krytycy sugerują że DRY po prostu źle sugeruje na czym trzeba się skupić

i później taka polska wiki skupia się tutaj na linijkach kodu :D

DRY (ang. Don't Repeat Yourself, pol. Nie powtarzaj się) – reguła stosowana podczas wytwarzania oprogramowania, zalecająca unikanie różnego rodzaju powtórzeń wykonywanych przez programistów – na przykład unikanie tych samych czynności podczas kompilowania, unikanie wklejania (lub pisania) tych samych (lub bardzo podobnych) fragmentów kodu w wielu miejscach. Reguła pozwala na wykonywanie powtarzających się czynności jeżeli jest ona zautomatyzowana – wykonywana przez komputer. W przypadku powtarzającego się kodu źródłowego można separować powtarzający się fragment i jedynie odwoływać się do niego.

Niektórzy sugerują aby nacisk był na Single source of truth

albo jak to na angielskiej wikii jest napisane Every piece of knowledge must have a single, unambiguous, authoritative representation within a system

I to już brzmi sensowniej, aczkolwiek nie jest tak proste jak DRY

ale kompromis jest taki, że z drugiej strony DRY może być uzasadnieniem złych rozwiązań :P

3

czy jeszcze ten nieszczęsny DRY stawiałbym jeszcze poziom niżej.

W ogóle to jest zabawne, bo często ludzie nie wiedzą że DRY dotyczy bardziej "algorytmów", i stosują to do struktur danych.

Musisz przewidywać co się prawdopodobnie zmieni i jakie nowe ficzery będą na pewno potrzebne. Bo robienie na zapas wcale nie jest dobre i może się właśnie skończyć Enterprise FizzBuzz.

@Krolik: nie za bardzo rozumiem. SOLID dla mnie to pewne wskazówki i nie uważam że trzeba na siłę ich wpychać. Przykładowo, jeśli mamy eksport historii transakcji bankowej do pliku, i na razie zakładamy że obsługujemy tylko PDF to nie robimy jakiś tam interfejsów typu ReportGenerator tylko robimy jakąs tam klasę funkcję czy whatever. Później jeśli się okaże że to ma się rozrosnąć to korzystamy z IDE, robimy sobie prosty refaktor jak np. wydzielenie interfejsu i coco jambo.
Podobnie np. z dependency inversion - jak mamy jakiegoś CRUDa gdzie tylko odbieramy wiadomości z Kafki i wysyłamy jakiegoś emaila i nie ma jakieś domeny , taki po prostu nanoserwis, to nie ma sensu wydzielać dla mnie jakiejś abstrakcji. Ale jeśli już mamy jakiś kantor walutowy, gdzie mamy pobierane informacje o kursie walut, to wydzielenie osobnej warstwy infrastruktury i zrobienie jakiegoś CurrencyRateProvider i CurrencyRateRestProvider może mieć jak najbardziej sens.

5
Krolik napisał(a):

Zawsze znajdzie się taki ficzer, którego dodanie będzie wymagało modyfikacji istniejącego kodu.

Tak, to prawda, zawsze znajdzie się. Byle nie było to regułą.

I niby dlaczego zmiana istniejącego kodu miałaby być gorsza niż dopisanie nowego kodu?

Bo łatwiej napisać nową małą klasę/metodę niż kolejnego ifa w drabince w linii 7435, a przy okazji będzie to bezpieczniejsze dla już zaimplementowanych ficzerów.

Zwłaszcza jeśli z powodu chęci zachowania otwartości skomplikowany został istniejący kod. Wyglada to trochę jak promocja w markecie. Zapłać więcej teraz za dwie sztuki, z których jednej teraz nie potrzebujesz, ale może przyda się później.

Ja tak szczerze chciałbym zobaczyć to skomplikowanie przez stosowanie dobrych praktyk. Widziałem już sporo skomplikowanego kodu, ale jego skomplikowanie wynikało z uwielbienia do pisania długich procedur pełnych skoków warunkowych, żadnego SOLIDa nikt nigdy nie próbował implementować.

Dla mnie O stoi w jawnej sprzeczności z YAGNI, więc jest podobna sytuacja co z S. Musisz przewidywać co się prawdopodobnie zmieni i jakie nowe ficzery będą na pewno potrzebne.

Nie sądzę:

  • Tego, że będą różne ficzery nie trzeba przewidywać, bo jest to pewne, więc na poziomie logiki aplikacji już można zacząć używać np. CQS. Mamy OCP, nie łamiemy YAGNI.
  • W momencie, gdy dochodzi nowy sposób realizowania jakiejś logiki, możemy wydzielić wspólny interfejs dla starego i nowego sposobu. Znowu mamy OCP, nie łamiemy YAGNI.

Bo robienie na zapas wcale nie jest dobre

Oczywiście, że nie. Na szczęście żadna zasada nie mówi, aby robić rzeczy na zapas.

jarekr000000 napisał(a):

Proponuję w takim razie:
DLID
czyli Do not put all code in a single method + L I D . I będzię dużo jaśniejsze niż jakieś mętne SRP czy OC.

Jestem za. Masz siłę przebicia, możesz to zacząć promować, zobaczymy jak społeczność przyjmie.

0

Interpretacja tych skrótów nie ma sensu, bo wykładnia jest już dawno zasugerowana przez samego autora, czyli Roberta C. Martina, choćby w jego wykładach dostępnych na yt.

4

Wujek Bob ostatnio na Twitterze:

The Single Responsibility Principle (SRP): Gather together those things that change for the same reasons and at the same times. Separate those things that change for different reasons or at different times.

The Open-Closed Principle (OCP): Separate modules that frequently change from modules that change less frequently with a layer of abstraction.

The Liskov Substitution Principle (LSP): The implementation of an interface must never violate the contract between that interface and its users.

The Interface Segregation Principle (ISP): Don’t depend on things you don’t need.

The Dependency Inversion Principle (DIP): Lower level policies should depend on higher level policies.

1
Michał Kuliński napisał(a):

Wujek Bob ostatnio na Twitterze:

The Single Responsibility Principle (SRP): Gather together those things that change for the same reasons and at the same times. Separate those things that change for different reasons or at different times.

Nie kupuję tego. Przykładowo mogę pomieszać logikę z IO razem ze sobą beż żadnej separacji czy abstrakcji. I taki kod za każdym razem będzie się zmieniał razem.

The Open-Closed Principle (OCP): Separate modules that frequently change from modules that change less frequently with a layer of abstraction.

I dalej zamiast konkretów skupiamy się na wróżbiarstwie. Pisząc kod nie wiem jak często będzie się zmieniał

5
slsy napisał(a):

The Open-Closed Principle (OCP): Separate modules that frequently change from modules that change less frequently with a layer of abstraction.

I dalej zamiast konkretów skupiamy się na wróżbiarstwie. Pisząc kod nie wiem jak często będzie się zmieniał

Ten cały temat jest trochę to D... bo o ile te zasady można zdefiniować jednym zdaniem to potem potrzeba cały rozdział książki żeby to skomentować. I tak właśnie skomentował w którejś ze swoich książek Wujek Bob. Że nie chodzi tu o wróżniarstwo tylko o predykcję (progrmozowanie):

  • Jeśli moduł zmieniał się często w przeszłości to prawdopodobnie będzie zmianiał się nadal czesto
  • Jeśli moduł nie zmieniał się czesto w przeszłości to na razie zakładamy że nie będzie się często zmieniał w przyszłości

Oczywiście nasze założenia mogą byc błędne - np zmieni się główny klient czy inwestor i zostaną zaproponowane zupełnie inne funkcje systemu, wjadą nowi architekci legacy kodu i będą narzekać kto napisał to g'wno. Prowadzi to mnie do spostrzeżenia że nie można napisać kodu dobrego pernamentnie, może on być tylko dobry na daną chwilę :(

0
slsy napisał(a):
Michał Kuliński napisał(a):

Wujek Bob ostatnio na Twitterze:

The Single Responsibility Principle (SRP): Gather together those things that change for the same reasons and at the same times. Separate those things that change for different reasons or at different times.

Nie kupuję tego. Przykładowo mogę pomieszać logikę z IO razem ze sobą beż żadnej separacji czy abstrakcji. I taki kod za każdym razem będzie się zmieniał razem.

Ale powinieneś je odseparować.

The Open-Closed Principle (OCP): Separate modules that frequently change from modules that change less frequently with a layer of abstraction.

I dalej zamiast konkretów skupiamy się na wróżbiarstwie. Pisząc kod nie wiem jak często będzie się zmieniał

Nie musisz wiedzieć jak często.

Wystarczy że oddzielisz od siebie moduły, które podejrzewasz że mogą zmieniać się niezależnie.

4
slsy napisał(a):

Nie kupuję tego. Przykładowo mogę pomieszać logikę z IO razem ze sobą beż żadnej separacji czy abstrakcji. I taki kod za każdym razem będzie się zmieniał razem.

Oczywiście, że możesz, przecież nie ma żadnego zakazu. Kod i tak będzie świadczył o autorze kodu, nie o autorze zasad.

I dalej zamiast konkretów skupiamy się na wróżbiarstwie. Pisząc kod nie wiem jak często będzie się zmieniał

Nie wróżbiarstwo tylko oparta na doświadczeniu hipoteza. No, ale oczywiście tu jest problem, bo nie każdy posiada doświadczenie, a nawet jeśli posiada, to nie każdy potrafi wnioskować.

KamilAdam napisał(a):

Oczywiście nasze założenia mogą byc błędne - np zmieni się główny klient czy inwestor i zostaną zaproponowane zupełnie inne funkcje systemu, wjadą nowi architekci legacy kodu i będą narzekać kto napisał to g'wno. Prowadzi to mnie do spostrzeżenia że nie można napisać kodu dobrego pernamentnie, może on być tylko dobry na daną chwilę :(

Może być dobry na dużo dłużej niż dana chwila.

A programowanie to branża wymagająca twórczego myślenia i zdrowego rozsądku, to nie jest miejsce dla ludzi, którzy wymagają dokładnych instrukcji postępowania w każdej możliwej sytuacji, bo takich zwyczajnie nie da się stworzyć.

0
Riddle napisał(a):

Ale powinieneś je odseparować.

somekind napisał(a):

Oczywiście, że możesz, przecież nie ma żadnego zakazu. Kod i tak będzie świadczył o autorze kodu, nie o autorze zasad.

Wspaniały poziom dyskusji. Staram się sprowadzić do absurdu "nową" definicję wujka Boba na temat tego czym jest SRP. To jakie jest moje zdanie jest bez znaczenia.

somekind napisał(a):
slsy napisał(a):

Nie wróżbiarstwo tylko oparta na doświadczeniu hipoteza. No, ale oczywiście tu jest problem, bo nie każdy posiada doświadczenie, a nawet jeśli posiada, to nie każdy potrafi wnioskować.

Moje doświadczenie jest takie, że trzeba znaleźć balans pomiędzy abstrakcją a narzutem kognitywnym budowanym przez tą abstrakcję. Open abstrakcję mogę wrzucić w każde miejsce. Nawet tam, gdzie nie ma to sensu (i często taka błędna abstrakcja sprawia, że kod jest bardziej closed niż open dla konkretnej sytuacji).

somekind napisał(a):
slsy napisał(a):

Nie kupuję tego. Przykładowo mogę pomieszać logikę z IO razem ze sobą beż żadnej separacji czy abstrakcji. I taki kod za każdym razem będzie się zmieniał razem.
A programowanie to branża wymagająca twórczego myślenia i zdrowego rozsądku, to nie jest miejsce dla ludzi, którzy wymagają dokładnych instrukcji postępowania w każdej możliwej sytuacji, bo takich zwyczajnie nie da się stworzyć.

Zgadzam się. Dlatego nie podoba mi się definicja software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification" bo ja to widzę jako ma być rozszerzalnie i ch*j. Ta definicja nie prowokuje do twórczego myślenia i zdrowego rozsądku. Według mnie ta zasada powinna brzmieć bardziej według własnego doświadczenia warto czynić kod otwartym na rozszerzenia a zamkniętym na modyfikację jeśli uznamy, że potencjalne zalety będą większe niż wady

2
slsy napisał(a):

Zgadzam się. Dlatego nie podoba mi się definicja software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification" bo ja to widzę jako ma być rozszerzalnie i ch*j. Ta definicja nie prowokuje do twórczego myślenia i zdrowego rozsądku. Według mnie ta zasada powinna brzmieć bardziej według własnego doświadczenia warto czynić kod otwartym na rozszerzenia a zamkniętym na modyfikację jeśli uznamy, że potencjalne zalety będą większe niż wady

To jakiego słowa byś użył zamiast "should"?

Nikt nie mówi że ma być rozszerzalne i chuj. Nikt też tak nie uważa i nie ma tego na myśli kiedy przywołuję O/C. Myśle że ogromna większość ludzi kiedy mówi o O/C ma na myśli, to co napisałeś: według własnego doświadczenia warto czynić kod otwartym na rozszerzenia a zamkniętym na modyfikację jeśli uznamy, że potencjalne zalety będą większe niż wady.

3
slsy napisał(a):

Wspaniały poziom dyskusji. Staram się sprowadzić do absurdu "nową" definicję wujka Boba na temat tego czym jest SRP. To jakie jest moje zdanie jest bez znaczenia.

Nie chcę się czepiać, ale sprowadzanie do absurdu to nie jest jakieś wybitne dyskutowanie. :)

Moje doświadczenie jest takie, że trzeba znaleźć balans pomiędzy abstrakcją a narzutem kognitywnym budowanym przez tą abstrakcję.

Niewątpliwie, tylko w sumie w czym rzecz? Przecież żadna zasada nie mówi, że abstrakcje są obowiązkowe wszędzie.
Zresztą, należałoby najpierw zdefiniować abstrakcję, a z moich obserwacji wynika, że to może być niezły ubaw.

Open abstrakcję mogę wrzucić w każde miejsce. Nawet tam, gdzie nie ma to sensu (i często taka błędna abstrakcja sprawia, że kod jest bardziej closed niż open dla konkretnej sytuacji).

Jak najbardziej, każdego narzędzia można użyć źle.
Na szczęście bez wydzielania abstrakcji jest jeszcze gorzej.

Zgadzam się. Dlatego nie podoba mi się definicja software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification" bo ja to widzę jako ma być rozszerzalnie i ch*j. Ta definicja nie prowokuje do twórczego myślenia i zdrowego rozsądku.

Ale to naprawdę nie jest branża dla ludzi, którzy potrzebują specjalnego zaproszenia do myślenia.

Według mnie ta zasada powinna brzmieć bardziej według własnego doświadczenia warto czynić kod otwartym na rozszerzenia a zamkniętym na modyfikację jeśli uznamy, że potencjalne zalety będą większe niż wady

Te części, które dodałeś są w domyśle, i dotyczą wszystkiego. Jeśli ktoś bierze jakąś zasadę, ideę, technologię, framework, bibliotekę, wzorzec czy cokolwiek, i używa bezmyślnie, to straci czas i zapewne stworzy coś, czego nie będzie dało się rozwijać.

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