Quando le enumerazioni NON sono un codice olfattivo?

11

Dilemma

Ho letto molti libri di best practice sulle pratiche orientate agli oggetti, e quasi tutti i libri che ho letto avevano una parte in cui dicono che le enumerazioni sono un odore di codice. Penso che abbiano perso la parte in cui spiegano quando le enumerazioni sono valide.

Pertanto, sto cercando le linee guida e / o casi d'uso in cui le enumerazioni sono NON un odore di codice e in effetti un costrutto valido.

Fonti:

"AVVERTENZA Come regola generale, le enumerazioni sono odori di codice e dovrebbero essere sottoposte a refactoring alle classi polimorfiche. [8]" Seemann, Mark, Dependency Injection in .Net, 2011, p. 342

[8] Martin Fowler et al., Refactoring: migliorare il design del codice esistente (New York: Addison-Wesley, 1999), 82.

Contesto

La causa del mio dilemma è un'API di trading. Mi danno un flusso di dati di tick inviando questo metodo:

void TickPrice(TickType tickType, double value)

dove enum TickType { BuyPrice, BuyQuantity, LastPrice, LastQuantity, ... }

Ho provato a creare un wrapper attorno a questa API perché la modifica delle interruzioni è il modo di vita di questa API. Volevo tenere traccia del valore di ogni ultimo tipo di tick ricevuto sul mio wrapper e l'ho fatto usando un dizionario di ticktypes:

Dictionary<TickType,double> LastValues

Per me, questo mi è sembrato un uso appropriato di enum se sono stati usati come chiavi. Ma ho dei ripensamenti perché ho un posto in cui prendo una decisione basata su questa collezione e non riesco a pensare a un modo in cui avrei potuto eliminare la dichiarazione dell'intervento, potrei usare una fabbrica ma quella fabbrica avrà ancora un passare la dichiarazione da qualche parte. Mi è sembrato che stavo solo spostando le cose ma ancora odora.

È facile trovare i NON FATTI delle enumerazioni, ma i DO, non così facile, e sarei grato se le persone possano condividere la loro esperienza, i pro e i contro.

Secondi pensieri

Alcune decisioni e azioni si basano su questi TickType e non riesco a pensare a un modo per eliminare le dichiarazioni enum / switch. La soluzione più pulita che riesco a pensare è usare una factory e restituire un'implementazione basata su TickType . Anche in questo caso avrò ancora una dichiarazione switch che restituisce un'implementazione di un'interfaccia.

Di seguito è elencata una delle classi di esempio in cui ho dei dubbi sul fatto che potrei utilizzare un enum errato:

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;
    }       
  }
}
    
posta stromms 16.10.2015 - 23:02
fonte

5 risposte

19

Le enumerazioni sono destinate ai casi d'uso quando hai letteralmente elencato tutti i valori possibili che una variabile può assumere. Mai. Pensa a casi d'uso come giorni della settimana o mesi dell'anno o valori di configurazione di un registro hardware. Cose che sono entrambe altamente stabili e rappresentabili con un valore semplice.

Ricorda che, se stai creando un livello anti-corruzione, non puoi evitare di avere un'istruzione switch da qualche parte , a causa del design che stai tagliando, ma se lo fai giusti puoi limitarlo a quell'unico luogo e usare il polimorfismo altrove.

    
risposta data 17.10.2015 - 05:15
fonte
14

In primo luogo, un odore di codice non significa che qualcosa non va. Significa che qualcosa potrebbe essere sbagliato. enum odora perché è frequentemente abusato, ma ciò non significa che devi evitarli. Semplicemente ti ritrovi a digitare enum , fermati e controlla soluzioni migliori.

Il caso particolare che accade più spesso è quando i diversi enum corrispondono a tipi diversi con comportamenti diversi ma con la stessa interfaccia. Ad esempio, parlando con diversi backend, rendendo diverse pagine, ecc. Questi sono molto più naturalmente implementati usando classi polimorfiche.

Nel tuo caso, il TickType non corrisponde a comportamenti diversi. Sono diversi tipi di eventi o proprietà differenti dello stato corrente. Quindi penso che questo sia il posto ideale per un enum.

    
risposta data 17.10.2015 - 00:54
fonte
4

Quando si trasmettono enumerazioni di dati non si ha odore di codice

IMHO, quando si trasmettono dati usando enums per indicare che un campo può avere un valore da un insieme di valori limitato (che cambiano di rado) è buono.

Ritengo preferibile trasmettere arbitrariamente strings o ints . Le stringhe possono causare problemi con variazioni di ortografia e lettere maiuscole. Gli Ints consentono di trasmettere valori fuori intervallo e hanno poca semantica (ad esempio, ottengo 3 dal tuo servizio di trading, che cosa significa? LastPrice ? LastQuantity ? Qualcos'altro?

Trasmettere oggetti e utilizzare le gerarchie di classi non è sempre possibile; ad esempio non consentono al destinatario della ricezione di distinguere quale classe è stata inviata .

In my project the service uses a class hierarchy for effects of operations, just before transmitting via a DataContract the object from the class hierarchy is copied into a union-like object which contains an enum to indicate the type. The client receives the DataContract and creates an object of a class in a hierarchy using the enum value to switch and create an object of the correct type.

Un altro motivo per cui non si vorrebbe trasmettere oggetti di classe è che il servizio potrebbe richiedere un comportamento completamente diverso per un oggetto trasmesso (come LastPrice ) rispetto al client. In tal caso, l'invio della classe e dei suoi metodi è indesiderato.

Le istruzioni switch sono errate?

IMHO, un singolo switch -stato che chiama costruttori diversi a seconda di enum non è un odore di codice. Non è necessariamente migliore o peggiore di altri metodi, come la riflessione su un nome tipografico; questo dipende dalla situazione attuale.

Avere attivato un enum in tutto il luogo è un odore di codice, fornisce alternative spesso migliori:

  • Usa l'oggetto di diverse classi di una gerarchia che hanno metodi sovrascritti; cioè polimorfismo.
  • Utilizza lo schema visitatore quando le classi nella gerarchia cambiano raramente e quando le (molte) operazioni devono essere liberamente accoppiate alle classi nella gerarchia.
risposta data 17.10.2015 - 11:12
fonte
2

cosa succede se hai scelto un tipo più complesso:

    abstract class TickType
    {
      public abstract string Name {get;}
      public abstract double TickValue {get;}
    }

    class BuyPrice : TickType
    {
      public override string Name { get { return "Buy Price"; } }
      public override double TickValue { get { return 2.35d; } }
    }

    class BuyQuantity : TickType
    {
      public override string Name { get { return "Buy Quantity"; } }
      public override double TickValue { get { return 4.55d; } }
    }
//etcetera

quindi potresti caricare i tuoi tipi dal riflesso o crearlo da solo, ma la cosa principale da fare è che stai tenendo premuto Apri Close Principle of SOLID

    
risposta data 17.10.2015 - 00:59
fonte
0

Se l'uso di un enum è un codice che odora o meno dipende dal contesto. Penso che tu possa avere qualche idea per rispondere alla tua domanda se consideri il problema di espressione . Quindi, hai una collezione di diversi tipi e una serie di operazioni su di loro, e devi organizzare il tuo codice. Ci sono due semplici opzioni:

  • Organizza il codice in base alle operazioni. In questo caso puoi utilizzare un'enumerazione per taggare diversi tipi e avere un'istruzione switch in ogni procedura che utilizza i dati taggati.
  • Organizza il codice in base ai tipi di dati. In questo caso è possibile sostituire l'enumerazione con un'interfaccia e utilizzare una classe per ogni elemento dell'enumerazione. Quindi implementa ogni operazione come metodo in ogni classe.

Quale soluzione è migliore?

Se, come sottolineò Karl Bielefeldt, i tuoi tipi sono corretti e ti aspetti che il sistema cresca principalmente aggiungendo nuove operazioni su questi tipi, quindi usare enum e avere un'istruzione switch è una soluzione migliore: ogni volta che hai bisogno di un nuova operazione devi solo implementare una nuova procedura mentre usando le classi dovresti aggiungere un metodo per ogni classe.

D'altro canto, se si prevede di avere un insieme piuttosto stabile di operazioni, ma si pensa che sarà necessario aggiungere più tipi di dati nel tempo, utilizzare una soluzione orientata agli oggetti è più conveniente: come devono essere implementati nuovi tipi di dati , continui ad aggiungere nuove classi implementando la stessa interfaccia, mentre se usassi un enum dovresti aggiornare tutte le istruzioni switch in tutte le procedure usando l'enum.

Se non riesci a classificare il tuo problema in nessuna delle due opzioni sopra, puoi guardare a soluzioni più sofisticate (vedi ad esempio ancora Wikipedia pagina citata sopra per una breve discussione e alcuni riferimenti per ulteriori letture).

Quindi, dovresti cercare di capire in quale direzione la tua applicazione potrebbe evolversi e quindi scegliere una soluzione adeguata.

Dato che i libri cui fai riferimento si occupano del paradigma orientato agli oggetti, non sorprende che siano prevenuti sull'uso di enumerazioni. Tuttavia, una soluzione orientata agli oggetti non è sempre l'opzione migliore.

Bottomline: le enumerazioni non sono necessariamente un odore di codice.

    
risposta data 17.11.2015 - 20:25
fonte

Leggi altre domande sui tag