Best Practices für die Verwendung und Beibehaltung von Enums

77

Ich habe hier mehrere Fragen / Diskussionen darüber gesehen , wie enumähnliche Werte am besten behandelt und beibehalten werden können (z. B. Bestehende Daten, die für Aufzählungen geeignet sind , wie eine Aufzählung mit NHibernate beibehalten wird ), und ich möchte fragen, was der allgemeine Konsens ist .

Speziell:

  • Wie sollen diese Werte im Code behandelt werden?
  • Wie sollen sie in einer Datenbank gespeichert werden (als Text / als Nummer)?
  • Was sind die Kompromisse zwischen verschiedenen Lösungen?

Hinweis: Ich habe die ursprünglich in dieser Frage enthaltenen Erklärungen in eine Antwort verschoben.

sleske
quelle

Antworten:

19

Ich stimme vielem zu, was Sie sagen. Eine Sache, die ich jedoch an die Persistenz von Aufzählungen anhängen möchte: Ich glaube nicht, dass die Generierung der Aufzählungen zur Erstellungszeit aus den DB-Werten akzeptabel ist, aber ich denke auch, dass die Laufzeitprüfung keine gute Lösung ist . Ich würde ein drittes Mittel definieren: einen Komponententest durchführen lassen, bei dem die Werte der Aufzählung anhand der Datenbank überprüft werden. Dies verhindert "zufällige" Abweichungen und vermeidet den Aufwand, die Aufzählungen bei jeder Ausführung des Codes mit der Datenbank zu vergleichen.

Paul Sonier
quelle
14

Der erste Artikel sieht für mich gut aus. Basierend auf den Kommentaren scheinen jedoch einige Kommentare zu Java-Enums einige Dinge zu klären.

Der Aufzählungstyp in Java ist per Definition eine Klasse, aber viele Programmierer neigen dazu, dies zu vergessen, da sie ihn eher auf "eine Liste zulässiger Werte" beziehen, wie in einigen anderen Sprachen. Es ist mehr als das.

Um diese switch-Anweisungen zu vermeiden, ist es möglicherweise sinnvoll, Code und zusätzliche Methoden in die enum-Klasse einzufügen. Es besteht fast nie die Notwendigkeit, eine separate "enum-like real class" zu erstellen.

Berücksichtigen Sie auch den Punkt der Dokumentation - möchten Sie die tatsächliche Bedeutung Ihrer Aufzählung in der Datenbank dokumentieren? Im Quellcode, der die Werte widerspiegelt (Ihr Aufzählungstyp) oder in einer externen Dokumentation? Ich persönlich bevorzuge den Quellcode.

Wenn Sie Enum-Werte aufgrund der Geschwindigkeit oder aus irgendeinem Grund als Ganzzahlen in der Datenbank darstellen möchten, sollte sich diese Zuordnung auch in der Java-Enum befinden. Sie erhalten standardmäßig eine Zeichenfolgennamenzuordnung, und damit bin ich zufrieden. Jedem Aufzählungswert ist eine Ordnungszahl zugeordnet, aber die direkte Verwendung als Ordnungszahl zwischen Code und Datenbank ist nicht sehr hell, da sich diese Ordnungszahl ändert, wenn jemand die Werte im Quellcode neu anordnet. Oder fügt zusätzliche Aufzählungswerte zwischen vorhandenen Werten hinzu. Oder entfernt einen Wert.

(Wenn jemand den Namen der Aufzählung im Quellcode ändert, wird die Standard-Zeichenfolgenzuordnung natürlich auch schlecht, aber das ist weniger wahrscheinlich. Und Sie können sich bei Bedarf leichter davor schützen, indem Sie eine Laufzeitprüfung durchführen und überprüfen Sie die Einschränkungen in der Datenbank, wie hier bereits vorgeschlagen.)

lokori
quelle
1
Es gibt zwei Szenarien, die unterstützt werden müssen: Jemand, der die Aufzählungen in meiner Datei neu anordnet, oder jemand, der Refactoring durchführt (um schlechte Auswahlmöglichkeiten für den Anfangsnamen zu klären) und dauerhafte Daten zerstört. Ich denke, letzteres ist wichtiger, und Ordnungszahl ist der Weg für die Datenpersistenz.
Justin
7

Ich habe versucht, mein Verständnis zusammenzufassen. Sie können dies jederzeit bearbeiten, wenn Sie Korrekturen vorgenommen haben. Also los geht's:

Im Code

Im Code sollten Aufzählungen entweder mit dem nativen Aufzählungstyp der Sprache (zumindest in Java und C #) oder mit dem "typsicheren Aufzählungsmuster" behandelt werden . Es wird davon abgeraten, einfache Konstanten (Integer oder ähnliches) zu verwenden, da Sie die Typensicherheit verlieren (und es schwierig machen zu verstehen, welche Werte für eine Methode zulässig sind).

Die Wahl zwischen diesen beiden hängt davon ab, wie viel zusätzliche Funktionalität an die Aufzählung angehängt werden soll:

  • Wenn Sie eine Menge Funktionen in die Enumeration einfügen möchten (was gut ist, da Sie vermeiden, ständig darauf zu schalten), ist eine Klasse normalerweise besser geeignet.
  • Andererseits ist bei einfachen Aufzählungswerten die Aufzählung der Sprache normalerweise klarer.

Insbesondere in Java kann eine Aufzählung nicht von einer anderen Klasse erben. Wenn Sie also mehrere Aufzählungen mit ähnlichem Verhalten haben, die Sie in eine Oberklasse einfügen möchten, können Sie die Aufzählungen von Java nicht verwenden.

Anhaltende Aufzählungen

Um Aufzählungen beizubehalten, sollte jedem Aufzählungswert eine eindeutige ID zugewiesen werden. Dies kann entweder eine Ganzzahl oder eine kurze Zeichenfolge sein. Eine kurze Zeichenfolge wird bevorzugt, da sie mnemonisch sein kann (erleichtert DBAs usw. das Verständnis der Rohdaten in der Datenbank).

  • In der Software sollte jede Aufzählung dann Zuordnungsfunktionen haben, die zwischen der Aufzählung (zur Verwendung in der Software) und dem ID-Wert (zur Beibehaltung) konvertiert werden können. Einige Frameworks (z. B. (N) Hibernate) unterstützen dies nur eingeschränkt automatisch. Andernfalls müssen Sie es in den Aufzählungstyp / die Aufzählungsklasse einfügen.
  • Die Datenbank sollte (idealerweise) eine Tabelle für jede Aufzählung enthalten, in der die gesetzlichen Werte aufgeführt sind. Eine Spalte wäre die ID (siehe oben), die PK. Zusätzliche Spalten können beispielsweise für eine Beschreibung sinnvoll sein. Alle Tabellenspalten, die Werte aus dieser Aufzählung enthalten, können diese "Aufzählungstabelle" dann als FK verwenden. Dies garantiert, dass falsche Aufzählungswerte niemals beibehalten werden können, und ermöglicht es der Datenbank, "für sich allein zu stehen".

Ein Problem bei diesem Ansatz besteht darin, dass die Liste der zulässigen Aufzählungswerte an zwei Stellen vorhanden ist (Code und Datenbank). Dies ist schwer zu vermeiden und wird daher oft als akzeptabel angesehen. Es gibt jedoch zwei Alternativen:

  • Behalten Sie nur die Liste der Werte in der Datenbank bei und generieren Sie den Aufzählungstyp zur Erstellungszeit. Elegant, bedeutet jedoch, dass eine DB-Verbindung erforderlich ist, damit ein Build ausgeführt werden kann, was problematisch erscheint.
  • Definieren Sie die Liste der Werte im Code als maßgeblich. Überprüfen Sie zur Laufzeit (normalerweise beim Start) die Werte in der Datenbank, beschweren Sie sich / brechen Sie bei Nichtübereinstimmung ab.
sleske
quelle
6

Bei der Codebehandlung für C # haben Sie die Definition des Löschens des 0-Werts verpasst. Ich erkläre fast immer meinen ersten Wert als:

public enum SomeEnum
{
    None = 0,
}

Um als Nullwert zu dienen. Da der Hintergrundtyp eine Ganzzahl ist und eine Ganzzahl standardmäßig 0 ist, ist es an vielen Stellen äußerst nützlich zu wissen, ob eine Aufzählung tatsächlich programmgesteuert festgelegt wurde oder nicht.

Streit
quelle
6
Ich stimme dir nicht zu. Dies wäre nur dann sinnvoll, wenn Sie manchmal Variablen nicht initialisiert lassen, was ich als ernsthaft schlechte Praxis betrachten würde. Ich habe diese Idee, einen "Keine" -Wert zu haben, oft gesehen, aber ich glaube, sie verbirgt nur das eigentliche Problem (die nicht initialisierte Variable).
Sleske
3
Wie verbirgt es das Problem? Es macht es explizit wie ein nullable int. Ich lasse Werte im Code nicht initialisiert, weil ich weiß, auf was die CLR sie standardmäßig setzt. Sie sind immer noch initialisiert, es ist nur implizit.
Quibblesome
Nun, es ist wahrscheinlich eine Frage des Stils. Ich bin fest davon überzeugt, alle Variablen bei der Deklaration vollständig zu initialisieren (oder höchstens in einem if-else direkt nach der Deklaration). Andernfalls vergessen Sie möglicherweise, sie zu initialisieren, insbesondere wenn der Codefluss kompliziert ist. Siehe auch c2.com/cgi/wiki?SingleStepConstructor .
Sleske
5

Java oder C # sollten immer Enums im Code verwenden. Haftungsausschluss: Mein Hintergrund ist C #.

Wenn der Wert in einer Datenbank beibehalten werden soll, sollten die Integralwerte jedes Aufzählungselements explizit definiert werden, damit eine spätere Änderung des Codes nicht versehentlich die übersetzten Aufzählungswerte und damit das Anwendungsverhalten ändert.

Werte sollten immer als integrale Werte in einer Datenbank gespeichert werden, um sie vor dem Refactoring von Enum-Namen zu schützen. Bewahren Sie die Dokumentation zu jeder Aufzählung in einem Wiki auf und fügen Sie dem Datenbankfeld einen Kommentar hinzu, der auf die Wiki-Seite verweist, die den Typ dokumentiert. Fügen Sie dem Aufzählungstyp, der einen Link zum Wiki-Eintrag enthält, auch XML-Dokumentation hinzu, damit dieser über Intellisense verfügbar ist.

Wenn Sie ein Tool zum Generieren von CRUD-Code verwenden, sollte es in der Lage sein, einen Aufzählungstyp zu definieren, der für eine Spalte verwendet werden soll, damit generierte Codeobjekte immer aufgezählte Elemente verwenden.

Wenn für ein Aufzählungsmitglied eine benutzerdefinierte Logik angewendet werden muss, haben Sie einige Optionen:

  • Wenn Sie über eine Aufzählung MyEnum verfügen, erstellen Sie eine statische Klasse MyEnumInfo, die Dienstprogrammmethoden bietet, um zusätzliche Informationen über das Aufzählungselement mithilfe von switch-Anweisungen oder anderen erforderlichen Mitteln zu ermitteln. Durch Anhängen von "Info" an das Ende des Aufzählungsnamens im Klassennamen wird sichergestellt, dass sie in IntelliSense nebeneinander stehen.
  • Dekorieren Sie die Aufzählungselemente mit Attributen, um zusätzliche Parameter anzugeben. Zum Beispiel haben wir ein EnumDropDown-Steuerelement entwickelt, das ein ASP.NET-Dropdown-Menü mit Aufzählungswerten erstellt, und ein EnumDisplayAttribute gibt den gut formatierten Anzeigetext an, der für jedes Mitglied verwendet werden soll.

Ich habe dies nicht versucht, aber mit SQL Server 2005 oder höher könnten Sie theoretisch C # -Code in der Datenbank registrieren, der Aufzählungsinformationen enthält, und die Möglichkeit, Werte in Aufzählungen zu konvertieren, um sie in Ansichten oder anderen Konstrukten zu verwenden, und so eine Methode zum Übersetzen von erstellen Daten auf eine Weise, die für DBAs einfacher zu verwenden ist.

David Boike
quelle
+1 explizite Zuweisung von Werten ist der einzige Weg, um "Beschädigung" zu vermeiden, wenn Sie die Aufzählung ändern
ashes999
3

Imho, wie für den Codeteil:

Sie sollten immer den Typ 'enum' für Ihre Aufzählungen verwenden. Grundsätzlich erhalten Sie eine Menge Werbegeschenke, wenn Sie dies tun: Typensicherheit, Kapselung und Vermeidung von Schaltern, Unterstützung einiger Sammlungen wie EnumSetund EnumMapund Codeklarheit.

Für den Persistenzteil können Sie die Zeichenfolgendarstellung der Aufzählung jederzeit beibehalten und mit der Methode enum.valueOf (Zeichenfolge) zurückladen.

MahdeTo
quelle
Im Prinzip zustimmen, jedoch ist zumindest in Java "enum" dahingehend begrenzt, dass es keine Oberklasse haben kann (wie oben erwähnt), so dass manchmal eine "typesafe enum" -Klasse wahrscheinlich besser ist.
Sleske
3

Das Speichern des Textwerts einer Aufzählung in einer Datenbank ist dem Speichern einer Ganzzahl aufgrund des zusätzlichen Speicherplatzes und der langsameren Suche weniger vorzuziehen. Es ist insofern wertvoll, als es mehr Bedeutung als eine Zahl hat, die Datenbank jedoch zur Speicherung dient und die Präsentationsschicht dazu dient, die Dinge schön aussehen zu lassen.

cjk
quelle
1
Es ist nicht garantiert, dass der int-Wert der Aufzählung im Laufe der Zeit gleich ist.
Miguel Ping
Wenn Sie eine kurze Zeichenfolge verwenden, sollte die Leistung gleich sein. Ein char (2) benötigt 2 Bytes, ein int normalerweise auch 2 oder 4.
sleske
6
@ Miguel Ping: Die Idee ist , jeder Aufzählung explizit eine ID (int oder char) zuzuweisen. Die Verwendung des intern generierten int der Aufzählung ist in der Tat sehr gefährlich.
Sleske
1
Wenn in der Datenbank eine Bedeutung über ganzzahlige Werte hinaus gewünscht wird, würde ich eine Tabelle verwenden, die die ganzzahligen Werte auf lesbare Zeichenfolgen abbildet. Ja, die ganzzahligen Werte dürfen sich später nicht ändern. sollte aber kein Problem sein; Die einzige Relevanz, die zugrunde liegende ganzzahlige Enum-Werte haben sollten, besteht darin, dass sie sich von den anderen Mitgliedern der Enum unterscheiden. (dh sie sollten keinen Grund haben, sich jemals zu ändern). Wenn die ganzzahligen Werte eine Bedeutung haben, die über die eindeutige Identifizierung hinausgeht, sollte wahrscheinlich eine andere Datenstruktur verwendet werden.
Dave Cousineau
3

Nach meiner Erfahrung führt die Verwendung von Aufzählungen für andere Zwecke als zum Übergeben von Optionen (als Flags) an einen sofortigen Methodenaufruf switchirgendwann zu -ing.

  • Wenn Sie die Aufzählung in Ihrem gesamten Code verwenden, erhalten Sie möglicherweise Code, der nicht so einfach zu pflegen ist (die berüchtigte switchAussage).
  • Das Erweitern von Aufzählungen ist ein Schmerz. Sie fügen ein neues Aufzählungselement hinzu und gehen am Ende Ihren gesamten Code durch, um nach allen Bedingungen zu suchen.
  • Mit .NET 3.5 können Sie Enums Erweiterungsmethoden hinzufügen, damit sie sich ein bisschen mehr wie Klassen verhalten. Das Hinzufügen realer Funktionen auf diese Weise ist jedoch nicht so einfach, da es sich immer noch nicht um eine Klasse handelt (Sie würden am Ende switch-es in Ihren Erweiterungsmethoden verwenden, wenn nicht anderswo.

Für eine enumähnliche Entität mit etwas mehr Funktionalität sollten Sie sich also etwas Zeit nehmen und sie als Klasse erstellen, wobei verschiedene Aspekte berücksichtigt werden:

  • Damit sich Ihre Klasse wie eine Aufzählung verhält, können Sie entweder jede abgeleitete Klasse zwingen, als Singleton zu instanziieren, oder Equals überschreiben, um einen Wertevergleich verschiedener Instanzen zu ermöglichen.
  • Wenn Ihre Klasse enumartig ist, sollte dies bedeuten, dass sie keinen serialisierbaren Status enthalten sollte - eine Deserialisierung sollte nur aufgrund ihres Typs möglich sein (eine Art "ID", wie Sie sagten).
  • Die Persistenzlogik sollte nur auf die Basisklasse beschränkt sein, da sonst die Erweiterung Ihrer "Aufzählung" ein Albtraum wäre. Wenn Sie sich für das Singleton-Muster entschieden haben, müssen Sie eine ordnungsgemäße Deserialisierung in Singleton-Instanzen sicherstellen.
Groo
quelle
3

Jedes Mal, wenn Sie "magische Zahlen" im Code verwenden, ändern Sie diese in Aufzählungen. Neben der Zeitersparnis (da die Magie verschwindet, wenn die Fehler auftreten ...) werden auch Ihre Augen und Ihr Gedächtnis geschont (aussagekräftige Aufzählungen machen den Code lesbarer und selbstdokumentierender) Ihren eigenen Code

Yordan Georgiev
quelle
1

Ich weiß, dass dies ein altes Forum ist. Was ist, wenn in die Datenbank andere Dinge direkt integriert sind? Zum Beispiel, wenn die resultierende Datenbank der einzige Zweck des Codes ist. Anschließend definieren Sie die Aufzählungen bei jeder Integration. Besser als sie in der DB zu haben. Ansonsten stimme ich dem ursprünglichen Beitrag zu.

Kreidig
quelle