Znaczenie nieszczelnej abstrakcji?

89

Co oznacza termin „nieszczelna abstrakcja”? (Proszę wyjaśnić na przykładach. Często mam trudności z grzebaniem w zwykłej teorii).

Geonne
źródło
możliwe duplikaty interfejsów Fluent i nieszczelne abstrakcje
missingfaktor
14
Możesz przeczytać oryginalny artykuł Joela Spolsky'ego The Law of Leaky Abstractions, który, o ile wiem, jest źródłem tego terminu.
Daniel Pryden,
1
Większość odpowiedzi proponowanego duplikatu dotyczy płynnych interfejsów.
David Thornley
@David: Drugi post z najwyższą liczbą głosów odpowiada na pytanie, co oznacza nieszczelna abstrakcja i na świetnym przykładzie.
missingfaktor
4
Kiedy 4 lata później wyszukujesz w Google to pytanie, trudno odgadnąć, który post był drugim z najwyżej ocenionym postem.
John Reynolds,

Odpowiedzi:

106

Oto przykład Meatspace :

Samochody mają abstrakcje dla kierowców. W najczystszej postaci jest kierownica, pedał gazu i hamulec. Ta abstrakcja ukrywa wiele szczegółów na temat tego, co znajduje się pod maską: silnik, krzywki, pasek rozrządu, świece zapłonowe, chłodnica itp.

Fajną rzeczą w tej abstrakcji jest to, że możemy zastąpić części implementacji ulepszonymi częściami bez ponownego szkolenia użytkownika. Powiedzmy, że zamieniamy pokrywę dystrybutora na elektroniczny zapłon, a stałą krzywkę zastępujemy zmienną krzywką. Te zmiany poprawiają wydajność, ale użytkownik nadal kieruje kierownicą i używa pedałów do uruchamiania i zatrzymywania.

To naprawdę niezwykłe ... 16-latek lub 80-latek może obsługiwać tę skomplikowaną maszynę, nie wiedząc zbyt wiele o tym, jak działa w środku!

Ale są przecieki. Transmisja to mały wyciek. W automatycznej skrzyni biegów można przez chwilę poczuć, jak samochód traci moc, gdy zmienia biegi, natomiast w CVT można poczuć płynny moment obrotowy aż do samego końca.

Są też większe wycieki. Zbyt szybkie zwiększanie obrotów silnika może spowodować jego uszkodzenie. Jeśli blok silnika jest zbyt zimny, samochód może się nie uruchomić lub może mieć słabe działanie. A jeśli jednocześnie włączysz radio, reflektory i klimatyzację, zobaczysz, jak zmniejsza się przebieg paliwa.

Mark E. Haase
źródło
7
Dzięki za przykład. Nikt inny nie był w stanie udzielić prostego wyjaśnienia.
Sebastian Patten,
7
To świetna odpowiedź, zwłaszcza, że ​​pokazuje punkt widzenia użytkownika, o co chodzi w wersji oprogramowania.
czad,
1
Co znaczy Meatspace? Wyjaśnienie dla laika?
brumScouse
1
@brumScouse „meatspace” oznacza fizyczny świat offline. Służy do kontrastowania z internetowym światem cyberprzestrzeni. Zmienię odpowiedź, tak aby zawierała link do definicji.
Mark E. Haase
1
Podoba mi się, jak ten post wskazuje, że „nadal są przecieki”. Chodzi o zminimalizowanie ich.
alaboudi
51

Oznacza to po prostu, że twoja abstrakcja ujawnia niektóre szczegóły implementacji lub że musisz być świadomy szczegółów implementacji, gdy używasz abstrakcji. Termin ten przypisuje się Joelowi Spolsky'emu , około 2002 roku. Więcej informacji można znaleźć w artykule na Wikipedii .

Klasycznym przykładem są biblioteki sieciowe, które pozwalają traktować zdalne pliki jako lokalne. Deweloper korzystający z tej abstrakcji musi mieć świadomość, że problemy z siecią mogą spowodować niepowodzenie w sposób, w jaki nie działają pliki lokalne. Następnie należy opracować kod obsługujący w szczególności błędy poza abstrakcją zapewnianą przez bibliotekę sieciową.

tvanfosson
źródło
7
@mehaase Nie rozumiem, jakie znaczenie ma to, czy twoja abstrakcja jest nieszczelna z powodu projektu, czy przez zaniedbanie. Rozszerzyłem odpowiedź o przykład i więcej informacji z przywoływanego artykułu, aby mógł on działać samodzielnie. Co więcej, nie sądzę, że „nieszczelna abstrakcja” musi koniecznie być pejoratywna. Według mnie opisuje to jedynie sytuację, w której jako programista musisz być bardziej ostrożny podczas pracy z abstrakcją. Projekt może być dobry, zły lub obojętny niezależnie od „nieszczelności”.
tvanfosson
13

Wikipedia ma dość dobrą definicję dla tego

Nieszczelna abstrakcja odnosi się do każdej zaimplementowanej abstrakcji, mającej na celu zmniejszenie (lub ukrycie) złożoności, w której podstawowe szczegóły nie są całkowicie ukryte

Innymi słowy, w przypadku oprogramowania jest to moment, w którym można obserwować szczegóły implementacji funkcji poprzez ograniczenia lub efekty uboczne w programie.

Szybkim przykładem mogą być zamknięcia C # / VB.Net i ich niezdolność do przechwytywania parametrów ref / out. Powodem, dla którego nie można ich uchwycić, jest szczegółowa implementacja sposobu, w jaki przebiega proces podnoszenia. Nie oznacza to jednak, że jest na to lepszy sposób.

JaredPar
źródło
13

Oto przykład znany programistom .NET: ASP.NET Page klasa próbuje ukryć szczegóły operacji HTTP, szczególnie zarządzanie danymi formularza, aby programiści nie musieli zajmować się opublikowanymi wartościami (ponieważ automatycznie mapuje wartości formularzy na serwer sterownica).

Ale jeśli wyjdziesz poza najbardziej podstawowe scenariusze użycia, Pageabstrakcja zacznie przeciekać i praca ze stronami stanie się trudna, jeśli nie zrozumiesz szczegółów implementacji klasy.

Jednym z typowych przykładów jest dynamiczne dodawanie formantów do strony - wartość dynamicznie dodawanych formantów nie zostanie zmapowana, chyba że dodasz je w odpowiednim momencie : zanim silnik bazowy mapuje przychodzące wartości formularza do odpowiednich kontrolek. Kiedy musisz się tego nauczyć, abstrakcja wyciekła .

Jeff Sternal
źródło
Formularze internetowe miały dno w swoim wiadrze. Co gorsza, cienko zawoalowane abstrakcje sprowadzały się do pracy z HTTP tak, jakbyś pracował w schowku.
brumScouse
9

Cóż, w pewnym sensie jest to rzecz czysto teoretyczna, choć nie bez znaczenia.

Używamy abstrakcji, aby ułatwić zrozumienie. Mogę operować na klasie ciągów w jakimś języku, aby ukryć fakt, że mam do czynienia z uporządkowanym zestawem znaków, które są pojedynczymi elementami. Zajmuję się uporządkowanym zestawem znaków, aby ukryć fakt, że mam do czynienia z liczbami. Zajmuję się liczbami, aby ukryć fakt, że mam do czynienia z jedynkami i zerami.

Nieszczelna abstrakcja to taka, która nie ukrywa szczegółów, które ma ukryć. Jeśli wywołasz string.Length w 5-znakowym ciągu w Javie lub .NET, mogę uzyskać dowolną odpowiedź od 5 do 10, ze względu na szczegóły implementacji, w których te języki nazywają znaki w rzeczywistości punktami danych UTF-16, które mogą reprezentować 1 lub .5 znaku. Abstrakcja wyciekła. Jednak brak przecieku oznacza, że ​​znalezienie długości wymagałoby więcej miejsca do przechowywania (aby zapisać rzeczywistą długość) lub zmienić z O (1) na O (n) (aby ustalić, jaka jest rzeczywista długość). Jeśli zależy mi na prawdziwej odpowiedzi (często tak naprawdę nie), musisz popracować nad wiedzą, co się naprawdę dzieje.

Bardziej dyskusyjne przypadki zdarzają się w przypadkach, gdy metoda lub właściwość pozwala ci wejść do wewnętrznych mechanizmów, niezależnie od tego, czy są to wycieki abstrakcji, czy dobrze zdefiniowane sposoby przejścia na niższy poziom abstrakcji, czasami może być kwestią, z którą ludzie się nie zgadzają.

Jon Hanna
źródło
2
I pracujesz z 1 i 0, aby ukryć fakt, że pracujesz z elektroniką i fizyką (bardzo późny komentarz, wiem)
Davy8
7

Kontynuuję w duchu podawania przykładów przy użyciu RPC.

W idealnym świecie RPC zdalne wywołanie procedury powinno wyglądać jak wywołanie procedury lokalnej (a przynajmniej tak się dzieje). Powinien być całkowicie przejrzysty dla programisty, tak aby podczas wywoływania SomeObject.someFunction()nie mieli pojęcia, czy SomeObject(lub tylko someFunctiono to) są przechowywane i wykonywane lokalnie, czy też przechowywane i wykonywane zdalnie. Teoria głosi, że to upraszcza programowanie.

Rzeczywistość jest inna, ponieważ istnieje OGROMNA różnica między wykonywaniem lokalnego wywołania funkcji (nawet jeśli używasz najwolniejszego języka interpretowanego na świecie) a:

  • wywołanie przez obiekt proxy
  • serializowanie parametrów
  • nawiązanie połączenia sieciowego (jeśli jeszcze nie zostało nawiązane)
  • przesyłanie danych do zdalnego proxy
  • posiadanie zdalnego serwera proxy w celu przywrócenia danych i wywołania funkcji zdalnej w Twoim imieniu
  • serializacja wartości zwracanych
  • przesyłanie zwracanych wartości do lokalnego serwera proxy
  • ponowne złożenie zserializowanych danych
  • zwracanie odpowiedzi z funkcji zdalnej

W samym czasie to około trzech rzędów (lub więcej!) Różnicy wielkości. Te trzy + rzędy wielkości spowodują ogromną różnicę w wydajności, która sprawi, że abstrakcja wywołania procedury będzie dość oczywista, gdy po raz pierwszy błędnie potraktujesz RPC jako prawdziwe wywołanie funkcji. Co więcej, prawdziwe wywołanie funkcji, wykluczające poważne problemy w kodzie, będzie miało bardzo mało punktów awarii poza błędami implementacji. W przypadku wywołania RPC występują wszystkie następujące możliwe problemy, które będą traktowane jako przypadki awarii wykraczające poza to, czego można oczekiwać od zwykłego połączenia lokalnego:

  • utworzenie wystąpienia lokalnego serwera proxy może być niemożliwe
  • możesz nie być w stanie utworzyć wystąpienia zdalnego serwera proxy
  • serwery proxy mogą nie być w stanie się połączyć
  • wysyłane parametry mogą nie działać w nienaruszonym stanie lub w ogóle
  • wartość zwracana, którą wysyła zdalnie, może nie być nienaruszona lub w ogóle

Więc teraz twoje wywołanie RPC, które jest "tak jak wywołanie funkcji lokalnej" ma całą masę dodatkowych warunków awarii, z którymi nie musisz się zmagać podczas wykonywania lokalnych wywołań funkcji. Abstrakcja ponownie wyciekła, jeszcze mocniej.

Ostatecznie RPC jest złą abstrakcją, ponieważ przecieka jak sito na każdym poziomie - zarówno w przypadku sukcesu, jak i niepowodzenia obu.

TYLKO MOJA poprawna OPINIA
źródło
<pimp> Bardziej podoba mi się podejście Erlanga, ponieważ nie próbuje ono ukryć różnicy między wywołaniem funkcji a wysłaniem komunikatu do procesu do tego stopnia, że ​​oba używają bardzo różnej składni. A zdalne wysłanie komunikatu procesu bardzo wyraźnie różni się od lokalnego wysłania procesu, aczkolwiek używa tej samej ogólnej składni. </pimp>
TYLKO MOJA poprawna OPINIA
2
Cóż, jest to jedyna odpowiedź, która faktycznie daje dobry przykład (czytanie ze zrozumieniem, ludzie), więc dostaje moje +1.
Mark E. Haase
4

Przykład w przykładzie Django ORM wiele do wielu :

Zwróć uwagę, że w sekcji Przykładowe użycie interfejsu API należy .save () bazowy obiekt Artykułu a1, zanim będzie można dodać obiekty publikacji do atrybutu wiele do wielu. Zauważ, że aktualizacja atrybutu wiele do wielu powoduje natychmiastowe zapisanie w bazowej bazie danych, podczas gdy aktualizacja pojedynczego atrybutu nie jest odzwierciedlana w bazie danych, dopóki nie zostanie wywołana .save ().

Abstrakcji jest to, że pracujemy z grafem obiektów, w którym atrybuty jednowartościowe i atrybuty wielowartościowe są tylko atrybutami. Jednak implementacja jako relacyjna baza danych oparta na magazynie danych przecieka ... ponieważ system integralności RDBS pojawia się przez cienką warstwę interfejsu obiektowego.

hash1baby
źródło
3

Co to jest abstrakcja?

Abstrakcja to sposób na uproszczenie świata. Oznacza to, że nie musisz się martwić o to, co faktycznie dzieje się pod maską lub za zasłoną. To znaczy, że coś jest idiotyczne.

Przykład abstrakcji: Złożoność latania 737/747 jest „wyabstrahowana”

Samoloty to bardzo skomplikowane maszyny. Masz silniki odrzutowe, systemy tlenowe, systemy elektryczne, systemy podwozia itp., Ale pilot nie musi martwić się o zawiłości silnika odrzutowego… wszystko to jest „oderwane”. Oznacza to, że pilot musi się tylko martwić o sterowanie samolotem: w lewo, aby skręcić w lewo, w prawo, aby skręcić w prawo, podciągnij się, aby uzyskać elewację i naciśnij w dół, aby zejść.

To dość proste… właściwie skłamałem: sterowanie kierownicą jest trochę bardziej skomplikowane. W idealnym świecie to jedyna rzecz, o którą powinien się martwić pilot . Ale tak nie jest w prawdziwym życiu: jeśli latasz samolotem jak małpa, bez prawdziwego zrozumienia, jak działa samolot, ani żadnych szczegółów implementacji, prawdopodobnie rozbijesz się i zabijesz wszystkich na pokładzie.

Dziurawe abstrakcje w przykładzie 737

W rzeczywistości pilot musi martwić się WIELE ważnych rzeczy - nie wszystko zostało wyabstrahowane: piloci muszą martwić się o prędkość wiatru, ciąg, kąty natarcia, paliwo, wysokość, problemy pogodowe, kąty zniżania i czy pilot zmierza we właściwym kierunku. Komputery mogą pomóc pilotowi w tych zadaniach, ale nie wszystko jest zautomatyzowane / uproszczone.

Np. jeśli pilot zbyt mocno podciągnie się na kolumnę - samolot będzie posłuszny, ale wtedy pilot będzie ryzykował przeciągnięcie samolotu, a po utknięciu bardzo trudno jest odzyskać nad nim kontrolę, zanim spadnie z powrotem na ziemię .

Innymi słowy, nie wystarczy pilotowi po prostu sterować kierownicą, nie wiedząc nic innego ......... nieeee ....... musi wiedzieć o podstawowych zagrożeniach i ograniczeniach samolotu zanim poleci, musi wiedzieć, jak działa samolot i jak leci; on musi znać szczegóły implementacji ..... musi wiedzieć, że zbyt mocne podciągnięcie doprowadzi do przeciągnięcia, albo że zbyt strome lądowanie zniszczy samolot.

Te rzeczy nie są oderwane. Wiele rzeczy jest wyabstrahowanych, ale nie wszystko. Pilot musi się tylko martwić o kolumnę kierownicy i być może o jedną lub dwie inne rzeczy. Abstrakcja jest „nieszczelna”.

Nieszczelne abstrakcje w kodzie

...... to jest to samo w twoim kodzie. Jeśli nie znasz podstawowych szczegółów implementacji, częściej niż nie, zapracujesz się w kąt.

Oto przykład w kodowaniu:

ORMy uciążają wiele kłopotów związanych z zapytaniami do bazy danych, ale jeśli kiedykolwiek zrobiłeś coś takiego:

User.all.each do |user|
   puts user.name # let's print each user's name
end

Wtedy zdasz sobie sprawę, że to dobry sposób na zabicie aplikacji, jeśli masz więcej niż kilka milionów użytkowników. Nie wszystko jest oderwane. Musisz wiedzieć, że dzwonienie User.allz 25 milionami użytkowników spowoduje wzrost zużycia pamięci i spowoduje problemy. Musisz znać kilka podstawowych szczegółów. Abstrakcja jest nieszczelna.

BKSpurgeon
źródło
0

Fakt, że w pewnym momencie , kierując się skalą i wykonaniem, będziesz musiał zapoznać się ze szczegółami implementacji twojego frameworka abstrakcji, aby zrozumieć, dlaczego zachowuje się tak, jak się zachowuje.

Na przykład rozważmy to SQLzapytanie:

SELECT id, first_name, last_name, age, subject FROM student_details;

I jego alternatywa:

SELECT * FROM student_details;

Teraz wyglądają jak logicznie równoważne rozwiązania, ale wydajność pierwszego z nich jest lepsza dzięki specyfikacji nazw poszczególnych kolumn.

To trywialny przykład, ale ostatecznie wraca do cytatu Joela Spolsky'ego:

Wszystkie nietrywialne abstrakcje są do pewnego stopnia nieszczelne.

W pewnym momencie, gdy osiągniesz określoną skalę w swojej działalności, będziesz chciał zoptymalizować sposób działania bazy danych (SQL). Aby to zrobić, musisz wiedzieć, jak działają relacyjne bazy danych. Na początku było to dla ciebie abstrakcyjne, ale jest nieszczelne. W pewnym momencie musisz się tego nauczyć.

Jasio
źródło
-1

Załóżmy, że w bibliotece mamy następujący kod:

Object[] fetchDeviceColorAndModel(String serialNumberOfDevice)
{
    //fetch Device Color and Device Model from DB.
    //create new Object[] and set 0th field with color and 1st field with model value. 
}

Kiedy konsument wywołuje API, otrzymuje Object []. Konsument musi zrozumieć, że pierwsze pole tablicy obiektów ma wartość koloru, a drugie pole to wartość modelu. Tutaj abstrakcja wyciekła z biblioteki do kodu konsumenta.

Jednym z rozwiązań jest zwrócenie obiektu, który zawiera model i kolor urządzenia. Konsument może wywołać ten obiekt, aby uzyskać model i wartość koloru.

DeviceColorAndModel fetchDeviceColorAndModel(String serialNumberOfTheDevice)
{
    //fetch Device Color and Device Model from DB.
    return new DeviceColorAndModel(color, model);
}
Niranjan R
źródło
-3

Przeciekająca abstrakcja dotyczy stanu hermetyzacji. bardzo prosty przykład nieszczelnej abstrakcji:

$currentTime = new DateTime();

$bankAccount1->setLastRefresh($currentTime);
$bankAccount2->setLastRefresh($currentTime);
$currentTime->setTimestamp($aTimestamp);

class BankAccount {
    // ...

    public function setLastRefresh(DateTimeImmutable $lastRefresh)
    {
        $this->lastRefresh = $lastRefresh;
    } }

i we właściwy sposób (nie przeciekająca abstrakcja):

class BankAccount
{
    // ...

    public function setLastRefresh(DateTime $lastRefresh)
    {
        $this->lastRefresh = clone $lastRefresh;
    }
}

więcej opisu tutaj .

Alireza Rahmani Khalili
źródło