Was sind die Nachteile bei der Zuordnung integraler Bezeichner zu Aufzählungen?

16

Ich habe darüber nachgedacht, benutzerdefinierte Typen für Bezeichner wie diesen zu erstellen:

public enum CustomerId : int { /* intentionally empty */ }
public enum OrderId : int { }
public enum ProductId : int { }

Meine Hauptmotivation dafür ist es, den Fehler zu vermeiden, bei dem Sie eine orderItemId versehentlich an eine Funktion übergeben, die eine orderItemDetailId erwartet hat.

Anscheinend funktionieren Aufzählungen nahtlos mit allem, was ich in einer typischen .NET-Webanwendung verwenden möchte:

  • MVC-Routing funktioniert einwandfrei
  • Die JSON-Serialisierung funktioniert einwandfrei
  • Jeder ORM, den ich mir vorstellen kann, funktioniert einwandfrei

Jetzt frage ich mich also: "Warum sollte ich das nicht tun?" Dies sind die einzigen Nachteile, die mir einfallen:

  • Dies kann andere Entwickler verwirren
  • Es führt zu Inkonsistenzen in Ihrem System, wenn Sie nicht-integrale Bezeichner haben.
  • Es kann zusätzliches Casting erfordern, wie z (CustomerId)42. Ich denke aber nicht, dass dies ein Problem sein wird, da das ORM- und MVC-Routing Ihnen normalerweise Werte des Aufzählungstyps direkt übergibt.

Meine Frage ist also, was fehle ich? Das ist wahrscheinlich eine schlechte Idee, aber warum?

default.kramer
quelle
3
Dies wird als „microtyping“, können Sie Google um (zB dies ist für Java, Details sind unterschiedlich, aber die Motivation gleich)
Qbd
Ich habe zwei Teilantworten getippt und sie dann gelöscht, weil mir klar wurde, dass ich darüber falsch nachdachte. Ich stimme Ihnen zu, dass "Dies ist wahrscheinlich eine schlechte Idee, aber warum?"
user2023861

Antworten:

7

Es ist eine großartige Idee, einen Typ für jede Art von Kennung zu erstellen , hauptsächlich aus den von Ihnen angegebenen Gründen. Ich würde das nicht tun, indem ich den Typ als enum implementiere, weil es Ihnen mehr Optionen gibt, wenn Sie ihn zu einer richtigen Struktur oder Klasse machen:

  • Sie können int eine implizite Konvertierung hinzufügen. Wenn Sie darüber nachdenken, ist es die andere Richtung, int -> CustomerId, die problematisch ist und die eine ausdrückliche Bestätigung durch den Verbraucher verdient.
  • Sie können Geschäftsregeln hinzufügen, die angeben, welche Bezeichner zulässig sind und welche nicht. Es geht nicht nur um eine schwache Überprüfung, ob das int positiv ist: Manchmal wird die Liste aller möglichen IDs in der Datenbank gespeichert, sondern ändert sich nur während der Bereitstellung. Dann können Sie einfach überprüfen, ob die angegebene ID vorhanden ist, und das Ergebnis der richtigen Abfrage im Cache abgleichen. Auf diese Weise können Sie sicherstellen, dass Ihre Anwendung bei ungültigen Daten schnell fehlschlägt .
  • Methoden für die ID können Ihnen bei der Durchführung teurer DB-Existenzprüfungen oder beim Abrufen des entsprechenden Entitäts- / Domänenobjekts helfen.

Informationen zur Implementierung finden Sie im Artikel Functional C #: Primitive Obsession .

Lukáš Lánský
quelle
1
Ich würde sagen , Sie mit ziemlicher Sicherheit so nicht jeden int in einer Klasse wickeln wollen , aber in einer Struktur
jk.
Gute Bemerkung, ich habe die Antwort leicht aktualisiert.
Lukáš Lánský
3

Es ist ein kluger Hack, aber dennoch ein Hack, der ein Feature für etwas missbraucht, für das es nicht der beabsichtigte Zweck ist. Hacks haben Kosten in Bezug auf die Wartbarkeit, daher ist es nicht genug, dass Sie keine anderen Nachteile finden. Sie müssen auch erhebliche Vorteile im Vergleich zu der idiomatischeren Methode zur Lösung desselben Problems haben.

Der idiomatische Weg wäre, einen Wrapper-Typ oder eine Wrapper-Struktur mit einem einzigen Feld zu erstellen. Dies gibt Ihnen die gleiche Art von Sicherheit in einer expliziten und idiomatischen Art und Weise. Obwohl Ihr Vorschlag zugegebenermaßen clever ist, sehe ich keine wesentlichen Vorteile bei der Verwendung von Wrapper-Typen.

JacquesB
quelle
1
Der Hauptvorteil einer Aufzählung besteht darin, dass sie mit MVC, Serialisierung, ORMs usw. "genau so funktioniert", wie Sie es möchten. Ich habe in der Vergangenheit benutzerdefinierte Typen (Strukturen und Klassen) erstellt und am Ende schreiben Sie mehr Code als Sie müssen, damit diese Frameworks / Bibliotheken mit Ihren benutzerdefinierten Typen funktionieren.
default.kramer
Die Serialisierung sollte in beiden Fällen transparent funktionieren, aber z. ORMs müssen Sie eine Art explizite Konvertierung konfigurieren. Dies ist jedoch der Preis für eine stark typisierte Sprache.
JacquesB
2

Aus meiner Sicht ist die Idee gut. Die Absicht, nicht verwandten Klassen von Bezeichnern unterschiedliche Typen zuzuweisen und ansonsten die Semantik über Typen auszudrücken und vom Compiler überprüfen zu lassen, ist gut.

Ich weiß nicht, wie es in Bezug auf die Leistung in .NET funktioniert, z. B. sind die Enum-Werte unbedingt in Kästchen angegeben. OTOH-Code, der mit einer Datenbank funktioniert, ist wahrscheinlich ohnehin E / A-gebunden, und Boxed Identifiers stellen keinen Engpass dar.

In einer perfekten Welt wären Identifikatoren unveränderlich und nur für die Gleichheit vergleichbar. tatsächliche Bytes wären ein Implementierungsdetail. Nicht verwandte Bezeichner hätten inkompatible Typen, sodass Sie nicht versehentlich einen für einen anderen verwenden könnten. Ihre Lösung passt praktisch zur Rechnung.

9000
quelle
-1

Sie können dies nicht tun, Aufzählungen müssen vordefiniert sein. Wenn Sie also nicht bereit sind, das gesamte Spektrum der möglichen Werte von Kunden-IDs vorab zu definieren, ist Ihr Typ unbrauchbar.

Wenn Sie eine Besetzung vornehmen, widersprechen Sie Ihrem primären Ziel, die versehentliche Zuweisung einer Typ-A-ID zu einer Typ-B-ID zu verhindern. Daher sind Aufzählungen für Ihren Zweck nicht hilfreich.

Dies wirft jedoch die Frage auf, ob es hilfreich wäre, Ihre Typen für Kunden-ID, Bestell-ID und Produkt-ID zu erstellen, indem Sie von Int32 (oder Guid) abstammen. Ich kann mir Gründe vorstellen, warum dies falsch wäre.

Der Zweck einer ID ist die Identifizierung. Integrale Typen sind dafür bereits perfekt, sie werden durch ihre Abstammung nicht mehr identifizierbar. Es ist keine Spezialisierung erforderlich oder erwünscht. Ihre Welt wird nicht besser dargestellt, wenn Sie unterschiedliche Typen für Bezeichner verwenden. Aus OO-Sicht wäre es also schlecht.

Mit Ihrem Vorschlag möchten Sie mehr als Typensicherheit, Sie möchten Wertesicherheit und das ist jenseits der Modellierungsdomäne ein operatives Problem.

Martin Maat
quelle
1
Nein, Aufzählungen müssen in .NET nicht vordefiniert sein. Und der Punkt ist, dass Casting selten vorkommt, z. B. wenn public ActionResult GetCustomer(CustomerId custId)kein Casting benötigt wird - das MVC-Framework liefert den Wert.
default.kramer
2
Ich stimme nicht zu. Für mich ist es aus OO-Sicht absolut sinnvoll. Int32 hat keine Semantik, es ist rein "technischer" Typ. Aber ich brauche einen abstrakten Identifier-Typ, der dem Zweck dient, Objekte zu identifizieren, und der zufällig als Int32 implementiert ist (aber lang oder was auch immer sein könnte, spielt eigentlich keine Rolle). Wir haben konkrete Spezialisierungen wie ProductId, die von Identifier abstammen und die konkrete Instanz von ProductId identifizieren. Es macht keinen logischen Sinn, ProductId den Wert von OrderItemId zuzuweisen (dies ist eindeutig ein Fehler). Es ist also großartig, dass das Typsystem uns davor schützen kann.
qbd
1
Ich wusste nicht, dass C # in Bezug auf die Verwendung von Aufzählungen so flexibel ist. Für mich als Entwickler ist es sicherlich verwirrend, dass Aufzählungen Aufzählungen möglicher Werte sind. Sie geben normalerweise einen Bereich benannter IDs an. Dieser Typ wird also in dem Sinne "missbraucht", dass es keinen Bereich und keine Namen gibt, nur der Typ bleibt übrig. Und anscheinend ist der Typ auch nicht so sicher. Eine andere Sache: Das Beispiel ist offensichtlich eine Datenbankanwendung und irgendwann wird Ihre "Aufzählung" einem integralen Typfeld zugeordnet, in dem Sie Ihren mutmaßlichen Typ schließlich sicher verlieren würden.
Martin Maat