Dilemma
Ich habe viele Best-Practice-Bücher über objektorientierte Praktiken gelesen, und fast jedes Buch, das ich gelesen habe, enthielt einen Teil, in dem Enums als Codegeruch bezeichnet werden. Ich denke, sie haben den Teil verpasst, in dem sie erklären, wann Aufzählungen gültig sind.
Als solches suche ich nach Richtlinien und / oder Anwendungsfällen, bei denen Aufzählungen KEIN Codegeruch und tatsächlich ein gültiges Konstrukt sind.
Quellen:
"WARNUNG Als Faustregel gilt, dass Aufzählungen Code-Gerüche sind und in polymorphe Klassen umgewandelt werden sollten. [8]" Seemann, Mark, Dependency Injection in .Net, 2011, p. 342
[8] Martin Fowler et al., Refactoring: Verbesserung des Entwurfs bestehender Codes (New York: Addison-Wesley, 1999), 82.
Kontext
Die Ursache meines Dilemmas ist eine Handels-API. Sie geben mir einen Stream von Tick-Daten, indem sie diese Methode senden:
void TickPrice(TickType tickType, double value)
wo enum TickType { BuyPrice, BuyQuantity, LastPrice, LastQuantity, ... }
Ich habe versucht, einen Wrapper für diese API zu erstellen, da das Brechen von Änderungen die Lebensweise dieser API ist. Ich wollte den Wert jedes zuletzt empfangenen Tick-Typs auf meinem Wrapper verfolgen und habe dazu ein Wörterbuch mit Ticktypen verwendet:
Dictionary<TickType,double> LastValues
Für mich schien dies die richtige Verwendung einer Aufzählung zu sein, wenn sie als Schlüssel verwendet werden. Aber ich habe Bedenken, weil ich einen Ort habe, an dem ich basierend auf dieser Sammlung eine Entscheidung treffen kann, und ich kann mir keine Möglichkeit vorstellen, wie ich die switch-Anweisung beseitigen könnte. Ich könnte eine Factory verwenden, aber diese Factory wird immer noch eine haben switch-Anweisung irgendwo. Es schien mir, dass ich nur Dinge bewege, aber es riecht immer noch.
Es ist einfach, das NICHT von Aufzählungen zu finden, aber die DOs, nicht so einfach, und ich würde es begrüßen, wenn die Leute ihre Fachkenntnisse, die Vor- und Nachteile teilen können.
Bedenken
Einige Entscheidungen und Aktionen basieren auf diesen TickType
und ich kann mir keine Möglichkeit vorstellen, Enum- / Switch-Anweisungen zu eliminieren. Die sauberste Lösung, die ich mir vorstellen kann, ist die Verwendung einer Factory und die Rückgabe einer Implementierung auf der Basis TickType
. Selbst dann habe ich noch eine switch-Anweisung, die eine Implementierung einer Schnittstelle zurückgibt.
Im Folgenden ist eine der Beispielklassen aufgeführt, in denen ich Zweifel habe, dass ich möglicherweise eine falsche Aufzählung verwende:
public class ExecutionSimulator
{
Dictionary<TickType, double> LastReceived;
void ProcessTick(TickType tickType, double value)
{
//Store Last Received TickType value
LastReceived[tickType] = value;
//Perform Order matching only on specific TickTypes
switch(tickType)
{
case BidPrice:
case BidSize:
MatchSellOrders();
break;
case AskPrice:
case AskSize:
MatchBuyOrders();
break;
}
}
}
enums as switch statements might be a code smell ...
Antworten:
Aufzählungen sind für Anwendungsfälle gedacht, in denen Sie buchstäblich jeden möglichen Wert aufgelistet haben, den eine Variable annehmen könnte. Je. Denken Sie an Anwendungsfälle wie Wochentage oder Monate des Jahres oder Konfigurationswerte eines Hardware-Registers. Dinge, die sehr stabil sind und sich durch einen einfachen Wert darstellen lassen.
Denken Sie daran, wenn Sie eine Anti-Korruptions-Schicht erstellen, können Sie es nicht vermeiden, irgendwo eine switch-Anweisung zu haben , da Sie das Design umschließen, aber wenn Sie es richtig machen, können Sie es auf diesen einen Ort beschränken und Verwenden Sie Polymorphismus an anderer Stelle.
quelle
Erstens bedeutet ein Codegeruch nicht, dass etwas nicht stimmt. Es bedeutet, dass etwas nicht stimmt.
enum
riecht, weil es häufig missbraucht wird, aber das heißt nicht, dass man sie meiden muss. Sie müssen nur noch tippenenum
, anhalten und nach besseren Lösungen suchen .Der besondere Fall, der am häufigsten auftritt, ist, wenn die verschiedenen Aufzählungen verschiedenen Typen mit unterschiedlichem Verhalten, aber derselben Schnittstelle entsprechen. Zum Beispiel mit verschiedenen Backends sprechen, verschiedene Seiten rendern usw. Diese werden viel natürlicher mit polymorphen Klassen implementiert.
In Ihrem Fall entspricht der TickType nicht unterschiedlichen Verhaltensweisen. Dies sind verschiedene Arten von Ereignissen oder verschiedene Eigenschaften des aktuellen Zustands. Ich denke, dies ist der ideale Ort für eine Aufzählung.
quelle
Bei der Übertragung von Daten sind Aufzählungen kein Codegeruch
IMHO, wenn Daten übertragen werden,
enums
um anzuzeigen, dass ein Feld einen Wert aus einem eingeschränkten (sich selten ändernden) Satz von Werten haben kann, ist dies gut.Ich halte es für vorzuziehen, willkürliche
strings
oderints
. Zeichenfolgen können durch unterschiedliche Schreibweise und Großschreibung Probleme verursachen. Ints ermöglichen Bereichswerte zu übertragen und haben wenig Semantik (zB erhalte ich3
von Ihrem Trading - Service, was ist das?LastPrice
?LastQuantity
? Etwas anderes?Das Übertragen von Objekten und die Verwendung von Klassenhierarchien ist nicht immer möglich. Beispielsweise erlaubt es wcf dem empfangenden Ende nicht, zu unterscheiden, welche Klasse gesendet wurde.
Ein weiterer Grund, warum man keine Klassenobjekte übertragen möchte, ist, dass der Dienst möglicherweise ein völlig anderes Verhalten für ein übertragenes Objekt (z. B.
LastPrice
) als den Client erfordert . In diesem Fall ist das Senden der Klasse und ihrer Methoden unerwünscht.Sind switch-Anweisungen schlecht?
IMHO, eine Single-
switch
Anweisung, die abhängig von einem unterschiedliche Konstruktoren aufruft,enum
ist kein Code-Geruch. Es ist nicht unbedingt besser oder schlechter als andere Methoden, z. B. die Reflexion anhand eines Typnamens. Dies hängt von der tatsächlichen Situation ab.Schalter
enum
überall anzuschalten ist ein Codegeruch, oop bietet Alternativen, die oft besser sind:quelle
int
. Mein Wrapper ist derjenige, der verwendet,TickType
der aus dem empfangenen gegossen wirdint
. Bei mehreren Ereignissen wird dieser Tick-Typ mit unterschiedlichen Signaturen verwendet, die auf verschiedene Anforderungen reagieren. Ist es gängige Praxis, eineint
ständig für verschiedene Funktionen zu nutzen?TickPrice(int type, double value)
Verwendet zB 1,3 und 6,type
währendTickSize(int type, double value
2,4 und 5 verwendet werden? Ist es überhaupt sinnvoll, diese in zwei Ereignisse zu unterteilen?Was wäre, wenn Sie sich für einen komplexeren Typ entscheiden würden:
Dann könnten Sie Ihre Typen aus Reflection laden oder selbst erstellen, aber das Wichtigste dabei ist, dass Sie sich an das Open-Close-Prinzip von SOLID halten
quelle
TL; DR
Um die Frage zu beantworten, fällt es mir jetzt schwer, mir vorzustellen, dass Aufzählungen auf irgendeiner Ebene kein Codegeruch sind . Es gibt eine bestimmte Absicht, die sie effizient erklären (es gibt eine klar begrenzte, begrenzte Anzahl von Möglichkeiten für diesen Wert), aber ihre inhärent geschlossene Natur macht sie architektonisch minderwertig.
Entschuldigen Sie, dass ich Tonnen meines Legacy-Codes umgestalte. / seufz; ^ D
Aber wieso?
Ziemlich guter Rückblick, warum das bei LosTechies so ist :
Ein weiterer Implementierungsfall ...
Wenn Sie ein Verhalten haben, das von den Enum-Werten abhängt, warum sollten Sie nicht stattdessen unterschiedliche Implementierungen einer ähnlichen Schnittstelle oder einer übergeordneten Klasse verwenden, die sicherstellen, dass dieser Wert vorhanden ist? In meinem Fall werden basierend auf den REST-Statuscodes verschiedene Fehlermeldungen angezeigt. Anstatt von...
... vielleicht sollte ich Statuscodes in Klassen einbinden.
Jedes Mal, wenn ich einen neuen IAmStatusResult-Typ benötige, verschlüssele ich ihn ...
... und jetzt kann ich sicherstellen, dass der frühere Code erkennt, dass er einen eingeschränkten
IAmStatusResult
Geltungsbereich hat, und auf seinen verweisen,entity.ErrorKey
anstatt auf den komplexeren, tödlicheren_getErrorCode(403)
.Und was noch wichtiger ist: Jedes Mal , wenn ich einen neuen Typ von Rückgabewert hinzufüge, muss kein weiterer Code hinzugefügt werden, um damit umzugehen . Was weißt du, die
enum
undswitch
waren wahrscheinlich Code-Gerüche.Profitieren.
quelle
StatusResult
Wert. Sie könnten argumentieren, dass die Aufzählung in diesem Anwendungsfall nützlich ist, aber ich würde das wahrscheinlich immer noch als Codegeruch bezeichnen, da es gute Alternativen gibt, für die die geschlossene Auflistung nicht erforderlich ist.enum
, Sie tun müssen , um Update - Code jedes Mal , wenn Sie einen Wert hinzuzufügen oder zu entfernen, und möglicherweise viele Orte, je nachdem , wie Sie Ihren Code strukturiert. Die Verwendung von Polymorphismus anstelle einer Aufzählung entspricht der Sicherstellung, dass Ihr Code in Boyce-Codd-Normalform vorliegt .Ob die Verwendung einer Aufzählung ein Codegeruch ist oder nicht, hängt vom Kontext ab. Ich denke, Sie können einige Ideen für die Beantwortung Ihrer Frage erhalten, wenn Sie das Ausdrucksproblem betrachten . Sie verfügen also über eine Sammlung verschiedener Typen und Operationen, und Sie müssen Ihren Code organisieren. Es gibt zwei einfache Möglichkeiten:
Welche Lösung ist besser?
Wenn, wie Karl Bielefeldt betonte, Ihre Typen festgelegt sind und Sie erwarten, dass das System hauptsächlich durch Hinzufügen neuer Operationen für diese Typen wächst, ist die Verwendung einer Aufzählung und einer switch-Anweisung eine bessere Lösung: Jedes Mal, wenn Sie eine neue Operation benötigen, müssen Sie Implementieren Sie einfach eine neue Prozedur, während Sie bei Verwendung von Klassen jeder Klasse eine Methode hinzufügen müssten.
Auf der anderen Seite ist es bequemer, eine objektorientierte Lösung zu verwenden, wenn Sie erwarten, dass Sie einen ziemlich stabilen Betrieb haben, aber im Laufe der Zeit mehr Datentypen hinzufügen müssen: Da neue Datentypen implementiert werden müssen, müssen Sie nur Fügen Sie immer neue Klassen hinzu, die dieselbe Schnittstelle implementieren. Wenn Sie jedoch eine Aufzählung verwenden, müssen Sie alle switch-Anweisungen in allen Prozeduren mit der Aufzählung aktualisieren.
Wenn Sie Ihr Problem nicht in eine der beiden oben genannten Optionen einordnen können, können Sie sich anspruchsvollere Lösungen ansehen (siehe z. B. die oben zitierte Wikipedia-Seite für eine kurze Diskussion und eine Referenz zur weiteren Lektüre).
Versuchen Sie also zu verstehen, in welche Richtung sich Ihre Anwendung entwickeln kann, und wählen Sie dann eine geeignete Lösung aus.
Da sich die Bücher, auf die Sie sich beziehen, mit dem objektorientierten Paradigma befassen, ist es nicht verwunderlich, dass sie gegen die Verwendung von Aufzählungen voreingenommen sind. Eine objektorientierte Lösung ist jedoch nicht immer die beste Option.
Fazit: Aufzählungen sind nicht unbedingt ein Codegeruch.
quelle
paint()
,show()
,close()
resize()
Operationen und eigene Widgets) , dann die objektorientierte Ansatz ist besser in dem Sinne , dass es auch einen neuen Typ hinzufügen können ohne Beeinträchtigung viel vorhandenen Code (Sie implementieren im Grunde eine neue Klasse, die eine lokale Änderung ist).