Was bedeutet der Begriff "Leaky Abstraction"? (Bitte erläutern Sie dies anhand von Beispielen. Es fällt mir oft schwer, eine bloße Theorie zu verstehen.)
Was bedeutet der Begriff "Leaky Abstraction"? (Bitte erläutern Sie dies anhand von Beispielen. Es fällt mir oft schwer, eine bloße Theorie zu verstehen.)
Antworten:
Hier ist ein Meatspace- Beispiel:
Autos haben Abstraktionen für Fahrer. In seiner reinsten Form gibt es ein Lenkrad, ein Gaspedal und eine Bremse. Diese Abstraktion verbirgt viele Details darüber, was sich unter der Motorhaube befindet: Motor, Nocken, Zahnriemen, Zündkerzen, Kühler usw.
Das Schöne an dieser Abstraktion ist, dass wir Teile der Implementierung durch verbesserte Teile ersetzen können, ohne den Benutzer neu zu schulen. Angenommen, wir ersetzen die Verteilerkappe durch eine elektronische Zündung und den festen Nocken durch einen variablen Nocken. Diese Änderungen verbessern die Leistung, aber der Benutzer lenkt immer noch mit dem Rad und verwendet die Pedale zum Starten und Stoppen.
Es ist eigentlich ziemlich bemerkenswert ... ein 16-jähriger oder ein 80-jähriger kann diese komplizierte Maschine bedienen, ohne wirklich viel darüber zu wissen, wie sie im Inneren funktioniert!
Aber es gibt Lecks. Das Getriebe ist ein kleines Leck. In einem Automatikgetriebe spürt man, wie das Auto beim Schalten für einen Moment an Leistung verliert, während man im CVT ein gleichmäßiges Drehmoment spürt.
Es gibt auch größere Lecks. Wenn Sie den Motor zu schnell drehen, können Sie ihn beschädigen. Wenn der Motorblock zu kalt ist, startet das Auto möglicherweise nicht oder hat eine schlechte Leistung. Und wenn Sie gleichzeitig Radio, Scheinwerfer und Klimaanlage ankurbeln, sinkt Ihr Benzinverbrauch.
Dies bedeutet einfach, dass Ihre Abstraktion einige der Implementierungsdetails verfügbar macht oder dass Sie die Implementierungsdetails kennen müssen, wenn Sie die Abstraktion verwenden. Der Begriff wird Joel Spolsky , circa 2002, zugeschrieben. Weitere Informationen finden Sie im Wikipedia- Artikel .
Ein klassisches Beispiel sind Netzwerkbibliotheken, mit denen Sie entfernte Dateien als lokal behandeln können. Der Entwickler, der diese Abstraktion verwendet, muss sich bewusst sein, dass Netzwerkprobleme dazu führen können, dass dies auf eine Weise fehlschlägt, die lokale Dateien nicht tun. Sie müssen dann Code entwickeln, um bestimmte Fehler außerhalb der von der Netzwerkbibliothek bereitgestellten Abstraktion zu behandeln.
Wikipedia hat eine ziemlich gute Definition für diese
Eine undichte Abstraktion bezieht sich auf jede implementierte Abstraktion, die die Komplexität verringern (oder verbergen) soll, wenn die zugrunde liegenden Details nicht vollständig verborgen sind
Mit anderen Worten, für Software können Sie Implementierungsdetails einer Funktion über Einschränkungen oder Nebenwirkungen im Programm beobachten.
Ein schnelles Beispiel wären C # / VB.Net-Schließungen und ihre Unfähigkeit, Ref / Out-Parameter zu erfassen. Der Grund, warum sie nicht erfasst werden können, liegt in einem Implementierungsdetail, wie der Hebevorgang abläuft. Dies bedeutet jedoch nicht, dass es einen besseren Weg gibt, dies zu tun.
Hier ist ein Beispiel, das .NET-Entwicklern bekannt ist: Die ASP.NET- Page
Klasse versucht, die Details von HTTP-Vorgängen, insbesondere die Verwaltung von Formulardaten, auszublenden, damit Entwickler sich nicht mit veröffentlichten Werten befassen müssen (da Formulare automatisch dem Server zugeordnet werden Kontrollen).
Wenn Sie jedoch über die grundlegendsten Verwendungsszenarien hinausgehen, Page
beginnt die Abstraktion zu lecken und es wird schwierig, mit Seiten zu arbeiten, es sei denn, Sie verstehen die Implementierungsdetails der Klasse.
Ein häufiges Beispiel ist das dynamische Hinzufügen von Steuerelementen zu einer Seite. Der Wert von dynamisch hinzugefügten Steuerelementen wird nur dann für Sie zugeordnet, wenn Sie sie zum richtigen Zeitpunkt hinzufügen : Bevor die zugrunde liegende Engine die eingehenden Formularwerte den entsprechenden Steuerelementen zuordnet. Wenn Sie das lernen müssen, ist die Abstraktion durchgesickert .
In gewisser Weise ist es eine rein theoretische Sache, wenn auch nicht unwichtig.
Wir verwenden Abstraktionen, um das Verständnis zu erleichtern. Ich kann eine Zeichenfolgenklasse in einer bestimmten Sprache bearbeiten, um die Tatsache zu verbergen, dass es sich um einen geordneten Satz von Zeichen handelt, bei denen es sich um einzelne Elemente handelt. Ich beschäftige mich mit einem geordneten Zeichensatz, um die Tatsache zu verbergen, dass es sich um Zahlen handelt. Ich beschäftige mich mit Zahlen, um die Tatsache zu verbergen, dass ich mit Einsen und Nullen zu tun habe.
Eine undichte Abstraktion ist eine, die die Details, die sie verbergen soll, nicht verbirgt. Wenn Sie string.Length für eine 5-stellige Zeichenfolge in Java oder .NET aufrufen, kann ich aufgrund von Implementierungsdetails, bei denen es sich bei den von diesen Sprachen als Zeichen bezeichneten Zeichen tatsächlich um UTF-16-Datenpunkte handelt, die entweder 1 oder darstellen können, eine Antwort von 5 bis 10 erhalten .5 eines Charakters. Die Abstraktion ist durchgesickert. Wenn Sie es nicht verlieren, bedeutet dies, dass das Ermitteln der Länge entweder mehr Speicherplatz erfordert (um die tatsächliche Länge zu speichern) oder von O (1) zu O (n) wechselt (um die tatsächliche Länge zu ermitteln). Wenn mir die richtige Antwort wichtig ist (oft nicht wirklich), müssen Sie an dem Wissen arbeiten, was wirklich vor sich geht.
Weitere umstrittene Fälle treten in Fällen auf, in denen eine Methode oder Eigenschaft es Ihnen ermöglicht, in das Innenleben einzusteigen, ob es sich um Abstraktionslecks handelt oder ob es sich um genau definierte Wege handelt, um auf eine niedrigere Abstraktionsebene zu gelangen.
Ich werde weiterhin Beispiele mit RPC nennen.
In der idealen Welt von RPC sollte ein Remote-Prozeduraufruf wie ein lokaler Prozeduraufruf aussehen (so lautet die Geschichte). Es sollte für den Programmierer vollständig transparent sein, damit er beim Aufruf SomeObject.someFunction()
keine Ahnung hat, ob SomeObject
(oder nur someFunction
für diese Angelegenheit) lokal gespeichert und ausgeführt oder remote gespeichert und ausgeführt werden. Die Theorie besagt, dass dies die Programmierung vereinfacht.
Die Realität sieht anders aus, weil es einen RIESIGEN Unterschied gibt zwischen einem lokalen Funktionsaufruf (selbst wenn Sie die am langsamsten interpretierte Sprache der Welt verwenden) und:
Allein in der Zeit sind das drei Größenordnungen (oder mehr!) Der Größenunterschiede. Diese drei Größenordnungen + werden einen großen Unterschied in der Leistung bewirken, der Ihre Abstraktion eines Prozeduraufruflecks ziemlich offensichtlich macht, wenn Sie einen RPC zum ersten Mal fälschlicherweise als echten Funktionsaufruf behandeln. Darüber hinaus weist ein echter Funktionsaufruf, abgesehen von schwerwiegenden Problemen in Ihrem Code, nur sehr wenige Fehlerpunkte außerhalb von Implementierungsfehlern auf. Ein RPC-Anruf weist alle folgenden möglichen Probleme auf, die als Fehlerfälle auftreten und über das hinausgehen, was Sie von einem regulären Ortsgespräch erwarten würden:
Jetzt hat Ihr RPC-Aufruf, der "genau wie ein lokaler Funktionsaufruf" ist, eine ganze Reihe zusätzlicher Fehlerbedingungen, mit denen Sie sich bei lokalen Funktionsaufrufen nicht auseinandersetzen müssen. Die Abstraktion ist wieder durchgesickert, noch härter.
Am Ende ist RPC eine schlechte Abstraktion, weil es auf jeder Ebene wie ein Sieb leckt - wenn es erfolgreich ist und wenn beide fehlschlagen.
Ein Beispiel im Django ORM Viele-zu-Viele-Beispiel :
Beachten Sie in der Beispiel-API-Verwendung, dass Sie das Basisartikelobjekt a1 speichern müssen, bevor Sie dem Many-to-Many-Attribut Publikationsobjekte hinzufügen können. Beachten Sie, dass das Aktualisieren des Many-to-Many-Attributs sofort in der zugrunde liegenden Datenbank gespeichert wird, während das Aktualisieren eines einzelnen Attributs erst in der Datenbank angezeigt wird, wenn .save () aufgerufen wird.
Die Abstraktion besteht darin, dass wir mit einem Objektdiagramm arbeiten, bei dem Einzelwertattribute und Mehrwertattribute nur Attribute sind. Die Implementierung als relationaler datenbankgestützter Datenspeicher ist jedoch undicht ... da das Integritätssystem des RDBS durch das dünne Furnier einer Objektschnittstelle angezeigt wird.
Zunächst ist es am besten zu verstehen, was "Abstraktion" ist?
Abstraktion ist ein Weg, die Welt zu vereinfachen. Sie müssen sich also keine Gedanken darüber machen, was tatsächlich unter der Motorhaube / hinter dem Vorhang passiert. Es bedeutet, dass etwas idiotensicher ist. Ok, was bedeutet das? Dies lässt sich am besten anhand eines Beispiels veranschaulichen.
Beispiel für Abstraktion: Die Komplexität des Fliegens einer 737/747 wird "abstrahiert"
Nehmen wir das Beispiel eines Boeing-Passagierflugzeugs. Diese Flugzeuge sind sehr komplizierte Maschinen. Sie haben Düsentriebwerke, Sauerstoffsysteme, elektrische Systeme, Fahrwerkssysteme usw., aber der Pilot muss sich nicht um die Feinheiten des Düsentriebwerks kümmern ... alles, was "weg abstrahiert" ist, was bedeutet: am Ende von An diesem Tag macht sich ein Pilot nur Sorgen um das Rad und eine Kontrollsäule, um das Flugzeug zu steuern. Links nach links und rechts nach rechts, nach oben ziehen, um die Höhe zu erreichen, und nach unten drücken, um abzusteigen. Es ist einfach genug ...... eigentlich habe ich gelogen: Die Steuerung des Lenkrads ist etwas komplizierter. In einer idealen Welt ist das das einzige, was er solltemach dir Sorgen. Im wirklichen Leben ist dies jedoch nicht der Fall: Wenn Sie ein Flugzeug wie einen Affen fliegen, ohne wirklich zu verstehen, wie ein Flugzeug funktioniert oder welche Implementierungsdetails vorliegen, werden Sie wahrscheinlich alle an Bord abstürzen und töten.
In Wirklichkeit muss sich ein Pilot um eine Menge wichtiger Dinge kümmern - nicht alles wurde weg abstrahiert: Piloten müssen sich um Windgeschwindigkeit, Schub, Anstellwinkel, Treibstoff, Höhe, Wetterprobleme, Abstiegswinkel kümmern, ob die Der Pilot geht in die richtige Richtung, wo sich das Flugzeug gerade befindet und so weiter. Computer können dem Piloten bei diesen Aufgaben helfen, aber nicht alles ist automatisiert / vereinfacht.
Beispiel: Wenn der Pilot zu stark an der Säule vorfährt, gehorcht das Flugzeug, aber dann riskiert der Pilot, das Flugzeug zum Stillstand zu bringen. Wenn Sie das Flugzeug zum Stillstand bringen, ist es möglicherweise schwierig, die Kontrolle wiederzugewinnen, bevor es wieder auf den Boden stürzt .
Mit anderen Worten, es reicht nicht aus, dass der Pilot einfach das Lenkrad steuert, ohne etwas anderes zu wissen ......... nooooo ....... sie muss über die zugrunde liegenden Risiken und Einschränkungen des Flugzeugs Bescheid wissen bevor er einen fliegt ....... muss sie wissen, wie das Flugzeug funktioniert und wie das Flugzeug fliegt; Er muss die Implementierungsdetails kennen. Sie muss wissen, dass ein zu starkes Hochziehen zu einem Stillstand führt oder dass eine zu steile Landung das Flugzeug zerstört.
Diese Dinge werden nicht weg abstrahiert. Viele Dinge werden weg abstrahiert, aber nicht alles. Der Pilot muss sich nur um die Lenksäule und vielleicht ein oder zwei andere Dinge kümmern. Die Abstraktion ist "undicht".
...... es ist dasselbe in deinem Code. Wenn Sie die zugrunde liegenden Implementierungsdetails nicht kennen, arbeiten Sie sich meistens selbst in eine Ecke.
Hier ist ein Beispiel für die Codierung:
ORMs abstrahieren viel Ärger beim Umgang mit Datenbankabfragen, aber wenn Sie jemals etwas getan haben wie:
User.all.each do |user|
puts user.name # let's print each user's name
end
Dann werden Sie feststellen, dass dies eine gute Möglichkeit ist, Ihre App zu beenden, wenn Sie mehr als ein paar Millionen Benutzer haben. Nicht alles wird weg abstrahiert. Sie müssen wissen, dass Anrufe User.all
mit 25 Millionen Benutzern Ihre Speichernutzung erhöhen und Probleme verursachen werden. Sie müssen einige zugrunde liegende Details kennen. Die Abstraktion ist undicht.
Die Tatsache, dass Sie sich irgendwann , abhängig von Ihrer Größe und Ausführung, mit den Implementierungsdetails Ihres Abstraktionsframeworks vertraut machen müssen, um zu verstehen, warum es sich so verhält, wie es sich verhält.
Betrachten Sie beispielsweise diese SQL
Abfrage:
SELECT id, first_name, last_name, age, subject FROM student_details;
Und seine Alternative:
SELECT * FROM student_details;
Jetzt sehen sie wie logisch äquivalente Lösungen aus, aber die Leistung der ersten Lösung ist aufgrund der Angabe der einzelnen Spaltennamen besser.
Es ist ein triviales Beispiel, aber schließlich kommt es auf Joel Spolsky Zitat zurück:
Alle nicht trivialen Abstraktionen sind bis zu einem gewissen Grad undicht.
Wenn Sie irgendwann einen bestimmten Maßstab in Ihrem Betrieb erreichen, möchten Sie die Funktionsweise Ihrer Datenbank (SQL) optimieren. Dazu müssen Sie wissen, wie relationale Datenbanken funktionieren. Es wurde Ihnen am Anfang abstrahiert, aber es ist undicht. Sie müssen es irgendwann lernen.
Angenommen, wir haben den folgenden Code in einer Bibliothek:
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.
}
Wenn der Verbraucher die API aufruft, erhält er ein Objekt []. Der Verbraucher muss verstehen, dass das erste Feld des Objektarrays einen Farbwert und das zweite Feld den Modellwert hat. Hier ist die Abstraktion von der Bibliothek zum Consumer-Code durchgesickert.
Eine der Lösungen besteht darin, ein Objekt zurückzugeben, das Modell und Farbe des Geräts kapselt. Der Verbraucher kann dieses Objekt aufrufen, um das Modell und den Farbwert abzurufen.
DeviceColorAndModel fetchDeviceColorAndModel(String serialNumberOfTheDevice)
{
//fetch Device Color and Device Model from DB.
return new DeviceColorAndModel(color, model);
}
Bei einer undichten Abstraktion geht es darum, den Zustand zu kapseln. sehr einfaches Beispiel für eine undichte Abstraktion:
$currentTime = new DateTime();
$bankAccount1->setLastRefresh($currentTime);
$bankAccount2->setLastRefresh($currentTime);
$currentTime->setTimestamp($aTimestamp);
class BankAccount {
// ...
public function setLastRefresh(DateTimeImmutable $lastRefresh)
{
$this->lastRefresh = $lastRefresh;
} }
und der richtige Weg (keine undichte Abstraktion):
class BankAccount
{
// ...
public function setLastRefresh(DateTime $lastRefresh)
{
$this->lastRefresh = clone $lastRefresh;
}
}
Weitere Beschreibung hier .