Jaka jest wartość ukrywania szczegółów za pomocą abstrakcji? Czy nie ma wartości w przejrzystości?


30

tło

Nie jestem wielkim fanem abstrakcji. Przyznaję, że można skorzystać z możliwości dostosowania, przenośności i ponownego wykorzystywania interfejsów itp. Są tam realne korzyści i nie chcę tego kwestionować, więc zignorujmy to.

Jest jeszcze jedna główna „korzyść” abstrakcji, która polega na ukryciu logiki implementacji i szczegółów przed użytkownikami tej abstrakcji. Argument polega na tym, że nie musisz znać szczegółów, i że w tym momencie należy skoncentrować się na własnej logice. Ma to sens w teorii.

Jednak za każdym razem, gdy utrzymuję aplikacje dla dużych przedsiębiorstw, zawsze muszę znać więcej szczegółów. Staje się to ogromnym problemem, coraz głębiej i głębiej zagłębiając się w abstrakcję na każdym kroku, aby dowiedzieć się, co dokładnie robi; tj. konieczność wykonania „otwartej deklaracji” około 12 razy przed znalezieniem zastosowanej procedury składowanej.

Ta mentalność „ukrywania szczegółów” wydaje się po prostu przeszkadzać. Zawsze pragnę bardziej przejrzystych interfejsów i mniej abstrakcji. Potrafię czytać kod źródłowy wysokiego poziomu i wiedzieć, co on robi, ale nigdy nie będę wiedział, jak to robi, kiedy to, jak to robi, to naprawdę muszę wiedzieć.

Co tu się dzieje? Czy każdy system, nad którym kiedykolwiek pracowałem, został źle zaprojektowany (przynajmniej z tej perspektywy)?

Moja filozofia

Kiedy tworzę oprogramowanie, czuję, że staram się podążać za filozofią, która moim zdaniem jest ściśle związana z filozofią ArchLinux :

Arch Linux zachowuje nieodłączną złożoność systemu GNU / Linux, jednocześnie zapewniając dobrą organizację i przejrzystość. Programiści i użytkownicy Arch Linux uważają, że próba ukrycia złożoności systemu prowadzi do jeszcze bardziej złożonego systemu i dlatego należy go unikać.

Dlatego nigdy nie próbuję ukrywać złożoności mojego oprogramowania za warstwami abstrakcji. Staram się nadużywać abstrakcji, a nie stać się jej niewolnikiem.

Pytanie w sercu

  1. Czy ukrywanie szczegółów ma realną wartość?
  2. Czy nie poświęcamy przejrzystości?
  3. Czy ta przejrzystość nie jest cenna?

7
Abstrakcja może być nadużywana w postaci złego projektu. Ale to nie znaczy, że abstrakcja w zasadzie nie jest cenna.
Bernard,

4
Wydaje mi się, że jest tam dobre pytanie, ale brzmi to jak zakaz przeciwko abstrakcji. Czy możesz odreagować to i jeszcze bardziej podkreślić swoje aktualne pytanie?
PersonalNexus

4
Czy na pewno używasz właściwej definicji „ukrywania szczegółów”? W tym kontekście chodzi o zmniejszenie sprzężenia , a nie o uniemożliwienie uczenia się wewnętrznego działania czegoś.
Andres F.,

24
O ile nie lubisz programować za pomocą woltomierza i oscyloskopu przy swoim biurku, programujesz wyłącznie na podstawie abstrakcji na abstrakcji na abstrakcji. Czy ukrywanie szczegółów, którymi tak naprawdę manipulujesz, nie jest bitami, ale napięciami? Czy czyniąc to, poświęcasz przejrzystość? Czy ta przejrzystość jest cenna?
Eric Lippert,

8
Myślę, że to, z czym masz problemy, to nie abstrakcja, to puste warstwy pośrednictwa, które tak naprawdę niczego nie abstrakują. Tak, często można je znaleźć w systemach dużych przedsiębiorstw i nie, nie są dobre.
Michael Borgwardt

Odpowiedzi:


47

Powodem ukrywania szczegółów nie jest ukrywanie szczegółów; umożliwia modyfikację implementacji bez rozbijania kodu zależnego.

Wyobraź sobie, że masz listę obiektów, a każdy obiekt ma Namewłaściwość i inne dane. I wiele razy musisz znaleźć pozycję na liście, która Namepasuje do określonego ciągu.

Oczywistym sposobem jest zapętlenie każdego elementu jeden po drugim i sprawdzenie, czy Namepasuje do łańcucha. Ale jeśli okaże się, że zajmuje to zbyt dużo czasu (tak jak gdyby to miało kilka tysięcy pozycji na liście), możesz chcieć zastąpić go wyszukiwaniem słownika obiektów łańcuchowych.

Teraz, jeśli wszystkie twoje wyszukiwania zostały wykonane przez pobranie listy i zapętlenie jej, masz ogromną pracę do zrobienia, aby to naprawić. Jest jeszcze trudniej, jeśli jesteś w bibliotece i korzystają z niej użytkownicy zewnętrzni; nie możesz wyjść i naprawić ich kodu!

Ale jeśli miał FindByNamemetodę hermetyzacji procesu nazwą odnośnika, można po prostu zmienić sposób jest to realizowane i cały kod, który wywołuje to będzie kontynuować pracę i dostać dużo szybciej za darmo. To jest prawdziwa wartość abstrakcji i enkapsulacji.


Zgadzam się z tym, ale nie o to chodziło w tym pytaniu. Mogę całkowicie zgodzić się, że metoda kapsułkowania określonej funkcjonalności jest przydatna, ale wydaje mi się, że nie zawsze jest to motywem. Czy się mylę?
user606723,

3
@ User606723: Powinno być. To nie znaczy, że zawsze tak jest . Niektórzy ludzie nie rozumieją sensu i po prostu nakładają więcej warstw na więcej warstw i zamieniają rzeczy w bałagan. Dlatego doświadczeni programiści radzą nowszym programistom, aby nigdy nie stosowali nowych technologii ani technik, dopóki ich nie zrozumieją.
Mason Wheeler,

3
@ user606723: Przejrzystość zachęca do ścisłego łączenia, więc może nie zawsze jest źle, ale zwykle tak jest.
Malfist

3
Problem, który opisuje Mason, polegający na tym, że ludzie układają się na warstwach w formie programowania kultowego, jest powodem, dla którego podejrzewam zbyt wiele dziedziczenia i dlaczego należy preferować kompozycję zamiast dziedziczenia. Wydaje się, że jest to szczególnie problem dla programistów Java.
jhocking

2
Nie, ciasne połączenie nie zawsze jest złe. Często jest źle, ale staranie się, aby tego uniknąć, może powodować jeszcze gorsze problemy, takie jak kodowanie miękkie.
Mason Wheeler

16

Właśnie skończyłem czytać sekcję w Code Complete o abstrakcji, więc tam większość źródeł.

Celem abstrakcji jest usunięcie potrzeby pytania „jak to się realizuje?”. Kiedy dzwonisz user.get_id(), wiesz, że idto właśnie otrzymasz. Jeśli musisz zapytać „w jaki sposób uzyskuje identyfikator użytkownika?” wtedy prawdopodobnie nie potrzebujesz idalbo get_id()zwraca coś, co jest nieoczekiwane i źle zaprojektowane.

Używasz abstrakcji, aby projektować:

a house with doors and windows

nie projekt

a box with four walls,
    with 3 holes,
        two of which fit panes of glass surrounded by wood frames,
        one that fits a large plank of wood with hinges and a metal knob,
etc.

Nie mówię o tych interfejsach. Te interfejsy są w porządku. Mówię o ogromnych złożonych systemach, które są podzielone na wiele różnych abstrakcji.
user606723,

9
@ user606723 Twoje pytanie dotyczy raczej nadmiernie złożonego projektu niż abstrakcji
Andres F.,

3
+1 za kod ukończony. Obejmuje to zarówno, dlaczego abstrakcja jest konieczna do zaprojektowania, jak i dlaczego zły poziom abstrakcji utrudnia projektowanie. Kontynuując twój przykład, jeśli pracuję w biurze zagospodarowania przestrzennego, chcę myśleć o twoich domach, nie zagłębiając się w szczegóły. Ale jeśli myślałeś o domach tak, jak ja, to nie możesz ich zbudować.
Spencer Rathbun

pytanie to należy zamknąć lub zmienić tytuł
Jake Berger,

8

Czy ukrywanie szczegółów ma realną wartość?

Tak. Prezentując abstrakcje, możemy myśleć i programować na wyższym poziomie.

Wyobraź sobie modelowanie układów fizycznych bez rachunku różniczkowego lub algebry macierzy. Jest to całkowicie niepraktyczne. Podobnie, jeśli możemy programować tylko na poziomie skalarnym, nie będziemy w stanie rozwiązać interesujących problemów. Nawet stosunkowo proste aplikacje internetowe mogą znacznie skorzystać z abstrakcji, takich jak biblioteki znaczników. Znacznie łatwiej jest wstawić znacznik oznaczający „pola wprowadzania adresu” niż wielokrotnie tworzyć cztery pola tekstowe i pole wyboru. A jeśli zdecydujesz się na ekspansję zagraniczną, możesz po prostu zmodyfikować definicję tagu, zamiast naprawiać każdy formularz do obsługi adresów międzynarodowych. Skuteczne wykorzystanie abstrakcji sprawia, że ​​niektórzy programiści są dziesięć razy skuteczniejsi niż inni.

Ludzie mają ograniczoną pamięć roboczą. Abstrakcja pozwala nam wnioskować o dużych systemach.

Aren't we sacrificing transparency?

Nie. Jeśli abstrakcje nie są używane, to cel komponentu oprogramowania jest zakopany w powtarzających się szczegółach. Programiści spędzają dni na brodzeniu przez kod w ten sposób:

for (i = 0; i < pilgrim.wives.size(); ++i) {
  wife = pilgrim.wives[i];
  for (j = 0; j < wife.sacks.size(); ++j) {
     sack = wife.sacks[i];
     for (k = 0; j < sack.cats.size(); ++j) {
        cat = sack.cats[k];
        for (m = 0; m < cat.kits.size(); ++m) {
           ++count;
        }
     }
  }
}

i myślę „och tak, kolejna czteropoziomowa pętla nad zestawami”, zamiast widzieć

pilgrim.kits.each { ++count; }

Czy ta przejrzystość nie jest cenna?

Jak wskazałeś, pośrednictwo wiąże się z pewnymi kosztami. Tworzenie warstw „na wszelki wypadek” nie ma sensu. Użyj abstrakcji, aby zmniejszyć powielanie i wyjaśnić kod.


7

Kiedy ludzie mówią, że abstrakcje ukrywają szczegóły implementacji, w rzeczywistości nie oznaczają „ukryć” w tym sensie, że utrudniają ich znalezienie. Chodzi o oddzielne szczegóły implementacji od interfejsu publicznego, aby interfejs był prosty, zwięzły i łatwy w zarządzaniu. Podobnie jak samochód „ukrywa” większość swoich istotnych części i oferuje jedynie dość podstawowy zestaw elementów sterujących do ich obsługi, moduł oprogramowania „ukrywa” większość swojej funkcjonalności głęboko w jelitach i udostępnia jedynie ograniczoną liczbę metod dostępu do prowadź to. Wyobraź sobie samochód, w którym musiałeś ręcznie obsługiwać wszystkie elementy wewnętrzne silnika (a jest ich naprawdę dużo), trudno byłoby mieć oko na ruch drogowy i znaleźć drogę.

Ale utrzymanie prostego interfejsu nie jest jedynie kwestią estetyczną; może to zmienić różnicę między udanym projektem a marszem śmierci. Grajmy przez chwilę w adwokata diabła; wyobraź sobie projekt oprogramowania bez żadnych abstrakcji. Jeśli chcesz zachować wartość, użyj zmiennej globalnej. Jeśli chcesz użyć funkcji więcej niż raz, skopiuj ją i wklej. Jeśli potrzebujesz dwóch różnych wersji określonej sekcji kodu, skopiuj-wklej, zawiń ją w ifinstrukcji i zmodyfikuj obie gałęzie. Technicznie rzecz biorąc, to działa, ale kilka miesięcy później będziesz walczył z kilkoma naprawdę paskudnymi problemami:

  • Gdy znajdziesz i naprawisz błąd, może on również istnieć w innych wklejonych do kopiowania instancjach podobnego kodu, więc oprócz znalezienia i naprawienia błędu musisz także poszukać innych zdarzeń i je naprawić.
  • Aby znaleźć błąd lub wprowadzić zmianę, programista serwisowy musi być w stanie zrozumieć odpowiedni kod. Trudność w tym zakresie wzrasta wraz z rozmiarem odpowiedniej sekcji kodu, ale jeszcze bardziej z jego zakresem. Możliwe jest trzymanie w głowie pół tuzina zmiennych podczas mentalnego przechodzenia przez jakiś kod; ale jeśli masz ich kilkaset, poważnie wpływa to na twoją produktywność (lubię porównywać proces myślowy z programem, który kończy fizyczną pamięć RAM i musi zanurzyć się w pliku wymiany: zamiast płynnie czytać kod za jednym razem , programista musi skakać tam i z powrotem, aby sprawdzić rzeczy).
  • Zakres fragmentu kodu wpływa również na rozmiar bazy kodu, którą należy przekopać, aby znaleźć błąd. Jeśli masz funkcję dziesięcioliniową z dwoma parametrami i nie ma globali, a znasz wartości wejścia i linii, w której się zawiesza, znalezienie błędu jest zwykle trywialne i często wymaga jedynie patrzenia na kod. Jeśli jest to kilkaset linii, dwadzieścia parametrów, piętnaście globali i wywołuje kilka innych funkcji o podobnej naturze, czeka cię poważny ból.
  • Bez odpowiedniej abstrakcji każda zmiana może potencjalnie wpłynąć na duże części bazy kodu, ponieważ praktycznie wszystko może zależeć od zmienianego kodu. Typowym objawem takich baz kodowych jest to, że dokonujesz niewielkiej, pozornie niewinnej zmiany, a zupełnie niezwiązana funkcja nagle się psuje. Dzięki abstrakcji możesz ograniczyć ilość obrażeń, jakie może wyrządzić zmiana, dzięki czemu wpływ będzie bardziej przewidywalny. Jeśli zmienisz nazwę prywatnego pola, będziesz mieć tylko jeden plik źródłowy do sprawdzenia; jeśli zmienisz nazwę zmiennej globalnej, musisz uruchomić całą bazę kodu.

W źle wyodrębnionej bazie kodu wpływ zwykle rośnie wykładniczo wraz z rozmiarem bazy kodu, to znaczy dodanie stałej ilości kodu zwiększa wysiłek konserwacyjny o stały współczynnik. Co gorsza, dodanie większej liczby programistów do projektu nie zwiększa produktywności liniowo, ale w najlepszym razie logarytmicznie (ponieważ im większy zespół, tym więcej narzutu wymaga komunikacja).


2

Myślę, że powinieneś zrozumieć, jak to działa w razie potrzeby. Kiedy już określisz, że robi to, co myślałeś, że to zrobi, masz spokój. Nigdy nie myślałem, że celem było ukrycie tego na zawsze.

Po ustawieniu budzika, który jest pewny, że zadziała, możesz spać, wiedząc, że zadziała w odpowiednim momencie. Budzenie się o godzinę wcześniej, żebyś mógł obserwować, jak sekundy odliczają, to marnowanie.


1
Jasne, ale często nie jestem proszony o zmianę sposobu działania mojego budzika lub o zmianę innych budzików przez innych ludzi bez pełnej informacji o zmianach.
user606723,

1
Czy widzisz zmiany w kodzie dla każdego frameworka, którego kiedykolwiek używałeś?
JeffO

1
nie, ale nie utrzymuję zmian w kodzie dla każdego frameworka, którego kiedykolwiek używałem.
user606723,

3
Prawie udowodniłeś, że JeffO ma rację: nie zajmujesz się także utrzymywaniem budzików. Jeśli kupisz taki i zaczniesz go używać bez przeprowadzania pełnego demontażu i analizy jego działania, zaakceptowałeś, że wnętrze będzie dla ciebie abstrakcyjne. Nic nie mówi, że nie możesz rozerwać go na części, aby dowiedzieć się, jak to działa, ale jak często uważasz, że jest to konieczne?
Blrfl

2

Aby odpowiedzieć konkretnie na twoje pytania:

Czy ukrywanie szczegółów ma realną wartość?

Tak. Jak przyznajesz w pierwszym wierszu pytania.

Czy nie poświęcamy przejrzystości?

Nie całkiem. Dobrze napisana abstrakcja ułatwi zrozumienie szczegółów w razie potrzeby.

Czy ta przejrzystość nie jest cenna?

Tak. Abstrakcje powinny być zaprojektowane i wdrożone, aby ułatwić zrozumienie szczegółów w razie potrzeby / potrzeby.


Wreszcie odpowiedź lubię.
user606723,

1

Powiedziałbym, że ukrywanie szczegółów jest świetne, gdy działają ukryte rzeczy.

Załóżmy na przykład, że opracowujemy interfejs, który definiuje zachowania (tj. GetListItems, SendListItem), które są dwiema funkcjami inicjowanymi przez użytkownika poprzez kliknięcie przycisku lub coś takiego .. TERAZ każdy użytkownik może mieć swój własny „ListItemStore” .. powiedz jeden jest na Facebooku, na myspace .. (na przykład) .. i powiedz, że jest zapisany jako właściwość / ustawienie użytkownika gdzieś w aplikacji poprzez preferencje użytkownika .. i powiedz, że twórcy aplikacji mogą dodawać dodatkowe ListItemStore w trakcie czas (mybook, facespace itp.)

teraz jest wiele szczegółów na temat łączenia się z Facebookiem i zdobywania ich przedmiotów .. i jest tyle samo szczegółów podczas łączenia się z myspace .. i tak dalej ...

teraz, po napisaniu początkowego kodu „dostępu do sklepu”, może nie być konieczne jego modyfikowanie (w przypadku Facebooka prawdopodobnie potrzebujemy pełnoetatowego programisty, aby nadążyć za zmianami, zing ..),

więc kiedy używasz kodu, jest to coś takiego:

    new ItemManager(user) //passes in user, allowing class to get all user properties
    ItemManager.GetListItems()

a teraz masz dane użytkownika z dowolnego miejsca, w którym je przechowujesz, a ponieważ martwię się o to, aby uzyskać listę przedmiotów i coś z tym zrobić, a ponieważ zajęło to tylko 2 wiersze, które będą działać bez względu na to, jak dodano wiele innych sklepów, mogę wrócić do odpowiadania / zamieszczania pytań na stosie ... lol ..

Tak więc cała instalacja hydrauliczna, aby tak się stało, jest „ukryta” i kogo to naprawdę obchodzi, o ile to zrobię, dopóki dostanę prawidłową listę przedmiotów. Jeśli masz testy jednostkowe, możesz nawet łatwiej odpocząć, ponieważ wyniki powinny „ zostały już określone ilościowo ..


Tak, ale częścią, którą należy zachować, nigdy nie są te dwie linie. Nie da się tego zepsuć. Zawsze musisz zmienić poziom 2., 3., 4. poziomu w dół. Zgadzam się, że jest to świetne, gdy masz interfejs API, któremu możesz zaufać, że jest stabilny , ale co, jeśli nie będzie stabilny ze względu na złożoność Twojej firmy?
user606723,

1
@ user606723, jeśli interfejs API nie jest stabilny, prawdopodobnie jest on niedojrzały lub, co bardziej prawdopodobne, niewłaściwa abstrakcja
sdg

@ user606723 - To prawda, a w tych przypadkach same konwencje nazewnictwa powinny określać jakąś formę przejrzystości, która ukrywa rzeczywistą logikę / szczegół programistyczny .. a jeśli kiedykolwiek będziesz musiał zmodyfikować rzeczywiste źródło, dalsze szczegóły i pomysły powinny być wyrażone za pomocą informacyjnego nazewnictwa więc w zasadzie przezroczystość można osiągnąć, stosując odpowiednie nazewnictwo aż do samego końca, jednak nie powinniśmy zbyt często schodzić w dół.
hanzolo

@sdg lub ponieważ wymagania systemu zawsze się zmieniają. Ponieważ prawa się zmieniają, ponieważ zmieniają się interfejsy API innych osób, to jest poza naszą kontrolą.
user606723,

1
@ user606723 - Właściwie pracuję na finansowym systemie SaaS i dostrzegam pułapki związane z brakiem wystarczającej abstrakcji w każdym cyklu wydawniczym. niektóre z nich są wynikiem złego projektu, ale zazwyczaj wynikają z pchania kodu tam, gdzie pierwotnie nie był przeznaczony. Schodzenie w dół łańcucha może być bolesne bez tych komentarzy, nazw i enkapsulacji . jeśli wszystko, co musiałem zrobić, to Remeasurements.GetRemeasureType (grant), a następnie reMeasure.PerformCalc (), byłoby to o wiele ładniejsze niż dodawanie przeciążeń i czytanie różnych gałęzi logicznych w celu uzyskania prawidłowego obliczenia lub dodania nowego
hanzolo

1

To, co nazywacie „ukrywaniem”, wielu postrzega jako oddzielne obawy (np. Implementacja vs. Interfejs).

Moim zdaniem, jedną z głównych korzyści z abstrakcji jest zmniejszenie niepotrzebnych szczegółów z ograniczonej przestrzeni mózgowej dewelopera.

Gdyby kod implementacyjny był zaciemniony, widziałbym, że utrudnia to przejrzystość, ale abstrakcja, którą widzę, jest po prostu dobrą organizacją.


1

Po pierwsze, wszystko poza instrukcją pojedynczego kodu maszynowego jest w istocie abstrakcją - while...dopętla jest spójnym symbolicznym sposobem reprezentowania porównań i wywołań adresowych wymaganych do powtórzenia zestawu instrukcji do momentu spełnienia warunku. Podobnie typ int jest abstrakcją dla liczby X bitów (w zależności od systemu). W programowaniu chodzi o abstrakcję.

Prawdopodobnie zgodziłbyś się, że te prymitywne abstrakcje są niezwykle przydatne. Cóż, więc jest w stanie zbudować własny. OOAD i OOP są najważniejsze.

Załóżmy, że masz wymaganie, aby użytkownicy chcieli móc eksportować dane z ekranu w różnych formatach: tekst rozdzielany, Excel i pdf. Czy to nie przydatne, że można utworzyć interfejs o nazwie „Eksporter” za pomocą metody eksportu (danych), na podstawie której można zbudować DelimitedTextExporter, ExcelExporter i PDFExporter, z których każdy wie, jak stworzyć swoje konkretne wyjście? Jedyny program wywołujący musi wiedzieć, że może wywoływać metodę eksportu (danych), a cokolwiek zastosuje implementacja, spełni swoje zadanie. Co więcej, jeśli zmieniają się reguły tekstowe rozdzielane, możesz zmienić DelimitedTextExporter bez konieczności bałagania się w ExcelExporter, być może zerwania.

Prawie wszystkie dobrze znane wzorce projektowe stosowane w programowaniu OO zależą od abstrakcji. Polecam lekturę Freemana i Wzory pierwszego projektu Freemana, aby lepiej zrozumieć, dlaczego abstrakcja jest dobra


1
nawet kod maszynowy jest abstrakcją, istnieją rzeczywiste, fizyczne procesy zachodzące pod logiką, nawet cyfrowa elektronika jest abstrakcją tego, co naprawdę się dzieje, ponieważ każdy, kto dokonał skrótu podkręcania układu, może zaświadczyć
jk.

To prawda, chociaż wydaje się bardziej konkretne na poziomie instrukcji maszyny.
Matthew Flynn

1

Myślę, że rozumiem twoje zdanie na ten temat i myślę, że mam podobne zdanie.

Współpracowałem z programistami Java, którzy przekształcają 50 linii wiersza kodu w 3 klasy i 3 interfejsy, ponieważ jest to łatwe do zrozumienia. I nie mogłem tego znieść.

Sprawa była strasznie trudna do zrozumienia, prawie niemożliwa do debugowania i nigdy nie musiała „przełączać implementacji”.

Z drugiej strony widziałem również kod, w którym wiele obiektów ma podobne zachowanie i jest używanych w jednym miejscu, i mogłyby naprawdę korzystać ze wspólnych pętli sortowania / przetwarzania, gdyby metody zostały ujawnione za pośrednictwem wspólnego interfejsu.

Tak więc, IMHO, podstawowe obiekty, które prawdopodobnie będą wykorzystywane w podobnych scenariuszach, zwykle korzystają ze wspólnego zachowania, które powinno być dostępne poprzez interfejs. Ale to wszystko, abstrahując od prostych rzeczy, ponieważ ma rację lub umożliwia zmianę implementacji, jest tylko sposobem na bałagan w kodzie.

Z drugiej strony wolę dłuższe, inteligentniejsze klasy od gwałtownej liczby małych klas ze wszystkimi problemami zarządzania przez całe życie, trudnymi do zobaczenia relacjami i wykresami połączeń spaghetti. Więc niektórzy ludzie się ze mną nie zgodzą.


1

Nadrzędnym celem ukrywania i abstrakcji powinno być oddzielenie użytkownika od implementacji, aby można je było niezależnie zmienić. Jeśli konsument jest połączony ze szczegółami implementacji, z powodu majstrowania przy wewnętrznych elementach, oba są odlane w kamieniu i trudniej jest wprowadzić nowe funkcje lub lepsze algorytmy w przyszłości.

Podczas pisania modułu ukryte części implementacji dają ci poczucie, że możesz je zmienić bez ryzyka złamania innego kodu, o którym nie możesz pomyśleć.

Kolejną zaletą zapewniania nieprzejrzystych interfejsów jest to, że znacznie zmniejszają one powierzchnię między podsystemami. Zmniejszając liczbę sposobów interakcji, mogą stać się bardziej przewidywalne, łatwiejsze do przetestowania i mniej błędów. Interakcje między modułami również zwiększają się kwadratowo wraz z liczbą modułów, dlatego warto kontrolować ten wzrost złożoności.


To powiedziawszy, oczywiście można ukryć za dużo i zbyt głęboko zagnieździć interfejsy. Zadaniem programisty, jako inteligentnego człowieka, jest zaprojektowanie systemu tak, aby był maksymalnie użyteczny, jednocześnie minimalizując złożoność i maksymalizując łatwość konserwacji.


0

W tak wielu przypadkach po prostu nie musisz wiedzieć, jak rzeczy są realizowane. Mogę prawie zagwarantować, że napiszesz taki kod people.Where(p => p.Surname == "Smith")tak wiele razy dziennie, ale prawie nigdy nie pomyślisz „jak ta Where()metoda faktycznie działa?” Po prostu cię to nie obchodzi - wiesz, że ta metoda istnieje i że zapewnia ci oczekiwane rezultaty. Dlaczego miałbyś obchodzić, jak to działa?

Jest to dokładnie to samo w przypadku dowolnego oprogramowania wewnętrznego; to, że nie zostało napisane przez Oracle, Microsoft itp., nie oznacza, że ​​powinieneś polować na sposób jego implementacji. Można zasadnie oczekiwać, że wywołana metoda GetAllPeopleWithSurname(string name)zwróci Ci listę osób o tym nazwisku. Może iterować po liście, może używać słownika, może zrobić coś całkowicie szalonego, ale po prostu nie powinno cię to obchodzić .

Istnieje oczywiście wyjątek od tej reguły (bez niej nie byłaby to reguła!), Czyli w przypadku błędu w metodzie. Tak więc w powyższym przykładzie, jeśli masz listę z 3 osób w tym i wiesz, że jeden z nich ma nazwisko Smith i nie są zwracane w liście następnie dbasz o realizacji tej metody, ponieważ jest wyraźnie uszkodzony .

Po prawidłowym wykonaniu abstrakcja jest cudowna, ponieważ pozwala odfiltrować wszystkie rzeczy, które nie są przydatne, gdy trzeba ją przeczytać później. Nie zapominaj, że dużo więcej czasu spędza się na czytaniu kodu niż na pisaniu go, dlatego należy położyć nacisk na jak najłatwiejsze wykonanie tego zadania. Być może myślisz również, że abstrakcja oznacza hierarchię obiektów tak długich jak twoje ramię, ale może być tak prosta, jak refaktoryzacja metody 100-liniowej na 10 metod, każda o długości 10 linii. To, co kiedyś było 10 krokami wszystkie razem, teraz jest 10 oddzielnymi krokami, dzięki czemu możesz udać się prosto do miejsca, w którym ukrywa się ten nieznośny błąd.


Ech, ale powiedzmy, że to ty utrzymujesz wdrożenie. Chodzi mi o to, że ktoś dba o wdrożenie, a tą osobą jesteś ty. Zdarza się również, że jesteś użytkownikiem implementacji ... Zmieniają się wymagania biznesowe (w sposób niemożliwy do przewidzenia), powodując zmiany na wielu poziomach. Chyba mam na myśli to, że błąd nie jest jedynym wyjątkiem od tej reguły.
user606723,

Problemem jestPeopleFactory.People.Strategy.MakePeople.(CoutryLaw.NameRegistry.NameMaker.Make()) as People.Female
Coder

@ user606723 Nie musisz cały czas dbać o każdy wiersz kodu w bazie kodu. Jeśli jest to błąd w tej realizacji lub, że został oznaczony jako konieczności ponownego napisane (bo jest powolny, źle napisane lub cokolwiek) i jesteś ponownego pisania, następnie dbasz o niego. W przeciwnym razie należy go trzymać z dala. Być może Twoim problemem jest to, że cały czas próbujesz posiadać cały kod. Moim zdaniem powinieneś skoncentrować się na kodzie, nad którym pracujesz w danym momencie.
Stuart Leyland-Cole

@Coder Oczywiście jest to skrajna forma abstrakcji! Z początku myślałem, że jest to coś, przeciwko czemu OP jest przeciwny, ale po przeczytaniu reszty odpowiedzi wydaje się, że postrzegają całą abstrakcję jako bardzo złą, złą. Dlatego w mojej odpowiedzi próbowałem wyjaśnić różne poziomy abstrakcji.
Stuart Leyland-Cole

0

Abstrakcje powodują ukrycie informacji. To powinno skończyć się na dolnym sprzęgle. Powinno to prowadzić do zmniejszenia ryzyka zmian. Powinno to doprowadzić do zadowolenia programistów, nie denerwując się przy dotykaniu kodu.

Te idee są wyrażone przez trzy podstawowe prawa w architekturze oprogramowania:

Prawo Simona: „Hierarchie zmniejszają złożoność”. (Hierarchie wprowadzają abstrakcję)

Prawo Parny: „Tylko to, co jest ukryte, można zmienić bez ryzyka”.

Prawo Constantina: solidne programy wymagają niskiego sprzężenia i wysokiej spójności


„Hierarchie zmniejszają złożoność”. - niekoniecznie prawda.
Koder

Żadna metodologia projektowania nie jest deterministyczna. To prawo nie pochodzi od IT / CS, jest sformułowane w znacznie szerszym znaczeniu i jest również określane przez matematykę, fizyków itp. Jest to ważna zasada, ale nikt nie może powstrzymać cię przed tworzeniem nonsensownych Hierarchii.
ins0m

0

Jestem również w branży aplikacji korporacyjnych i to pytanie przykuło moją uwagę, ponieważ sam mam takie samo pytanie. Do tej pory miałem pewien wgląd w kwestię abstrakcji w trakcie mojej kariery, ale mój wgląd nie jest bynajmniej uniwersalną odpowiedzią. Nadal uczę się / słucham nowych pomysłów i myśli, więc to, co obecnie uważam, może się zmienić.

Kiedy utrzymywałem dużą i złożoną aplikację opieki zdrowotnej, po prostu cię lubiłem, nienawidziłem tam wszystkich abstrakcji. Ustalenie, dokąd zmierza cały kod, to ból szyi. Skakanie po różnych klasach powodowało zawroty głowy. Więc powiedziałem sobie: „abstrakcja jest do bani, zminimalizuję abstrakcję, kiedy projektuję”.

Potem przyszedł czas, kiedy musiałem zaprojektować aplikację (względnie mały komponent usługi internetowej) od podstaw. Pamiętając cały ból, miałem dość płaską konstrukcję komponentu. Problem polegał na tym, że kiedy wymagania uległy zmianie, musiałem dokonać zmian w wielu różnych miejscach (wymagania były dość płynne i nie mogłem nic z tym zrobić). Było tak źle, że w zasadzie zrzucam swój pierwotny projekt i przeprojektowanie z abstrakcją, a sytuacja się poprawiła - nie musiałem wprowadzać zmian w wielu miejscach, gdy wymagania się zmieniały.

Dostarczyłem aplikację, usiadłem przez kilka tygodni, a potem powiedziano mi, aby rozpocząć utrzymywanie aplikacji. Od jakiegoś czasu nie pamiętałem wszystkiego, więc trochę starałem się zrozumieć mój własny kod, a abstrakcja nie pomagała.

Później wziąłem udział w wielu innych projektach i miałem trochę więcej okazji do zabawy z poziomami abstrakcji. To, co faktycznie znajduję, to tylko moja osobista opinia, abstrakcja bardzo pomaga w rozwoju, ale ma negatywny wpływ, gdy nie napisałeś kodu i próbujesz zrozumieć wszystko na najgłębszym poziomie aplikacji; spędzicie więcej czasu skacząc po różnych klasach i próbując nawiązać kontakty.

Mam wrażenie, że abstrakcja jest tak cenna w czasie programowania, że ​​kłopoty, które przechodzimy jako opiekun, gdy próbujemy zrozumieć kod, są warte zachodu. Istnieje oprogramowanie do rozwiązywania problemów biznesowych, problemy biznesowe ewoluują z czasem; stąd oprogramowanie musi ewoluować z czasem. Bez abstrakcji ewolucja oprogramowania jest bardzo trudna. Można zaprojektować abstrakcję w taki sposób, że opiekun może z łatwością poruszać się po bazie kodu, gdy zobaczy wzór struktury kodu, tak że frustruje tylko początkowa krzywa uczenia się.


0

Jak powiedzieli inni, „ukrywanie szczegółów” za abstrakcją pozwala na ich zmianę bez wpływu na użytkowników. Pomysł ten pochodzi z „ Kryteriów stosowanych przez Parnasa w dekompozycji systemów na moduły” (1972) i jest związany z ideą abstrakcyjnych typów danych (ADT) i programowaniem obiektowym.

Mniej więcej w tym samym czasie motywacyjny model relacyjnych danych dużych dużych banków danych Codda (1970) (patrz streszczenie i wprowadzenie) chciał zmienić reprezentację pamięci wewnętrznej baz danych bez wpływu na użytkowników bazy danych. Widział, jak programiści regularnie poświęcają dni, modyfikując strony kodu, aby poradzić sobie z niewielkimi zmianami pamięci.

To powiedziawszy, abstrakcja nie jest zbyt przydatna, jeśli musisz zobaczyć, co jest w środku, aby móc z niej skorzystać. Dobrze może być dobrze zaprojektowane. Przykładem dobrej abstrakcji jest dodatek - kiedy ostatni raz musiałeś pomyśleć o tym, co dzieje się w środku? (ale czasami to robisz, np. w przypadku przepełnienia).

Podstawowym problemem (IMHO) jest to, że aby dobrze zaprojektować moduły (w sensie Parnasa), musisz przewidzieć, co się zmieni, a co nie. Przewidywanie przyszłości jest trudne - ale jeśli masz duże doświadczenie z czymś i dobrze to rozumiesz, możesz dobrze przewidzieć. Dlatego możesz zaprojektować moduł (abstrakcja), który działa dobrze.

Wydaje się jednak, że los wszystkich abstrakcji - nawet tych najlepszych - że ostatecznie nastąpią nieprzewidziane (i prawdopodobnie nieprzewidywalne) zmiany, które wymagają przerwania abstrakcji. Aby rozwiązać ten problem, niektóre abstrakcje mają wyjście, w którym możesz uzyskać dostęp do głębszego poziomu, jeśli naprawdę tego potrzebujesz.

To wszystko wydaje się bardzo negatywne. Ale myślę, że prawda jest taka, że ​​otaczają nas abstrakcje, które działają tak dobrze, że ich nie zauważamy ani nie zdajemy sobie sprawy z tego, co ukrywają. Dostrzegamy tylko słabe abstrakcje, więc mamy ich żółtawy widok.


0

Abstrakcje są głównie z korzyścią dla ich konsumentów (np. Programistów aplikacji). Programiści systemowi (projektanci) mają więcej pracy, aby uczynić je pięknymi i użytecznymi, dlatego dobry projekt zwykle nie jest wykonywany przez początkujących.

Może nie lubisz abstrakcji, ponieważ zawsze dodają złożoności? Może systemy, nad którymi pracowałeś, miały zapalenie abstrakcji (nadmierne korzystanie z abstrakcji)? Nie są panaceum.

Dodatkowa praca i złożoność przydatnej abstrakcji powinny się opłacić, ale trudno jest na pewno wiedzieć. Jeśli myślisz o abstrakcji jako punkcie zwrotnym, wówczas projekt oprogramowania może być elastyczny po obu stronach: implementacje abstrakcji można modyfikować bez łamania kodu klienta i / lub nowi klienci mogą łatwo ponownie używać abstrakcji do tworzenia nowych rzeczy.

Można niemal zmierzyć zwrot z inwestycji w abstrakcje, pokazując, że zostały one „wygięte” w czasie w jednym lub obu z tych kierunków: stosunkowo bezbolesne zmiany wdrożenia i dodatkowi nowi klienci.

Na przykład: Używając abstrakcji klasy Socket w Javie, jestem pewien, że mój kod aplikacji z Java 1.2 nadal działa dobrze w Javie 7 (chociaż mogą to być pewne zmiany wydajności). Od wersji Java 1.2 zdecydowanie było też wielu nowych klientów, którzy korzystali z tej abstrakcji.

Jeśli chodzi o niezadowolenie z abstrakcji, jeśli rozmawiałem z programistami, którzy utrzymywali kod za klasą Socket, to może ich życie nie jest tak brzoskwiniowe i różowe jak klienci, którzy używali Socket do pisania zabawnych aplikacji. Praca nad implementacją abstrakcji z pewnością wymaga więcej pracy niż jej użycia. Ale to nie jest złe.

Jeśli chodzi o przezroczystość, w odgórnej strategii projektowania całkowita przejrzystość powoduje złe projektowanie. Inteligentni programiści mają tendencję do maksymalnego wykorzystywania informacji, którymi dysponują, a następnie system jest ściśle powiązany. Najmniejsza zmiana szczegółów (np. Zmiana kolejności bajtów w strukturze danych) w module może złamać jakiś kod gdzie indziej, ponieważ inteligentny programista wykorzystał te informacje, aby zrobić coś pożytecznego. David Parnas wskazał na ten problem w artykułach sprzed 1971 roku, w których proponował ukrywanie informacji w projektach.

Twoje odniesienie do ArchLinux ma dla mnie sens, jeśli weźmiesz pod uwagę „wnętrze” systemu operacyjnego jako złożoną implementację abstrakcji, jaką jest system operacyjny dla aplikacji na nim działających. Prostota w trzewiach abstrakcji.


0

Odpowiem na twoje pytanie pytaniem; kiedy pojechałeś dziś rano do pracy (zakładam, że tak zrobiłeś), czy obchodziło cię dokładnie, jak silnik otworzył zawory, aby wpuścić mieszanki paliwowo-powietrzne, a następnie je zapalił? Nie. Nie obchodzi Cię, jak działa silnik twojego samochodu podczas jazdy w dół drogi. Dbasz, że ma pracę.

Załóżmy, że pewnego dnia twój samochód nie działa. Nie uruchamia się, rzuca pręt, zrywa pasek, niewytłumaczalnie wbija się w tę betonową barierę bez własnej winy, kiedy byłeś zajęty pisaniem wiadomości. Teraz potrzebujesz nowego samochodu (przynajmniej tymczasowo). Czy obchodzi Cię dokładnie, jak działa ten nowy samochód? Nie. Najważniejsze jest, aby po pierwsze działało, a po drugie, możesz korzystać z tej samej wiedzy i umiejętności, których używałeś do prowadzenia starego samochodu do prowadzenia nowego. Idealnie powinno ci się wydawać, że nie ma zmian w samochodzie, którym jeździsz. Realistycznie sposób, w jaki działa ten nowy samochód, powinien dać ci jak najmniej „niespodzianek”, jak to możliwe.

Te podstawowe zasady są podstawową zasadą enkapsulacji i abstrakcji. Wiedza o tym, jak obiekt robi to, co robi, nie powinna być wymagana do używania go do robienia tego, co robi. Nawet w programowaniu komputerowym szczegóły ścieżek elektrycznych w procesorze uruchomionym przez program są wyodrębnione za co najmniej kilkanaście warstw instrukcji I / O, sterowników, oprogramowania systemu operacyjnego i środowiska wykonawczego. Wielu bardzo udanych inżynierów oprogramowania pisze doskonale dobry kod, nie martwiąc się nawet o dokładną architekturę sprzętu, a nawet kompilację systemu operacyjnego, która go uruchomi. W tym mnie.

Ukrywanie enkapsulacji / informacji pozwala na mentalność „nie przejmuj się, jak to robi, tylko dbaj o to”. Twój obiekt powinien ujawniać to, co jest użyteczne dla konsumenta, w taki sposób, aby konsument mógł go łatwo konsumować. Teraz, w prawdziwym świecie, nie oznacza to, że samochód nie powinien przekazywać użytkownikowi żadnych informacji o wewnętrznych działaniach ani że samochód powinien umożliwiać użytkownikowi jedynie najbardziej podstawowe funkcje, takie jak zapłon, kierownica, i pedały. Wszystkie samochody mają prędkościomierze i wskaźniki paliwa, tachometry, światła idioty i inne informacje zwrotne. Praktycznie wszystkie samochody mają również przełączniki do różnych niezależnych podsystemów, takich jak reflektory, kierunkowskazy, radio, regulacja siedzeń itp. Niektóre samochody umożliwiają dość ezoteryczny wkład użytkownika, na przykład czułość środkowego mechanizmu różnicowego o ograniczonym poślizgu. We wszystkich przypadkach, jeśli wiesz wystarczająco dużo, możesz go otworzyć i zmienić, aby działał nieco inaczej. Ale w większości przypadków, być może, użytkownik może nie być w stanie bezpośrednio i niezależnie sterować pompami paliwa z kabiny? Może, być może, użytkownik nie powinien być w stanie włączyć swoich świateł hamowania bez faktycznego naciśnięcia pedału hamulca?

Abstrakcja pozwala na to, że „to nie to samo, ale ponieważ oba są XI, mogę używać ich tak, jak bym dowolną X”. Jeśli Twój obiekt dziedziczy lub implementuje abstrakcję, twoi klienci powinni oczekiwać, że twoja implementacja przyniesie taki sam lub podobny wynik jak inne znane implementacje abstrakcji. Toyota Camry i Ford Fusion to „samochody”. Jako takie mają one wspólny zestaw oczekiwanych funkcji, takich jak kierownica. Obróć w lewo, samochód jedzie w lewo. Obróć w prawo, samochód jedzie w prawo. Możesz wsiąść do dowolnego samochodu w Stanach Zjednoczonych i oczekiwać, że samochód będzie miał kierownicę i co najmniej dwa pedały, z których jeden po prawej to pedał „samochód jedzie”, a środkowy to pedał „samochód zatrzymuje się” .

Następstwem abstrakcji jest „teoria najmniejszego zdziwienia”. Jeśli zasiadłeś za kierownicą nowego samochodu na jazdę próbną, obróciłeś kierownicę zgodnie z ruchem wskazówek zegara, a samochód skręcił w lewo, byłbyś zaskoczony co najmniej. Oskarżyłbyś dealera o sprzedawanie punktów sprzedaży i mało prawdopodobne jest, aby usłyszał którykolwiek z jego powodów, dla których nowe zachowanie jest „lepsze” niż to, do czego jesteś przyzwyczajony, lub jak dobrze to zachowanie jest „udokumentowane” lub jak „ przejrzysty ”system sterowania. Pomimo tego, że nowy samochód i wszystkie inne, którymi jeździłeś, nadal są „samochodami”, prowadząc ten samochód, musisz zmienić kilka podstawowych koncepcji, w jaki sposób samochód powinien być prowadzony, aby z powodzeniem prowadzić nowy samochód. To zazwyczaj zła rzecz, i dzieje się tak tylko wtedy, gdy nowy paradygmat ma intuicyjną przewagę. Być może dodanie pasów bezpieczeństwa jest dobrym przykładem; 50 lat temu właśnie wsiadłeś i pojechałeś, ale teraz musisz się zapiąć, intuicyjną zaletą jest to, że nie przechodzisz przez przednią szybę lub do siedzenia pasażera w razie wypadku. Nawet wtedy kierowcy stawiali opór; wielu właścicieli samochodów odcina pasy bezpieczeństwa od samochodu, dopóki nie zostaną uchwalone przepisy nakazujące ich stosowanie.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.