Verwenden der statischen Typprüfung zum Schutz vor Geschäftsfehlern

13

Ich bin ein großer Fan der statischen Typprüfung. Es verhindert, dass Sie dumme Fehler wie diese machen:

// java code
Adult a = new Adult();
a.setAge("Roger"); //static type checker would complain
a.setName(42); //and here too

Aber es hindert dich nicht daran, so dumme Fehler zu machen:

Adult a = new Adult();
// obviously you've mixed up these fields, but type checker won't complain
a.setAge(150); // nobody's ever lived this old
a.setWeight(42); // a 42lb adult would have serious health issues

Das Problem tritt auf, wenn Sie denselben Typ verwenden, um offensichtlich unterschiedliche Arten von Informationen darzustellen. Ich dachte, eine gute Lösung für dieses Problem wäre, die IntegerKlasse zu erweitern , nur um Fehler in der Geschäftslogik zu vermeiden, aber keine Funktionen hinzuzufügen. Beispielsweise:

class Age extends Integer{};
class Pounds extends Integer{};

class Adult{
    ...
    public void setAge(Age age){..}
    public void setWeight(Pounds pounds){...}
}

Adult a = new Adult();
a.setAge(new Age(42));
a.setWeight(new Pounds(150));

Wird dies als gute Praxis angesehen? Oder gibt es später unvorhergesehene technische Probleme mit einem derart restriktiven Design?

J-Bob
quelle
5
a.SetAge( new Age(150) )Kompilieren Sie noch nicht ?
John Wu
1
Der Int-Typ von Java hat einen festen Bereich. Natürlich möchten Sie Ganzzahlen mit benutzerdefinierten Bereichen haben, z. B. Ganzzahl <18, 110>. Sie suchen nach Verfeinerungstypen oder abhängigen Typen, die einige (Nicht-Mainstream-) Sprachen anbieten.
Theodoros Chatzigiannakis
3
Worüber Sie sprechen, ist eine großartige Möglichkeit, um zu entwerfen! Leider hat Java ein so primitives Typsystem, dass es schwierig ist, es richtig zu machen. Und selbst wenn Sie es versuchen, kann es zu Nebenwirkungen wie geringerer Leistung oder geringerer Entwicklerproduktivität kommen.
Euphoric
@JohnWu ja dein Beispiel wird noch kompiliert, aber das ist die einzige Fehlerquelle für diese Logik. Sobald Sie Ihr new Age(...)Objekt deklariert haben, können Sie es an Weightkeiner anderen Stelle einer Variablen vom Typ zuordnen . Es reduziert die Anzahl der Stellen, an denen Fehler auftreten können.
J-Bob

Antworten:

12

Sie fragen im Wesentlichen nach einem Einheitensystem (nein, keine Einheitentests, "Einheit" wie in "physikalische Einheit", wie Meter, Volt usw.).

In Ihrem Code Agerepräsentiert Zeit und Poundsrepräsentiert Masse. Dies führt zu Dingen wie Einheitenumrechnung, Basiseinheiten, Präzision usw.


Es gab / gibt Versuche, so etwas in Java zu bekommen, zum Beispiel:

Die beiden späteren scheinen in dieser Github-Sache zu leben: https://github.com/unitsofmeasurement


C ++ hat Einheiten über Boost


LabView wird mit einer Reihe von Einheiten geliefert .


Es gibt andere Beispiele in anderen Sprachen. (Änderungen sind willkommen)


Wird dies als gute Praxis angesehen?

Wie Sie oben sehen können, unterstützt eine Sprache umso mehr Einheiten, je wahrscheinlicher die Verwendung von Werten mit Einheiten ist. LabView wird häufig zur Interaktion mit Messgeräten verwendet. Daher ist es sinnvoll, eine solche Funktion in der Sprache zu haben, und die Verwendung dieser Funktion wird mit Sicherheit als gute Praxis angesehen.

Aber in jeder Hochsprache für allgemeine Zwecke, in der die Nachfrage nach einem solchen Maß an Strenge gering ist, ist dies wahrscheinlich unerwartet.

Oder gibt es später unvorhergesehene technische Probleme mit einem derart restriktiven Design?

Meine Vermutung wäre: Leistung / Gedächtnis. Wenn Sie mit einer Menge von Werten befassen, der Overhead eines Objekts pro Wert könnte ein Problem werden. Aber wie immer: Vorzeitige Optimierung ist die Wurzel allen Übels .

Ich denke, das größere "Problem" ist, dass sich die Leute daran gewöhnen, da die Einheit normalerweise implizit wie folgt definiert ist:

class Adult
{
    ...
    public void setAge(int ageInYears){..}

Menschen werden verwirrt sein, wenn sie ein Objekt als Wert für etwas übergeben müssen, das scheinbar mit einem Einfachen beschrieben werden könnte int, wenn sie nicht mit Einheitensystemen vertraut sind.

Null
quelle
"Die Leute werden verwirrt sein, wenn sie ein Objekt als Wert für etwas übergeben müssen, das scheinbar mit einem einfachen int... beschrieben werden könnte." Guter Fang.
Greg Burghardt
1
Ich denke, benutzerdefinierte Bereiche verschieben dies aus dem Bereich einer Einheitenbibliothek in die abhängigen Typen
jk.
6

Im Gegensatz zur Antwort von Null kann das Definieren eines Typs für eine "Einheit" von Vorteil sein, wenn eine Ganzzahl nicht ausreicht, um die Messung zu beschreiben. Beispielsweise wird das Gewicht häufig in mehreren Einheiten innerhalb desselben Messsystems gemessen. Denken Sie an "Pfund" und "Unzen" oder "Kilogramm" und "Gramm".

Wenn Sie eine detailliertere Messebene benötigen, ist es hilfreich, einen Typ für die Einheit zu definieren:

public struct Weight {
    private int pounds;
    private int ounces;

    public Weight(int pounds, int ounces) {
        // Value range checks go here
        // Throw exception if ounces is greater than 16?
    }

    // Getters go here
}

Für etwas wie "Alter" empfehle ich, das zur Laufzeit basierend auf dem Geburtsdatum der Person zu berechnen:

public class Adult {
    private Date birthDate;

    public Interval getCurrentAge() {
        return calculateAge(Date.now());
    }

    public Interval calculateAge(Date date) {
        // Return interval between birthDate and date
    }
}
Greg Burghardt
quelle
2

Was Sie suchen, nennt man markierte Typen . Sie sind eine Art zu sagen, "das ist eine ganze Zahl, die das Alter darstellt", während "das ist auch eine ganze Zahl, aber es stellt das Gewicht dar" und "Sie können nicht eins dem anderen zuordnen". Beachten Sie, dass dies weiter geht als physikalische Einheiten wie Meter oder Kilogramm: Ich habe in meinem Programm möglicherweise "Personenhöhen" und "Entfernungen zwischen Punkten auf einer Karte", die beide in Metern gemessen, aber nicht miteinander kompatibel sind, seitdem ich ihnen eine zugewiesen habe der andere macht aus Sicht der Geschäftslogik keinen Sinn.

Einige Sprachen, wie Scala, unterstützen ganz einfach Typen mit Tags (siehe den obigen Link). In anderen Fällen können Sie eigene Wrapper-Klassen erstellen, dies ist jedoch weniger praktisch.

Eine Validierung, z. B. die Überprüfung, ob die Größe einer Person "angemessen" ist, ist ein weiteres Problem. Sie können diesen Code in Ihre AdultKlasse (Konstruktor oder Setter) oder in Ihre mit Tags versehenen Typ- / Wrapper-Klassen einfügen . In gewisser Weise können eingebaute Klassen eine solche Rolle übernehmen URLoder UUIDübernehmen (unter anderem, indem sie Dienstprogrammmethoden bereitstellen).

Ob die Verwendung von Tag-Typen oder Wrapper-Klassen tatsächlich zur Verbesserung des Codes beiträgt, hängt von mehreren Faktoren ab. Wenn Ihre Objekte einfach sind und nur wenige Felder enthalten, ist das Risiko, sie falsch zuzuweisen, gering und der zusätzliche Code, der für die Verwendung markierter Typen erforderlich ist, lohnt sich möglicherweise nicht. In komplexen Systemen mit komplexen Strukturen und vielen Feldern (insbesondere wenn viele von ihnen denselben primitiven Typ haben) kann dies tatsächlich hilfreich sein.

In dem Code, den ich schreibe, erstelle ich häufig Wrapper-Klassen, wenn ich Maps umgebe. Typen wie Map<String, String>sind für sich genommen sehr undurchsichtig, daher NameToAddresshilft es sehr, sie in Klassen mit aussagekräftigen Namen wie einzuhüllen. Natürlich können Sie mit markierten Typen schreiben Map<Name, Address>und benötigen nicht den Wrapper für die gesamte Karte.

Für einfache Typen wie Strings oder Integer habe ich jedoch festgestellt, dass Wrapper-Klassen (in Java) zu lästig sind. Die normale Geschäftslogik war nicht so schlecht, aber es gab eine Reihe von Problemen bei der Serialisierung dieser Typen zu JSON, der Zuordnung zu DB-Objekten usw. Sie können Zuordnungen und Hooks für alle großen Frameworks schreiben (z. B. Jackson und Spring Data). Die zusätzliche Arbeit und Wartung, die mit diesem Code verbunden ist, gleicht jedoch alles aus, was Sie durch die Verwendung dieser Wrapper gewinnen. Natürlich kann YMMV und in einem anderen System das Gleichgewicht unterschiedlich sein.

Michał Kosmulski
quelle