Ale wiecie, że gdyby były ściśle określone zasady dla każdej z tych literek, to już dawno mielibyśmy w IDE funkcję 'make it SOLID'? Komputery są znacznie lepsze w przestrzeganiu zasad, w czytaniu kodu również...
SOLID w praktyce
- Rejestracja: dni
- Ostatnio: dni
- Lokalizacja: U krasnoludów - pod górą
- Postów: 4712
piotrpo napisał(a):
Ale wiecie, że gdyby były ściśle określone zasady dla każdej z tych literek, to już dawno mielibyśmy w IDE funkcję 'make it SOLID'? Komputery są znacznie lepsze w przestrzeganiu zasad, w czytaniu kodu również...
Nie. Ale byłaby funkcja "ckeck SOLID" dająca ostrzeżenia, że coś jest złamane.
- Rejestracja: dni
- Ostatnio: dni
- Postów: 3303
@jarekr000000: Co "nie"? Takie I dla przykładu - analiza przypadków użycia jakiejś klasy, wychodzi, że 30% miejsc używa metod ABC, 30% metod DEF, w pozostałych przypadkach są użyte miksy - trach, masz prowadzone 2 interface'y + jakiś agregujący.
- Rejestracja: dni
- Ostatnio: dni
- Postów: 1039
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.
I właśnie w ten sposób łamiesz SRP. Wytłumaczenie Bobka niejasne niepełne, ale chyba większość osób umie ocenić sytuację.
Poza tym, ja rozumiem inaczej powód do zmiany. Jednym powodem jest obsługa innego I/O, nowego formatu plików, etc, innym zmiana logiki związanej z domeną, middleware, etc. To są dwa różne powody do zmiany. Dla mnie to jest powiązane z "Volatility-Based Decomposition".
- Rejestracja: dni
- Ostatnio: dni
- Postów: 2553
Mi się wydawało że SRP jest właśnie najprostsze i imo jedno z ważniejszych. Ja zawsze miałem problem z tym trzecim, szczególnie do zapamietania na rozmowę rekrutacyjna, bo zrozumieć to zrozumiem po przeczytaniu.
- Rejestracja: dni
- Ostatnio: dni
- Postów: 1596
Czitels napisał(a):
Mi się wydawało że SRP jest właśnie najprostsze i imo jedno z ważniejszych. Ja zawsze miałem problem z tym trzecim, szczególnie do zapamietania na rozmowę rekrutacyjna, bo zrozumieć to zrozumiem po przeczytaniu.
Ja też. Tu mamy fajny przykład
https://dotnettutorials.net/lesson/liskov-substitution-principle/#:~:text=The%20Liskov%20Substitution%20Principle%20in%20C%23%20states%20that%20even%20the,follows%20the%20Liskov%20Substitution%20Principle.
- Rejestracja: dni
- Ostatnio: dni
- Lokalizacja: U krasnoludów - pod górą
- Postów: 4712
LSP jest dość proste - tutaj wyjaśnienie:
https://www.cs.cmu.edu/~wing/publications/LiskovWing94.pdf
- Rejestracja: dni
- Ostatnio: dni
- Postów: 2384
W praktyce LSP tłumaczę sobie tak:
Jeśli Car ma właściwość "Może przewieźć ładunek o wadze 2 ton"
- Klasa Tir - może przewieźć ładunek o wadze 10 ton (wzmocnienie kontraktu)
- Klasa Resorak - może przewieźć ładunek o wadze 5g (osłabienie kontraktu)
Obydwie dziedziczą z Car, to jednak Resorak łamie LSP, bo jak do Car załadujemy 1.5 tony, to Resorak marnie skończy.
- Rejestracja: dni
- Ostatnio: dni
- Lokalizacja: Warszawa
- Postów: 1
Nawet jakby dało się precyzyjnie zdefiniować co te literki znaczą, to nie jestem przekonany że byłoby to bardzo wartościowe. W praktyce stosowanie tych reguł, tak jak wszystkich dobrych praktyk, ma koszty, i tak naprawdę tylko z boleśnie zdobytego doświadczenia można wywnioskować czy w konkretnej sytuacji warto te koszty ponosić. A dyskusje o tym czy jakieś konkretne rozwiązanie trzyma się SRP czy nie imo zahaczają o bikeshedding i odwracają uwagę od sedna problemu.
- Rejestracja: dni
- Ostatnio: dni
- Postów: 44
Zasady OCP, SRP oraz DIP są bardzo dokładnie opisane w książce 'Clean code'. NIE DA SIĘ streścić 400-stronicową książkę w kilku zdaniach. Jeśli dla kogoś te reguły są zbyt ogólne, to zachęcam do przeczytania książki, która zawiera wiele praktycznych porad związanych z programowaniem. :)
- Rejestracja: dni
- Ostatnio: dni
- Postów: 13
LukeJL napisał(a):
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 wtyczkachew. 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.
W żadnym wypadku Single Responsibility Principle nie równa się "pisz małe klasy(funkcje/moduły) zamiast dużych". Takie rozumowanie przy projektowaniu architektury kodu nie ma najmniejszego sensu i nie chroni nas przed spaghetti code.
W tej zasadzie z grubsza chodzi o to, aby struktura miała jeden cel istnienia w kodzie. Przykładowo jezeli w programie mamy strukture uzywana do (de)serializacji obiektu z bazy danych (np. mysql) to nie powinienem używać tej samej struktury do odpowiadania przez API (np. http), ani jako struktury w warstwach w której znajduje się logika/domena aplikacji. Absolutnie nie ma to nic wspólnego z wielkością klasy, a chodzi o to aby poszczególne warstwy kodu były od siebie jak najbardziej odseparowane. SRP może wydawać się z początku łamaniem zasady DRY, bo często odpowiedz z API będzie posiadać podobne pola co obiekt w bazie danych, ale z czasem gdy serwis nad którym pracujemy się rozrasta przekonamy się, że nie zawsze tak jest i wygodniej stosować osobne struktury. Ta zasada jest bardzo ważna i dzięki niej jesteśmy w stanie uniknąć sytuacji, w której kod 3 miesiące od powstania będzie tak splątany, że developerzy będą od niego uciekać.
- Rejestracja: dni
- Ostatnio: dni
- Postów: 2
Myślę, że problem w przyswojeniu i stosowaniu SOLID polega na tym, że za tymi zasadami, kryją się pewne ukryte koszty o których moim skromnym zdaniem za mało się mówi.
S - pojedyncza odpowiedzialność. To tak jakby dążyć do tego, by nazwy w kodzie nigdy nie traciły znaczenia. Cel warty podjęcia, bo kod, który traci znaczenie w nazwach brzmi jak bełkot. Nie mniej utrzymanie nazw w ryzach bywa kłopotliwe, gdy model się zmienia, gdy do głównych procedur dochodzą nowe dodatkowe zadania. Np wcześniej miałeś akcję register, a teraz w ujęciu tej operacji program buduje profil, wysyła maila, i opcjonalnie zbiera jakąś statystykę. Jaka wówczas nazwa może precyzyjnie określić to za co odpowiada operacja?
O - chcąc otwarcia na zmiany musisz wstępnie określić niezmienny interfejs, a to jednak jest trudne na płaszczyźnie biznesowej. Niestety biznes zmienia wymagania i kolejna zmiana w założeniach może skutkować wywróceniem interfejsów. Wg mnie interfejsy innym może wyznaczać ten kogo stać zwyczajnie na to np. facebook, google czy jakieś inne wielkaśne firmy.
L - co z tego, że obiekty mogą mieć ten sam interfejs skoro na warstwie dostępu dwie implementacje zgodne z interfejsem mogą dostaczyć dwa rozbieżne wyniki (np. przez wzgląd na opóźnienia / synchronizację itp) Podmiana, w teście jest OK, ale w praktyce podamiana, która oddziaływuje na spójność IMO jest wątpliwa.
I - każdy interfejs opiera się na wyprowadzeniu jakiegoś uproszczenia, a uproszczenia bywają pomocne, pozwalają pewne problemy ująć precyzyjnie. Niestety tu znów biznes zmienia wymagania i kolejna zmiana w założeniach może skutkować wywróceniem interfejsów. Interfejsy przetrzymają dłużej jeśli biznes ich nie wystawia, lecz implementuje.
D - dotyczy współdzielenia obiektu, który powoduje efekty (proszę wróć na początek tego punktu i przeczytaj to jeszcze raz). Nie wiem jakim cudem to postrzegane jest jako element dobrych zasad, ale by zilustrować problem zastanów się co wiesz o tym wstrzykiwanym obiekcie, pomyśl co jeszcze od niego jest zależne. Żeby widzeć wpływ zmian trzeba widzieć projekt, to jest pewien rodzaj podatku od modyfikowalności.
Ogólnie SOLID jest super do wytwarzania ram, ale źle sprawdza się gdy podejmujesz się biznesowego kodu.
- Rejestracja: dni
- Ostatnio: dni
- Postów: 5227
L - ... na warstwie dostępu
warstwie? ktoś tu chyba architektem jest :P
która oddziaływuje na spójność IMO jest wątpliwa.
spójność czego? typów/zachowania/danych/...
dwie implementacje zgodne z interfejsem mogą dostaczyć dwa rozbieżne wyniki
ok, czyli to łamie Liskovą czy nie?
public abstract class Slownik
{
public abstract string PrzetlumaczOrEmpty(string word);
}
public class SlownikAngielskiNaPolski : Slownik
{
public override string PrzetlumaczOrEmpty(string word)
{
if (word == "Hello")
return "Cześć";
return string.Empty;
}
}
public class SlownikAngielskiNaNiemiecki : Slownik
{
public override string PrzetlumaczOrEmpty(string word)
{
if (word == "Hello")
return "Hallo";
return string.Empty;
}
}
- Rejestracja: dni
- Ostatnio: dni
- Lokalizacja: Wrocław
jantomx napisał(a):
W tej zasadzie z grubsza chodzi o to, aby struktura miała jeden cel istnienia w kodzie.
Słuszna uwaga, ale SRP dotyczy nie tylko struktur danych.
za_malo_postow napisał(a):
Np wcześniej miałeś akcję register, a teraz w ujęciu tej operacji program buduje profil, wysyła maila, i opcjonalnie zbiera jakąś statystykę. Jaka wówczas nazwa może precyzyjnie określić to za co odpowiada operacja?
To, ze nie wiadomo jak nazwać funkcję, to jest właśnie jeden z objawów łamania SRP. Trzeba dzielić kod.
O - chcąc otwarcia na zmiany musisz wstępnie określić niezmienny interfejs, a to jednak jest trudne na płaszczyźnie biznesowej. Niestety biznes zmienia wymagania i kolejna zmiana w założeniach może skutkować wywróceniem interfejsów. Wg mnie interfejsy innym może wyznaczać ten kogo stać zwyczajnie na to np. facebook, google czy jakieś inne wielkaśne firmy.
Interfejsów nie określa się na płaszczyźnie biznesowej, nie służą one do tego, aby wyznaczać je innym, a OCP w ogóle nie mówi nic o interfejsach.
L - co z tego, że obiekty mogą mieć ten sam interfejs skoro na warstwie dostępu dwie implementacje zgodne z interfejsem mogą dostaczyć dwa rozbieżne wyniki (np. przez wzgląd na opóźnienia / synchronizację itp)
Nawet powinny, jeśli dwie implementacje dają to samo, to znaczy, że jedna z nich jest zbędna.
I - każdy interfejs opiera się na wyprowadzeniu jakiegoś uproszczenia, a uproszczenia bywają pomocne, pozwalają pewne problemy ująć precyzyjnie. Niestety tu znów biznes zmienia wymagania i kolejna zmiana w założeniach może skutkować wywróceniem interfejsów. Interfejsy przetrzymają dłużej jeśli biznes ich nie wystawia, lecz implementuje.
Biznes nie implementuje niczego. A ISP pomaga właśnie w tym, żeby zmiany w interfejsach były możliwie bezbolesne.
D - dotyczy współdzielenia obiektu, który powoduje efekty (proszę wróć na początek tego punktu i przeczytaj to jeszcze raz). Nie wiem jakim cudem to postrzegane jest jako element dobrych zasad
Cudem takim, że nie chcemy pisać kodu spaghetti, uzależniając logikę biznesowa od szczegółów implementacji infrastruktury.
ale by zilustrować problem zastanów się co wiesz o tym wstrzykiwanym obiekcie, pomyśl co jeszcze od niego jest zależne.
Nie ma to znaczenia.
Ogólnie SOLID jest super do wytwarzania ram, ale źle sprawdza się gdy podejmujesz się biznesowego kodu.
Skąd Ty to wszystko skopiowałeś/czym to przetłumaczyłeś, czemu używasz 3 osoby liczby pojedynczej w stronie czynnej, i w ogóle o co chodzi?
- Rejestracja: dni
- Ostatnio: dni
- Postów: 2
To, ze nie wiadomo jak nazwać funkcję, to jest właśnie jeden z objawów łamania SRP. Trzeba dzielić kod.
To co wydzielisz zdołasz nazwiesz, ale to co korzysta z tych wydzielonych nazw niekoniecznie (chyba, że apka zacznie pozbywać się funkcjonalności jakie daje użytkownikowi)
Interfejsów nie określa się na płaszczyźnie biznesowej, nie służą one do tego, aby wyznaczać je innym, a OCP w ogóle nie mówi nic o interfejsach.
Jak masz 2-3 rozwiązania pewnego problemu i nie chcesz uzależniać się od jednej konkretnej implementacji to co wtedy robisz? Nie masz wtedy interfejsu? Nie jest wtedy on biznesowy?
Nawet powinny, jeśli dwie implementacje dają to samo, to znaczy, że jedna z nich jest zbędna.
Ciekawe, jak masz 2 providery płatności. To wolisz, aby oba dawały Ci inny rezultat? :-) Poza tym taki elementarny przykład, zobacz istnieje ArrayList i LinkedList - to czemu jednej nie wywalono skoro obie dają ten sam wynik?
Biznes nie implementuje niczego. A ISP pomaga właśnie w tym, żeby zmiany w interfejsach były możliwie bezbolesne.
Nie pisałem, że biznes implementuje, lecz, że biznes wpływa na wymagania, a to zasadnicza różnica. Jak biznes zmieni wymagania to żadna zasada na to nie jest odporna. Inaczej nie musiałbyś nawet rozmawiać z biznesem, łapiesz? Tu twoim orężem jedynie może być rozsądek i wiedza domenowa, czyli klepacz z miejsca przegrywa.
Nie ma to znaczenia.
Wołasz metodę na współdzielonym obiekcie, który powoduje efekty - sorry misiu, ale to ma znaczenie. Jak zakładasz blokadę to warto się upewnić, byś nie zrobił deadlocka. Tego nie wyczytasz z klasy jaką implementujesz.
Skąd Ty to wszystko skopiowałeś/czym to przetłumaczyłeś
Tym się różnię, że nie kopiuję.
- Rejestracja: dni
- Ostatnio: dni
- Postów: 5227
Poza tym taki elementarny przykład, zobacz istnieje ArrayList i LinkedList - to czemu jednej nie wywalono skoro obie dają ten sam wynik?
to do kryteriów poza danymi dorzuć sobie jeszcze perf i nagle masz różnice
- Rejestracja: dni
- Ostatnio: dni
- Lokalizacja: Wrocław
za_malo_postow napisał(a):
To co wydzielisz zdołasz nazwiesz, ale to co korzysta z tych wydzielonych nazw niekoniecznie (chyba, że apka zacznie pozbywać się funkcjonalności jakie daje użytkownikowi)
To, że podzielę przetwarzanie danych na ileś funkcji w moim kodzie nie ma wpływu na użytkownika.
Jak masz 2-3 rozwiązania pewnego problemu i nie chcesz uzależniać się od jednej konkretnej implementacji to co wtedy robisz? Nie masz wtedy interfejsu? Nie jest wtedy on biznesowy?
Może mam interfejs, może nie mam - zależy od tego jak ułożę kod, i jaki to język jest. Ale na pewno z biznesem nie ma nic wspólnego.
Ciekawe, jak masz 2 providery płatności. To wolisz, aby oba dawały Ci inny rezultat? :-) Poza tym taki elementarny przykład, zobacz istnieje ArrayList i LinkedList - to czemu jednej nie wywalono skoro obie dają ten sam wynik?
Żaden z tych przykładów nie daje tego samego.
Jak biznes zmieni wymagania to żadna zasada na to nie jest odporna.
Zmiana jest nieunikniona, a celem wszystkich tych zasad jest właśnie sprawienie, aby kod był podatny na zmiany.
Wołasz metodę na współdzielonym obiekcie, który powoduje efekty - sorry misiu, ale to ma znaczenie. Jak zakładasz blokadę to warto się upewnić, byś nie zrobił deadlocka. Tego nie wyczytasz z klasy jaką implementujesz.
Szczegóły implementacji takie jak locki nie powinny wyciekać do wyższych warstw. Jeśli musisz zastanawiać się nad tym, jak działa warstwa niższa, to nie masz poprawnych abstrakcji.