Czy są jakieś istotne wady uzależnienia od abstrakcji?

9

Czytałem tę wiki na temat Stable Abstractions Principle (SAP) .

SAP stwierdza, że ​​im bardziej stabilny pakiet, tym bardziej powinien on być abstrakcyjny. Oznacza to, że jeśli pakiet jest mniej stabilny (bardziej prawdopodobne, że się zmieni), powinien być bardziej konkretny. Nie do końca rozumiem, dlaczego tak powinno być. Z pewnością we wszystkich przypadkach, niezależnie od stabilności, powinniśmy polegać na abstrakcjach i ukrywać konkretne wdrożenie?

SteveCallender
źródło
Spróbuj użyć komponentu, z którym czujesz się komfortowo, nie wykorzystując zawartych w nim abstrakcji, ale wykonując wszystko ze szczegółami na poziomie niższym niż zwykle. To da całkiem niezłe wrażenie zalet i wad abstrakcji.
Kilian Foth,
2
Czy czytałeś powiązany artykuł i / lub książkę, na której opiera się artykuł?
Jörg W Mittag
1
+1 Dobre pytanie, zwłaszcza że uważam, że związek między stabilnością a abstrakcją nie jest natychmiast intuicyjny. Strona 11 tego artykułu pomaga, jego przykład abstrakcyjnego przypadku ma sens, ale być może ktoś może napisać jaśniejszy przykład konkretnego przypadku. Żądanie startu
Mike,
Z jaką dziedziną problemów masz do czynienia z tymi abstrakcjami? Jak zauważono w C2: „W modelowaniu domen świata rzeczywistego - świata klientów, pracowników, faktur, zestawień materiałowych, produktów, kodów SKU, płatności itp. - stabilne abstrakty mogą być trudne do znalezienia. Domeny obliczeniowe - świat stosów, kolejek, funkcji, drzew, procesów, wątków, widżetów graficznych, raportów, formularzy itp. - są znacznie bardziej stabilne ”. oraz „W niektórych domenach trudno uzyskać stabilne abstrakcje”. Nie wiedząc, jaki problem próbujesz rozwiązać za pomocą SAP, trudno jest udzielić ci dobrej odpowiedzi.
@ JörgWMittag i Mike - Tak, przeczytałem artykuł. Po prostu czuję, że nie ma wyjaśnienia, dlaczego „niestabilne pakiety powinny być konkretne”. Na stronie 13 tego artykułu pokazuje wykres, ale tak naprawdę nie wyjaśnia zbyt szczegółowo, dlaczego (1,1) na wykresie należy unikać? Czy idea, że ​​zasadniczo niestabilna oznacza mniej zależności aferentnych i nie ma potrzeby używania abstrakcji? Jeśli tak ... to czy nie jest dobrą praktyką używanie abstrakcji, na wypadek gdyby stabilność zmieniła się wraz ze zmianami wymagań.
SteveCallender

Odpowiedzi:

7

Myśl o swoich pakietów jak API, aby wziąć przykład z papieru, wziąć do definicji Readerz string Reader.Read()i Writerze void Writer.Write(string)jako abstrakcyjnej API.

Następnie możesz utworzyć klasę Copyza pomocą metody Copier.Copy(Reader, Writer)i implementacji, Writer.Write(Reader.Read())a może nawet kontroli poprawności.

Teraz konkretne implementacje, na przykład FileReader, FileWriter, KeyboardReaderi DownloadThingsFromTheInternetReader.

Co jeśli chcesz zmienić swoją implementację FileReader? Nie ma problemu, wystarczy zmienić klasę i ponownie skompilować.

Co jeśli chcesz zmienić definicję swojej abstrakcji Reader? Ups, nie można po prostu zmienić, ale trzeba będzie także zmienić Copier, FileReader, KeyboardReaderi DownloadThingsFromTheInternetReader.

Jest to uzasadnienie zasady stabilnej abstrakcji: Uczyń konkretyzację mniej stabilną niż abstrakcje.

Pozostałość
źródło
1
Zgadzam się ze wszystkim, co mówisz, ale wierzę, że autorzy definiują stabilność i twoją różnią się. Traktujesz stabilność jako potrzebę zmiany, autor mówi: „stabilność nie jest miarą prawdopodobieństwa zmiany modułu, a raczej miarą trudności w zmianie modułu”. Więc moje pytanie brzmi bardziej: dlaczego korzystne jest, aby pakiety, które można łatwo zmienić, były bardziej konkretne niż abstrakcyjne?
SteveCallender,
1
@ SteveCallender To subtelna różnica: autorska definicja „stabilności” jest tym, co większość ludzi nazywa „potrzebą stabilności”. Im więcej modułów zależy od modułu, tym bardziej „stabilny” musi być moduł.
Residuum
6

Z powodu YAGNI .

Jeśli obecnie masz tylko jedną implementację jednej rzeczy , po co zawracać sobie głowę dodatkową i bezużyteczną warstwą? Doprowadzi to tylko do niepotrzebnej złożoności. Co gorsza, czasami dostarczasz abstrakcyjne myślenie do dnia, w którym nadejdzie druga implementacja ... i ten dzień nigdy się nie wydarzy. Co za strata pracy!

Myślę też, że prawdziwym pytaniem nie jest: „Czy muszę polegać na abstrakcjach?” ale raczej „Czy potrzebuję modułowości?”. Modułowość nie zawsze jest potrzebna, patrz poniżej.

W firmie, w której pracuję, niektóre oprogramowanie, które rozwijam, jest ściśle powiązane z jakimś sprzętem, z którym musi się komunikować. Urządzenia te zostały opracowane, aby spełniać bardzo specyficzne cele i są praktycznie modułowe. :-) Po pierwsze wyprodukowane urządzenie wychodzi z fabryki i jest zainstalowany gdzieś, zarówno jego oprogramowania i sprzętu może nigdy się nie zmieniają, kiedykolwiek .

Mogę więc mieć pewność, że niektóre części oprogramowania nigdy się nie rozwiną. Te części nie muszą zależeć od abstrakcji, ponieważ istnieje tylko jedna implementacja i ta nigdy się nie zmieni. Deklarowanie abstrakcji w tych częściach kodu tylko dezorientuje wszystkich i zajmuje więcej czasu (bez generowania żadnej wartości).

Cętkowany
źródło
1
Zwykle zgadzam się z YAGNI, ale zastanawiam się nad twoim przykładem. Czy nigdy nie powtarzasz żadnego kodu na różnych urządzeniach? Trudno mi uwierzyć, że na urządzeniach tej samej firmy nie ma wspólnego kodu. A jak podoba się klientom, gdy nie naprawia się błędów w oprogramowaniu? Czy chcesz powiedzieć, że nigdy nie są błędy, kiedykolwiek ? Jeśli masz ten sam kod, który jest wadliwy w 4 różnych implementacjach, musisz naprawić błąd 4 razy, jeśli nie ma go we wspólnym module.
Fuhrmanator,
1
Wspólny kod @Fuhrmanator różni się od abstrakcji. Wspólny kod może po prostu oznaczać metodę pomocniczą lub bibliotekę - abstrakcje nie są potrzebne.
Eilon,
@Fuhrmanator Oczywiście mamy wspólny kod w bibliotekach, ale jak powiedział Eilon, nie wszystko zależy od abstrakcji (niektóre części jednak). Nigdy nie mówiłem, że nigdy nie ma błędów, powiedziałem, że nie można ich załatać (z powodów, które są poza zakresem pytania PO).
Zauważył
@Eilon mój komentarz na temat modułowości nie zawsze jest potrzebny (nie abstrakcje).
Fuhrmanator,
@Spotted Nie ma problemu z niemożnością łatania. To tylko dość konkretny przykład i nie jest typowy dla większości programów.
Fuhrmanator,
6

Myślę, że być może myli cię słowo stabilne wybrane przez Roberta Martina. Oto, jak myślę, zaczyna się zamieszanie:

Oznacza to, że jeśli pakiet jest mniej stabilny (bardziej prawdopodobne, że się zmieni), powinien być bardziej konkretny.

Jeśli przeczytasz oryginalny artykuł , zobaczysz (moje wyróżnienie):

Klasyczna definicja słowa „stabilność” brzmi: „Niełatwo go przenieść”. Jest to definicja, której będziemy używać w tym artykule. Oznacza to, że stabilność nie jest miarą prawdopodobieństwa zmiany modułu; jest raczej miarą trudności w zmianie modułu .

Oczywiście moduły, które są trudniejsze do zmiany, będą mniej niestabilne. Im trudniej jest zmienić moduł, tzn. Im jest bardziej stabilny, tym mniej będzie niestabilny.

Zawsze zmagałem się z wyborem autora słowa „ stabilny” , ponieważ (podobnie jak ty) myślę o aspekcie „prawdopodobieństwa” stabilności, tzn. Raczej nie zmieni się . Trudność oznacza, że ​​zmiana tego modułu spowoduje uszkodzenie wielu innych modułów i naprawienie kodu będzie wymagało dużo pracy.

Martin używa także słów niezależnych i odpowiedzialnych , które mają dla mnie znacznie większe znaczenie. W swoim seminarium szkoleniowym użył metafory o rodzicach dzieci dorastających i o tym, jak powinni być „odpowiedzialni”, ponieważ ich dzieci są od nich zależne. Rozwód, bezrobocie, uwięzienia itp. Są świetnymi przykładami negatywnego wpływu, jaki zmiana rodziców będzie miała na dzieci. Dlatego rodzice powinni być „stabilni” z korzyścią dla swoich dzieci. Nawiasem mówiąc, ta metafora dzieci / rodziców niekoniecznie wiąże się z dziedziczeniem w OOP!

Tak więc, kierując się duchem „odpowiedzialnego”, wymyśliłem alternatywne znaczenie trudnych do zmiany (lub nie powinno się zmieniać ):

  • Obowiązkowe - co oznacza, że ​​inne klasy zależą od tej klasy, więc nie powinna się zmieniać.
  • Beholden - ibid.
  • Ograniczony - obowiązki tej klasy ograniczają jej możliwości zmiany.

Więc podłączenie tych definicji do instrukcji

im bardziej stabilny pakiet, tym bardziej powinien być abstrakcyjny

  • im bardziej obowiązkowy pakiet, tym bardziej powinien on być abstrakcyjny
  • tym bardziej zobowiązany pakiet bardziej abstrakcyjne powinny być
  • im bardziej ograniczony pakiet, tym bardziej powinien on być abstrakcyjny

Przytoczmy zasadę stabilnych abstrakcji (SAP), podkreślając mylące słowa stabilne / niestabilne:

Pakiety, które są maksymalnie stabilne, powinny być maksymalnie abstrakcyjne. Niestabilne opakowania powinny być konkretne. Abstrakcyjność pakietu powinna być proporcjonalna do jego stabilności .

Wyjaśniając to bez tych mylących słów:

Pakiety, które są maksymalnie obserwowane w innych częściach systemu, powinny być maksymalnie abstrakcyjne. Paczki, które można zmieniać bez trudu, powinny być konkretne. Abstrakcyjność pakietu powinna być proporcjonalna do stopnia trudności modyfikacji .

TL; DR

Tytuł twojego pytania brzmi:

Czy są jakieś istotne wady uzależnienia od abstrakcji?

Myślę, że jeśli poprawnie utworzysz abstrakcje (np. Istnieją, ponieważ zależy od nich wiele kodu), nie będzie żadnych znaczących wad.

Fuhrmanator
źródło
0

Oznacza to, że jeśli pakiet jest mniej stabilny (bardziej prawdopodobne, że się zmieni), powinien być bardziej konkretny. Nie do końca rozumiem, dlaczego tak powinno być.

Abstrakcje to rzeczy, które trudno zmienić w oprogramowaniu, ponieważ wszystko zależy od nich. Jeśli twój pakiet będzie się często zmieniał i zawiera abstrakcje, ludzie, którzy na nim polegają, będą zmuszeni przepisać dużą część swojego kodu, gdy coś zmienisz. Ale jeśli twój niestabilny pakiet zawiera konkretne implementacje, po zmianach trzeba będzie napisać znacznie mniej kodu.

Jeśli więc twoja paczka często się zmienia, powinna lepiej zapewniać beton, a nie abstrakcje. W przeciwnym razie ... kto do diabła go wykorzysta? ;)

Vladislav Rastrusny
źródło
0

Pamiętaj o metodzie stabilności Martina i o tym, co rozumie przez „stabilność”:

Instability = Ce / (Ca+Ce)

Lub:

Instability = Outgoing / (Incoming+Outgoing)

Oznacza to, że pakiet jest uważany za całkowicie niestabilny, jeśli wszystkie jego zależności są wychodzące: używa innych rzeczy, ale nic go nie używa. W takim przypadku sensowne jest, aby ta rzecz była konkretna. Będzie to również najłatwiejszy rodzaj kodu do zmiany, ponieważ nic innego go nie używa, a zatem nic innego nie może się zepsuć, jeśli ten kod zostanie zmodyfikowany.

W międzyczasie, gdy masz odwrotny scenariusz pełnej „stabilności” z pakietem używanym przez jedną lub więcej rzeczy, ale nie używa on niczego samodzielnie, jak pakiet centralny używany przez oprogramowanie, wtedy Martin mówi, że to powinno być abstrakcyjny. Potwierdza to również część DIP SOLI (D), Zasada Inwersji Zależności, która zasadniczo stwierdza, że ​​zależności powinny jednolicie płynąć w kierunku abstrakcji zarówno dla kodu niskiego, jak i wysokiego poziomu.

Oznacza to, że zależności powinny równomiernie płynąć w kierunku „stabilności”, a dokładniej, zależności powinny płynąć w kierunku pakietów z większą liczbą zależności przychodzących niż zależności wychodzące, a ponadto zależności powinny płynąć w kierunku abstrakcji. Istotą tego uzasadnienia jest to, że abstrakcje zapewniają oddech, aby zastąpić jeden podtyp innym, oferując taki stopień elastyczności, aby konkretne części wdrażające interfejs mogły się zmieniać bez przerywania nadchodzących zależności od tego abstrakcyjnego interfejsu.

Czy są jakieś istotne wady uzależnienia od abstrakcji?

Cóż, właściwie nie zgadzam się z Martinem tutaj przynajmniej dla mojej domeny i tutaj muszę wprowadzić nową definicję „stabilności” jak w „braku powodów do zmiany”. W takim przypadku powiedziałbym, że zależności powinny płynąć w kierunku stabilności, ale abstrakcyjne interfejsy nie pomagają, jeśli abstrakcyjne interfejsy są niestabilne (z mojej definicji „niestabilny”, ponieważ podatny na wielokrotne zmiany, a nie Martina). Jeśli programiści nie mogą uzyskać poprawnych abstrakcji, a klienci wielokrotnie zmieniają zdanie w taki sposób, że abstrakcyjne próby modelowania oprogramowania są niekompletne lub nieskuteczne, nie czerpiemy już korzyści ze zwiększonej elastyczności abstrakcyjnych interfejsów w celu ochrony systemu przed kaskadowymi zmianami przerywającymi zależności . W moim osobistym przypadku znalazłem silniki ECS, takie jak te w grach AAA,najbardziej konkretny : w kierunku surowych danych, ale takie dane są wysoce stabilne (jak w „mało prawdopodobne, by kiedykolwiek trzeba je zmienić”). Często zdarza mi się, że prawdopodobieństwo czegoś, co wymaga przyszłych zmian, jest bardziej użyteczną miarą niż stosunek efektorowego do łącznego sprzężenia przy podejmowaniu decyzji dotyczących SE.

Chciałbym więc trochę zmienić DIP i powiedzieć: „zależności powinny przepływać w kierunku komponentów, które mają najniższe prawdopodobieństwo wymagania dalszych zmian”, niezależnie od tego, czy są to abstrakcyjne interfejsy czy surowe dane. Liczy się dla mnie tylko prawdopodobieństwo, że mogą wymagać bezpośrednich zmian przełomowych. Abstrakcje są użyteczne w tym kontekście stabilności tylko wtedy, gdy coś, będąc abstrakcją, zmniejsza to prawdopodobieństwo.

W wielu kontekstach może tak być w przypadku porządnych inżynierów i klientów, którzy z góry przewidują potrzeby oprogramowania i projektują stabilne (jak w niezmienionych) abstrakcjach, podczas gdy abstrakcje te zapewniają im całą swobodę, której potrzebują, aby zamienić konkretne wdrożenia. Ale w niektórych domenach abstrakcje mogą być niestabilne i podatne na nieodpowiednie, podczas gdy dane wymagane od silnika mogą być znacznie łatwiejsze do przewidzenia i stabilne z góry. Tak więc w takich przypadkach może być bardziej korzystne z punktu widzenia łatwości konserwacji (łatwość zmiany i rozszerzenia systemu), aby zależności płynęły w kierunku danych, a nie abstrakcji. W ECS najbardziej niestabilnymi częściami (jak w częściach najczęściej wymienianych) są zazwyczaj funkcje rezydujące w systemach (PhysicsSystem, np.), podczas gdy najbardziej stabilne części (jak najmniej prawdopodobne, że zostaną zmienione) to komponenty, które składają się z surowych danych ( MotionComponentnp.), z których korzystają wszystkie systemy.


źródło