istruzione switch - gestione del caso predefinito quando non è possibile raggiungerlo


14

Se sto usando un'istruzione switch per gestire i valori di una enum (di proprietà della mia classe) e ho un caso per ogni possibile valore - vale la pena aggiungere codice per gestire il caso "predefinito"?

enum MyEnum
{
    MyFoo,
    MyBar,
    MyBat
}

MyEnum myEnum = GetMyEnum();
switch (myEnum)
{
    case MyFoo:
        DoFoo();
        break;
    case MyBar:
        DoBar();
        break;
    case MyBat:
        DoBat();
        break;
    default:
        Log("Unexpected value");
        throw new ArgumentException() 
}

Non penso che sia perché questo codice non può mai essere raggiunto (anche con test unitari). Il mio collega non è d'accordo e pensa che ciò ci protegga da comportamenti imprevisti causati dall'aggiunta di nuovi valori a MyEnum.

Cosa dici tu, comunità?


Diciamo che MyEnum è un tipo non annullabile.
sd

3
"Oggi" è non annullabile. Che ne dici di domani quando non gestirai più il codice. O che dire quando "MyBiz" viene aggiunto all'enum ma non è il caso? I commenti di Caleb sulla manutenzione sono molto germani.

1
Insegna al tuo compilatore che è un errore fatale se c'è un interruttore che non copre tutti i casi.

Cosa succede se qualcuno lancia un valore non valido per MyEnumpoi lo passa attraverso il tuo interruttore?
Mawg dice di ripristinare Monica il

1
Quale lingua? Se Java, dovresti inserire un metodo all'interno dell'Enum e chiamarlo semplicemente (polimorfismo), eliminando completamente l' switchaffermazione.
user949300,

Risposte:


34

Includere il caso predefinito non cambia il modo in cui funziona il codice, ma rende il codice più gestibile. Facendo rompere il codice in modo ovvio (registra un messaggio e genera un'eccezione), includi una grande freccia rossa per il tirocinante che la tua azienda assume la prossima estate per aggiungere un paio di funzionalità. La freccia dice: "Ehi, tu! Sì, sto parlando con TE! Se hai intenzione di aggiungere un altro valore all'enum, è meglio aggiungere un caso anche qui." Questo ulteriore sforzo potrebbe aggiungere qualche byte al programma compilato, che è qualcosa da considerare. Ma salverà anche qualcuno (forse anche il tuo futuro) da qualche parte tra un'ora e un giorno di improduttivi graffi alla testa.


Aggiornamento: la situazione sopra descritta, ovvero la protezione da valori aggiunti a un elenco in un secondo momento, può essere rilevata anche dal compilatore. Clang (e gcc, credo) emetterà un avviso di default se si attiva un tipo elencato ma non si ha un caso che copre tutti i possibili valori dell'enumerazione. Quindi, ad esempio, se rimuovi il defaultcaso dal tuo switch e aggiungi un nuovo valore MyBazall'enumerazione, riceverai un avviso che dice:

Enumeration value 'MyBaz' not handled in switch

Lasciare che il compilatore rilevi i casi scoperti è che elimina in gran parte la necessità di quel defaultcaso irraggiungibile che ha ispirato la tua domanda in primo luogo.


2
Ok, mi hai convinto :) Devo solo accettare l'ammaccatura nei miei numeri di copertura del codice.
sd

@st Non c'è motivo per cui non puoi testare quel codice. È sufficiente eseguire una build di test che compila in modo condizionale un valore aggiuntivo nell'enumerazione, quindi scrivere un test di unità che lo utilizza. Forse non è l'ideale, ma probabilmente non è l'unico caso in cui è necessario testare il codice che normalmente non verrà mai raggiunto.
Caleb,

Oppure, si lancia un valore non enum nel tipo di enum e lo si utilizza.
Mawg dice di ripristinare Monica il

5

Ne ho parlato anche con un collega stamattina: è davvero un peccato, ma penso che la gestione del default sia necessaria per motivi di sicurezza, per due motivi:

In primo luogo, come menziona il tuo collega, esso rende il codice a prova di futuro rispetto ai nuovi valori aggiunti all'enum. Questa può o meno sembrare una possibilità, ma è sempre lì.

Ancora più importante, a seconda della lingua / compilatore, potrebbe essere possibile avere valori che non sono membri dell'enum nella variabile commutata. Ad esempio, in C #:

MyEnum myEnum = (MyEnum) 3; // This could come from anywhere, maybe parsed from text?
// ... code goes on for a while

switch ( myEnum )
{
    case MyEnum.A:
        // ... handle A case
        break;
    case MyEnum.B:
        // ... handle B case
        break;
}

// ... code that expects either A or B to have happened

Aggiungendo il semplice case default:e lanciando un'eccezione, ti sei protetto da questo strano caso in cui "nulla" accade ma "qualcosa" dovrebbe essere accaduto.

Sfortunatamente, praticamente ogni volta che scrivo più un'istruzione switch, è perché sto controllando i casi di una enum. Vorrei davvero che il comportamento "lancio di default" potesse essere applicato dalla lingua stessa (almeno aggiungendo una parola chiave).


3

L'aggiunta di un caso predefinito anche se non ti aspetti di raggiungerlo può essere una buona cosa. Renderà il debug molto più semplice se il tuo codice genera immediatamente un'eccezione "Questo non dovrebbe essere successo" piuttosto che successivamente nel programma, generando qualche eccezione misteriosa o restituendo risultati imprevisti senza errori.


2

Dico:

Prova ad aggiungere un altro tipo a MyEnum. Quindi cambia questa riga:

MyEnum myEnum = GetMyEnum();

per

MyEnum myEnum = SomethingElse;

Quindi esegui il codice con il caso predefinito e senza il caso predefinito. Quale comportamento preferisci?

Avere il caso predefinito può anche essere utile per intercettare NULLvalori e prevenire NullPointerExceptions.


-1

Se avessi idea di come risparmiare tempo mach insistendo sul caso predefinito, non dovrai porre la domanda. Silenziosamente non fare nulla in una condizione di errore non è accettabile, cogli silenziosamente eccezioni che non dovrebbero mai accadere? Lasciare "mine" per i programmatori che ti seguono, allo stesso modo inaccettabili.

Se il tuo codice non verrà mai cambiato o modificato ed è privo di bug al 100%, lasciare fuori il caso predefinito potrebbe essere OK.

Ada (il nonno di solidi linguaggi di programmazione) non compilerà un interruttore su un enum, a meno che non siano coperti tutti gli enum o non ci sia un gestore predefinito - questa funzione è nella mia lista dei desideri per ogni lingua. Il nostro standard di codifica Ada afferma che con switch on enum, la gestione esplicita di tutti i valori, senza default è il modo preferito per gestire questa situazione.


Perché tutti i -1?
Mattnz,

2
Non ti ho -1, ma immagino che sarebbe dovuto al tuo atteggiamento;)
Friek,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.