Quale dovrebbe essere l'ultima voce in un'istruzione switch / case?

1

Quando si scrive un'istruzione switch che deve sempre occuparsi di un insieme di valori noto (si immagini un'enumerazione implicita), mi ritrovo a chiedermi quale dovrebbe essere l'ultima voce nel costrutto. Sto sempre considerando gli stessi 3 approcci, ma mi piacerebbe davvero attenermi a quello che ha più senso e non pensarci più. Le alternative sono descritte di seguito. Per ragioni di discussione, supponiamo che ci siano esattamente 4 opzioni per la "variabile di commutazione".

  • Approccio n. 1 : 4 casi, nessun caso default .
  • Approccio n. 2 : 3 casi, default funge da ultimo caso (+ commento che lo spiega).
  • Approccio n. 3 : 4 casi,% casodefault con un " ShouldNeverHappenException ".

I miei pensieri su questo sono i seguenti:

  • Da un lato, poiché default è effettivamente un caso che non può essere raggiunto, sembra inutile ingombrare il codice con esso.
  • D'altra parte, non è corretto non gestire un default di considerazioni future-proofing, cioè, se un giorno verrà resa disponibile un'altra opzione (a causa, ad esempio, delle modifiche API al di fuori di il mio controllo), il codice potrebbe reagire in modo errato se viene utilizzato il caso default per una delle opzioni previste.
  • Se l'ultimo caso gestisce uno scenario di errore, potrebbe non essere così male se si tratta del comportamento predefinito (supponendo che non abbia una gestione degli errori a grana fine).

Sulla base del ragionamento di cui sopra tendo a preferire l'approccio 3 rd , ma non sono un ingegnere del software e forse mi manca qualcosa.

Quali sono le migliori pratiche in questo caso?

    
posta Dev-iL 19.07.2018 - 17:40
fonte

4 risposte

6

imagine an implicit enumeration

Penso che questo sia il punto chiave. Il significato implicito non è un tipo enum effettivo, ma, diciamo, numeri con significato speciale.

const int A = 1;
const int B = 2;
const int C = 3;

E un metodo che utilizza l'istruzione switch:

public void DoSomething(int type)
{
    switch (type)
    {
        case A:
            // Stuff
            break;
        case B:
            // Stuff
            break;
        case C:
            // Stuff
            break;
        default:
            // Do I even needs this?
            break;
    }
}

L'enumerazione "implicita" in questo esempio è un numero intero a 32 bit. L'intero intervallo di valori per l'intero a 32 bit sono i casi possibili. Quindi cosa succede se chiamo:

DoSomething(355);

o

DoSomething(-2);

Si potrebbe dire "Uso sempre le costanti A, B e C nel mio programma quando si chiama questo metodo, quindi è impossibile passare -1"

Ma il compilatore lo consente, quindi un cambio di codice in futuro nel modo in cui DoSomething viene chiamato può passare un valore non valido.

Se passi un valore illegale, c'è un default intelligente su cui puoi ricorrere? Se è così, questo è il tuo default . In caso contrario, lancia un'eccezione.

Vorrei sostenere un default anche per un'enumerazione esplicita:

enum Foo
{
    A = 1,
    B = 2,
    C = 3
}

public void DoSomething(Foo type)
{
    switch (type)
    {
        case A:
            // Stuff
            break;
        case B:
            // Stuff
            break;
        case C:
            // Stuff
            break;
        default:
            throw new NotImplementedException("Type " + type + " is not yet implemented");
            break;
    }
}

La clausola predefinita che genera un'eccezione al fatto che un valore non è implementato è un buon strumento di debug per il futuro in cui aggiungi un elemento all'enumerazione e poi ti dimentichi di cambiare il comportamento dell'applicazione in base a quello.

L'eccezione si verificherà nella produzione? Molto probabilmente no. Probabilmente succederà durante lo sviluppo o il test e verrà risolto.

Ma questo è il vantaggio di avere una clausola predefinita e fare qualcosa per impostazione predefinita o esplodere.

Ci sono alcuni casi in cui non fare nulla è OK. Onestamente, ancora inserisco una clausola predefinita con un commento:

public void DoSomething(int type)
{
    switch (type)
    {
        case A:
            ...
            break;
        case B:
            ...
            break;
        case C:
            ...
            break;
        default:
            // Do nothing intentionally
            break;
    }
}

Per me questo rientra nelle stesse linee guida della gestione delle eccezioni. Se si cattura l'eccezione, fare qualcosa con esso. Se non vuoi fare nulla, prendi l'eccezione e spiega perché non si fa nulla:

try
{
    ...
}
catch(Exception)
{
    // Do nothing, because we are trying to do something and we
    // really don't care if it happens.
}
    
risposta data 19.07.2018 - 18:54
fonte
2

Ricordo che all'inizio della mia carriera scrissi una serie di altre dichiarazioni if if che pensavo riguardassero tutti i casi.

Alla fine ho aggiunto:

else 
{
    Throw new Exception("THE IMPOSSIBLE HAPPENED!!");
}

Naturalmente alcuni mesi dopo il mio capo mi ha avvicinato con uno sguardo preoccupato "Ewan, cosa significa" L'impossibile è successo "?!".

Significa che hai dimenticato i null, questo è ciò che significa

    
risposta data 19.07.2018 - 17:45
fonte
0

Se hai a che fare con un tipo enumerato, per il quale esiste un set di valori fisso e relativamente piccolo, non vedo alcun motivo reale per avere un caso predefinito:

  • Usarlo come una forma di "pre-debug" per catturare i tuoi potenziali errori per te sembra avere poco valore. In effetti, è totalmente inutile se hai scritto il tuo interruttore giusto la prima volta. Potresti prendere il tempo in più che avresti speso aggiungendo il caso predefinito per fare in modo che il tuo switch avesse un'altra volta errori per errori.

  • Usarlo come una forma di proofing del futuro per catturare potenziali errori da successive modifiche al codice sembra avere anche poco valore. Supponi che qualcuno aggiunga più enumerazioni in seguito. Se sono diligenti, aggiungeranno correttamente il loro nuovo caso all'interruttore e il valore predefinito non sarebbe servito a nulla. E se non sono diligenti nel prestare attenzione al codice switch esistente, il tuo futuro a prova di prova potrebbe altrettanto facilmente causare un problema invece di risolverlo. Forse volevano che il passaggio non facesse assolutamente nulla con la loro nuova enumerazione (cioè nessun caso predefinito), ma non hanno visto che hai inserito un valore predefinito. Il tuo futuro-proofing avrebbe poi apportato un piccolo lavoro in più per loro. p>

In definitiva, dipenderà dalla tua situazione e da quanto è "conosciuto" il tuo insieme di valori. Se il set di valori è ampio o non ti fidi delle tue capacità per "conoscere" completamente ciò che sono o potrebbero essere, forse un valore predefinito di tutti vale la pena.

    
risposta data 19.07.2018 - 18:28
fonte
0
switch country
US: minimum_drinking_age = 21
Spain: minimum_drinking_age = 16
default: minimum_drinking_age = 18

Ho appena estratto questi numeri dal mio naso. Lì, presumo che negli Stati Uniti l'età legale per bere sia 21+, in Spagna è 16+ e in ogni altro paese del mondo è 18 +.

Questo è solo uno scenario casuale. Ci sono solo 2 casi in cui un valore deve essere impostato in modo specifico, in ogni altro caso, verrà impostato su un valore predefinito. Perché dovresti lanciare un'eccezione? Non ha senso. L'impostazione predefinita è utilizzata per i valori predefiniti. L'uso improprio per altri scopi è oltre il punto!

Secondo questo sembra che non fossi del tutto fuori (18 è il default)!

    
risposta data 19.07.2018 - 19:06
fonte