Co oznacza termin „nieszczelna abstrakcja”? (Proszę wyjaśnić na przykładach. Często mam trudności z grzebaniem w zwykłej teorii).
Co oznacza termin „nieszczelna abstrakcja”? (Proszę wyjaśnić na przykładach. Często mam trudności z grzebaniem w zwykłej teorii).
Odpowiedzi:
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.
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ą.
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.
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, Page
abstrakcja 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 .
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ą.
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 someFunction
o 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:
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:
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.
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.
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.
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.
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”.
...... 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.all
z 25 milionami użytkowników spowoduje wzrost zużycia pamięci i spowoduje problemy. Musisz znać kilka podstawowych szczegółów. Abstrakcja jest nieszczelna.
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 SQL
zapytanie:
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ć.
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);
}
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 .