Was ist falsch an der Datenvalidierung und was hat das Liskov-Substitutionsprinzip damit zu tun?



Wenn Sie sich manchmal die Frage stellen: "Ist bei dieser Methode alles gut für mich?" Und wählen Sie zwischen "Was passiert, wenn es durchbrennt?" Und "Es ist besser, nur für den Fall zu überprüfen".

Korrektur: Wie lorc und 0xd34df00d angemerkt haben , wird das, was unten diskutiert wird, abhängige Typen genannt. Sie können hier darüber lesen. Nun, unten ist der Quelltext mit meinen Gedanken dazu.

Während der Entwicklung muss häufig die Gültigkeit von Daten für einen Algorithmus überprüft werden. Formal kann dies wie folgt beschrieben werden: Nehmen wir an, wir erhalten eine bestimmte Datenstruktur, überprüfen deren Wert auf Einhaltung eines bestimmten Bereichs zulässiger Werte (ODZ) und leiten ihn weiter. Anschließend kann dieselbe Datenstruktur derselben Überprüfung unterzogen werden. Wenn die Struktur unverändert bleibt, ist es offensichtlich unnötig, ihre Gültigkeit zu überprüfen.

Obwohl die Validierung sehr lange dauern kann, liegt das Problem nicht nur in der Leistung. Viel unangenehmer ist die übermäßige Verantwortung. Der Entwickler ist sich nicht sicher, ob es notwendig ist, die Struktur erneut auf Gültigkeit zu überprüfen. Wir können im Gegenteil davon ausgehen, dass keine Überprüfung stattgefunden hat und dass die Struktur zu einem früheren Zeitpunkt überprüft wurde.

Daher sind Fehlfunktionen in Methoden zulässig, die eine bewährte Struktur erwarten und mit einer Struktur, deren Wert außerhalb eines bestimmten Bereichs akzeptabler Werte liegt, nicht ordnungsgemäß funktionieren.

Darin liegt ein nicht offensichtliches tieferes Problem. Tatsächlich ist eine gültige Datenstruktur ein Untertyp der ursprünglichen Struktur. Aus dieser Sicht entspricht das Problem mit einer Methode, die nur gültige Objekte akzeptiert, dem folgenden Code in einer fiktiven Sprache:

class Parent { ... } class Child : Parent { ... } ... void processValidObject(Parent parent) { if (parent is Child) { // process } else { // error } } 

Stimmen Sie zu, dass das Problem jetzt viel klarer ist. Vor uns liegt eine kanonische Verletzung des Liskov-Substitutionsprinzips. Lesen Sie hier zum Beispiel, warum es schlecht ist, gegen das Substitutionsprinzip zu verstoßen.

Das Problem der Übertragung ungültiger Objekte kann gelöst werden, indem ein Subtyp für die ursprüngliche Datenstruktur erstellt wird. Beispielsweise können Sie Objekte über eine Factory erstellen, die je nach ursprünglicher Struktur entweder ein gültiges Untertypobjekt oder null zurückgibt. Wenn wir die Signatur von Methoden ändern, die eine gültige Struktur erwarten, sodass sie nur einen Subtyp akzeptieren, verschwindet das Problem. Neben der Gewissheit, dass das System genau funktioniert, nimmt die Anzahl der Überprüfungen pro Quadratzentimeter Code ab. Ein weiteres Plus ist, dass wir bei solchen Aktionen die Verantwortung für die Validierung der Daten vom Entwickler auf den Compiler verlagern.

In Swift ist auf der Syntaxebene das Problem der Überprüfung auf Null behoben. Die Idee ist, Typen in nullfähige und nicht nullfähige Typen zu unterteilen. Gleichzeitig wird es in Form von Zucker hergestellt, so dass der Programmierer keinen neuen Typ deklarieren muss. Beim Deklarieren eines Variablentyps garantiert ClassName, dass die Variable ungleich Null ist, und beim Deklarieren von ClassName? Die Variable ist null. Gleichzeitig besteht zwischen den Typen eine Kovarianz. Sie können also ein Objekt vom Typ ClassName an Methoden übergeben, die ClassName? Akzeptieren.

Diese Idee kann zu benutzerdefiniertem DLD erweitert werden. Das Versehen von Objekten mit Metadaten, die DLD enthalten und in dem Typ gespeichert sind, beseitigt die oben beschriebenen Probleme. Es wäre schön, Unterstützung für ein solches Tool in einer Sprache zu erhalten, aber dieses Verhalten wird auch in "normalen" OO-Sprachen wie Java oder C # mithilfe von Vererbung und einer Factory implementiert.

Die Situation mit der Datenvalidierung ist eine weitere Bestätigung dafür, dass Entitäten in der OOP nicht aus der realen Welt stammen, sondern aus den technischen Anforderungen.

UPD: Wie in den Kommentaren richtig angegeben, lohnt es sich, Subtypen nur dann zu erstellen, wenn wir zusätzliche Zuverlässigkeit erhalten und die Anzahl identischer Validierungen reduzieren.

Außerdem fehlt dem Artikel ein Beispiel. Lassen Sie einige Dateipfade am Eingang zu uns kommen. Unser System funktioniert in einigen Fällen mit allen Dateien und in einigen Fällen nur mit Dateien, auf die wir Zugriff haben. Als nächstes wollen wir sie auf verschiedene Subsysteme übertragen, die auch mit zugänglichen und nicht erreichbaren Dateien arbeiten. Außerdem übertragen diese Subsysteme Dateien noch weiter, wobei wiederum nicht klar ist, ob die Datei verfügbar ist oder nicht. So wird an jedem fragwürdigen Ort eine Zugangskontrolle erscheinen oder es kann im Gegenteil vergessen werden. Aus diesem Grund wird das System aufgrund weit verbreiteter Mehrdeutigkeiten und Überprüfungen komplizierter. Aber diese Prüfungen laden die Festplatte und sind in der Regel schwer. Sie können diese Prüfung in einem booleschen Feld zwischenspeichern, dies wird uns jedoch nicht von der Notwendigkeit einer Überprüfung abhalten. Ich schlage vor, die Verantwortung für die Überprüfung vom Entwickler auf den Compiler zu verlagern.

Source: https://habr.com/ru/post/de476220/


All Articles