Cosa significa il termine "Leaky Abstraction"? (Per favore, spiega con esempi. Spesso ho difficoltà a elaborare una semplice teoria.)
Cosa significa il termine "Leaky Abstraction"? (Per favore, spiega con esempi. Spesso ho difficoltà a elaborare una semplice teoria.)
Risposte:
Ecco un esempio di meatspace :
Le automobili hanno astrazioni per i conducenti. Nella sua forma più pura, c'è volante, acceleratore e freno. Questa astrazione nasconde molti dettagli su cosa c'è sotto il cofano: motore, camme, cinghia di distribuzione, candele, radiatore, ecc.
La cosa bella di questa astrazione è che possiamo sostituire parti dell'implementazione con parti migliorate senza riqualificare l'utente. Supponiamo di sostituire il tappo dello spinterogeno con l'accensione elettronica e di sostituire la camma fissa con una camma variabile. Queste modifiche migliorano le prestazioni ma l'utente sterza ancora con il volante e utilizza i pedali per avviare e arrestare.
In realtà è davvero notevole ... un ragazzo di 16 o 80 anni può azionare questo complicato macchinario senza sapere davvero molto su come funziona all'interno!
Ma ci sono fughe di notizie. La trasmissione è una piccola perdita. In un cambio automatico puoi sentire l'auto perdere potenza per un attimo mentre cambia marcia, mentre in CVT senti una coppia fluida fino in fondo.
Ci sono anche perdite più grandi. Se giri il motore troppo velocemente, potresti danneggiarlo. Se il blocco motore è troppo freddo, l'auto potrebbe non avviarsi o potrebbe avere prestazioni scadenti. E se accendi la radio, i fari e l'aria condizionata allo stesso tempo, vedrai il tuo chilometraggio del gas diminuire.
Significa semplicemente che la tua astrazione espone alcuni dettagli di implementazione o che devi essere consapevole dei dettagli di implementazione quando usi l'astrazione. Il termine è attribuito a Joel Spolsky , intorno al 2002. Vedi l' articolo di wikipedia per ulteriori informazioni.
Un classico esempio sono le librerie di rete che consentono di trattare i file remoti come locali. Lo sviluppatore che utilizza questa astrazione deve essere consapevole del fatto che i problemi di rete possono causare un errore in modi che non fanno i file locali. È quindi necessario sviluppare codice per gestire specificamente errori al di fuori dell'astrazione fornita dalla libreria di rete.
Wikipedia ha una definizione abbastanza buona per questo
Un'astrazione che perde si riferisce a qualsiasi astrazione implementata, intesa a ridurre (o nascondere) la complessità, in cui i dettagli sottostanti non sono completamente nascosti
In altre parole, per il software è quando è possibile osservare i dettagli di implementazione di una funzionalità tramite limitazioni o effetti collaterali nel programma.
Un rapido esempio potrebbe essere le chiusure di C # / VB.Net e la loro incapacità di acquisire i parametri ref / out. Il motivo per cui non possono essere acquisiti è dovuto a un dettaglio di implementazione di come avviene il processo di sollevamento. Questo non vuol dire però che ci sia un modo migliore per farlo.
Ecco un esempio familiare agli sviluppatori .NET: ASP.NET Page
classe tenta di nascondere i dettagli delle operazioni HTTP, in particolare la gestione dei dati del modulo, in modo che gli sviluppatori non debbano occuparsi dei valori inviati (perché mappa automaticamente i valori del modulo sul server controlli).
Ma se si va oltre gli scenari di utilizzo più elementari, l' Page
astrazione inizia a trapelare e diventa difficile lavorare con le pagine a meno che non si comprendano i dettagli di implementazione della classe.
Un esempio comune è l'aggiunta dinamica di controlli a una pagina: il valore dei controlli aggiunti dinamicamente non verrà mappato per te a meno che non vengano aggiunti al momento giusto : prima che il motore sottostante associ i valori del modulo in arrivo ai controlli appropriati. Quando devi imparare questo, l'astrazione è trapelata .
Ebbene, in un certo senso è una cosa puramente teorica, anche se non irrilevante.
Usiamo astrazioni per rendere le cose più facili da comprendere. Posso operare su una classe stringa in qualche lingua per nascondere il fatto che ho a che fare con un insieme ordinato di caratteri che sono elementi individuali. Ho a che fare con un insieme ordinato di caratteri per nascondere il fatto che ho a che fare con i numeri. Mi occupo di numeri per nascondere il fatto che ho a che fare con 1 e 0.
Un'astrazione che perde è quella che non nasconde i dettagli che intende nascondere. Se si chiama string.Length su una stringa di 5 caratteri in Java o .NET potrei ottenere qualsiasi risposta da 5 a 10, a causa dei dettagli di implementazione in cui ciò che quei linguaggi chiamano caratteri sono in realtà punti dati UTF-16 che possono rappresentare 1 o .5 di un personaggio. L'astrazione è trapelata. Non colare però significa che trovare la lunghezza richiederebbe più spazio di archiviazione (per memorizzare la lunghezza reale) o passare da O (1) a O (n) (per capire qual è la lunghezza reale). Se mi interessa la vera risposta (spesso non lo fai davvero) devi lavorare sulla conoscenza di ciò che sta realmente accadendo.
Casi più discutibili si verificano con casi come quelli in cui un metodo o una proprietà ti consente di entrare nel funzionamento interno, che si tratti di perdite di astrazione o di modi ben definiti per passare a un livello di astrazione inferiore, a volte può essere una questione su cui le persone non sono d'accordo.
Continuerò a fornire esempi utilizzando RPC.
Nel mondo ideale di RPC, una chiamata di procedura remota dovrebbe apparire come una chiamata di procedura locale (o almeno così va la storia). Dovrebbe essere completamente trasparente per il programmatore in modo tale che quando chiamano SomeObject.someFunction()
non hanno idea se SomeObject
(o solo someFunction
per quella materia) sono archiviati ed eseguiti localmente o memorizzati ed eseguiti in remoto. La teoria dice che questo rende la programmazione più semplice.
La realtà è diversa perché c'è un'ENORME differenza tra fare una chiamata di funzione locale (anche se stai usando il linguaggio interpretato più lento del mondo) e:
Solo nel tempo si tratta di circa tre ordini (o più!) Di differenza di grandezza. Quei tre + ordini di grandezza faranno un'enorme differenza nelle prestazioni che renderà la tua astrazione di una chiamata di procedura trapelata piuttosto ovviamente la prima volta che consideri erroneamente un RPC come una vera chiamata di funzione. Inoltre una vera chiamata di funzione, salvo seri problemi nel codice, avrà pochissimi punti di errore al di fuori dei bug di implementazione. Una chiamata RPC presenta tutti i seguenti possibili problemi che verranno descritti come casi di errore oltre a quanto ci si aspetterebbe da una normale chiamata locale:
Quindi ora la tua chiamata RPC che è "proprio come una chiamata di funzione locale" ha un intero buttload di condizioni di errore extra con cui non devi fare i conti con le chiamate di funzione locali. L'astrazione è trapelata di nuovo, ancora più difficile.
Alla fine RPC è una cattiva astrazione perché perde come un setaccio ad ogni livello - quando ha successo e quando fallisce entrambi.
Un esempio nell'esempio molti-a-molti di django ORM :
Notare nell'utilizzo dell'API di esempio che è necessario salvare () l'oggetto Article di base a1 prima di poter aggiungere oggetti Publication all'attributo many-to-many. E si noti che l'aggiornamento dell'attributo molti-a-molti salva immediatamente nel database sottostante, mentre l'aggiornamento di un attributo singolare non si riflette nel db fino a quando non viene chiamato .save ().
L'astrazione è che stiamo lavorando con un oggetto grafico, dove gli attributi a valore singolo e gli attributi a più valori sono solo attributi. Ma l'implementazione come archivio dati supportato da un database relazionale perde ... poiché il sistema di integrità dell'RDBS appare attraverso la sottile patina di un'interfaccia a oggetti.
L'astrazione è un modo per semplificare il mondo. Significa che non devi preoccuparti di ciò che sta realmente accadendo sotto il cofano o dietro le quinte. Significa che qualcosa è a prova di idiota.
Gli aerei sono macchinari molto complicati. Hai motori a reazione, sistemi di ossigeno, sistemi elettrici, sistemi di carrelli di atterraggio ecc. Ma il pilota non deve preoccuparsi delle complessità del motore a reazione ... tutto ciò che è "estratto". Ciò significa che il pilota deve solo preoccuparsi di guidare l'aereo: a sinistra per andare a sinistra e a destra per andare a destra, tirare su per guadagnare quota e spingere verso il basso per scendere.
È abbastanza semplice ... in realtà ho mentito: controllare il volante è un po 'più complicato. In un mondo ideale, questa è l'unica cosa di cui il pilota dovrebbe essere preoccupato. Ma questo non è il caso nella vita reale: se voli su un aereo come una scimmia, senza alcuna reale comprensione di come funziona un aereo, o di nessuno dei dettagli di implementazione, allora probabilmente schianterai e ucciderai tutti a bordo.
In realtà, un pilota deve preoccuparsi di MOLTE cose importanti - non tutto è stato astratto: i piloti devono preoccuparsi della velocità del vento, della spinta, degli angoli di attacco, del carburante, dell'altitudine, dei problemi meteorologici, degli angoli di discesa e se il pilota sta andando nella giusta direzione. I computer possono aiutare il pilota in queste attività, ma non tutto è automatizzato / semplificato.
Ad esempio, se il pilota si solleva troppo forte sulla colonna, l'aereo obbedirà, ma poi il pilota rischierà di fermare l'aereo e, una volta bloccato, è molto difficile riprenderne il controllo, prima che si schianti di nuovo a terra .
In altre parole, non è sufficiente che il pilota controlli semplicemente il volante senza sapere altro ......... nooooo ....... deve conoscere i rischi e i limiti sottostanti dell'aereo prima di farne volare uno ....... deve sapere come funziona l'aereo e come vola l'aereo; deve conoscere i dettagli di implementazione ..... deve sapere che tirare su con troppa forza porterà ad uno stallo, o che un atterraggio troppo ripido distruggerà l'aereo.
Quelle cose non vengono astratte. Molte cose vengono astratte, ma non tutto. Il pilota deve preoccuparsi solo del piantone dello sterzo e forse di una o due altre cose. L'astrazione è "che perde".
...... è la stessa cosa nel tuo codice. Se non conosci i dettagli di implementazione sottostanti, il più delle volte ti ritroverai in un angolo.
Ecco un esempio di codifica:
Gli ORM astraggono gran parte della seccatura nel gestire le query sul database, ma se hai mai fatto qualcosa come:
User.all.each do |user|
puts user.name # let's print each user's name
end
Allora ti renderai conto che è un bel modo per uccidere la tua app se hai più di un paio di milioni di utenti. Non tutto è astratto. Devi sapere che le chiamate User.all
con 25 milioni di utenti aumenteranno l'utilizzo della memoria e causeranno problemi. Hai bisogno di conoscere alcuni dettagli di fondo. L'astrazione perde.
Il fatto che a un certo punto , che sarà guidato dalla tua scala e dalla tua esecuzione, sarà necessario acquisire familiarità con i dettagli di implementazione del tuo framework di astrazione per capire perché si comporta in quel modo.
Ad esempio, considera questa SQL
query:
SELECT id, first_name, last_name, age, subject FROM student_details;
E la sua alternativa:
SELECT * FROM student_details;
Ora, sembrano soluzioni logicamente equivalenti, ma le prestazioni della prima sono migliori grazie alla specifica dei nomi delle singole colonne.
È un esempio banale, ma alla fine torna alla citazione di Joel Spolsky:
Tutte le astrazioni non banali, in una certa misura, sono falle.
Ad un certo punto, quando raggiungerai una certa scala nella tua operazione, vorrai ottimizzare il modo in cui funziona il tuo DB (SQL). Per farlo, dovrai conoscere il modo in cui funzionano i database relazionali. All'inizio ti è stato astratto, ma perde. Devi impararlo ad un certo punto.
Supponiamo di avere il seguente codice in una libreria:
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.
}
Quando il consumatore chiama l'API, ottiene un oggetto []. Il consumatore deve capire che il primo campo della matrice di oggetti ha il valore del colore e il secondo campo è il valore del modello. Qui l'astrazione è trapelata dalla libreria al codice consumer.
Una delle soluzioni è restituire un oggetto che incapsula il modello e il colore del dispositivo. Il consumatore può chiamare quell'oggetto per ottenere il modello e il valore del colore.
DeviceColorAndModel fetchDeviceColorAndModel(String serialNumberOfTheDevice)
{
//fetch Device Color and Device Model from DB.
return new DeviceColorAndModel(color, model);
}
L'astrazione che perde è tutta incapsulata nello stato. esempio molto semplice di astrazione leaky:
$currentTime = new DateTime();
$bankAccount1->setLastRefresh($currentTime);
$bankAccount2->setLastRefresh($currentTime);
$currentTime->setTimestamp($aTimestamp);
class BankAccount {
// ...
public function setLastRefresh(DateTimeImmutable $lastRefresh)
{
$this->lastRefresh = $lastRefresh;
} }
e il modo giusto (astrazione non leaky):
class BankAccount
{
// ...
public function setLastRefresh(DateTime $lastRefresh)
{
$this->lastRefresh = clone $lastRefresh;
}
}
più descrizione qui .