Switch Statement Fallthrough … dovrebbe essere consentito?

Per tutto il tempo che posso ricordare, ho evitato di usare la dichiarazione di commutazione. In realtà, non riesco a ricordare che sia mai entrato nella mia coscienza come un modo ansible di fare le cose come mi è stato trapanato in testa presto che non era nient’altro che un bug nella dichiarazione switch. Tuttavia, oggi mi sono imbattuto in un codice che lo utilizza in base alla progettazione, il che mi ha fatto subito chiedersi cosa ne pensano tutti nella comunità.

È qualcosa che un linguaggio di programmazione non dovrebbe consentire esplicitamente (come fa C #, sebbene fornisca una soluzione alternativa) o è una caratteristica di un linguaggio che è abbastanza potente da lasciare nelle mani del programmatore?

Edit: non ero abbastanza specifico per quello che intendevo per fallthrough. Io uso molto questo tipo:

switch(m_loadAnimSubCt){ case 0: case 1: // Do something break; case 2: case 3: case 4: // Do something break; } 

Tuttavia, sono preoccupato per qualcosa di simile.

  switch(m_loadAnimSubCt){ case 0: case 1: // Do something but fall through to the other cases // after doing it. case 2: case 3: case 4: // Do something else. break; } 

In questo modo ogni volta che il caso è 0, 1 farà tutto nell’istruzione switch. Ho visto questo in base alla progettazione e non so se sono d’accordo che le istruzioni switch dovrebbero essere utilizzate in questo modo. Penso che il primo esempio di codice sia molto utile e sicuro. Il secondo sembra pericoloso.

Può dipendere da ciò che consideri avveniristico. Sto bene con questo genere di cose:

 switch (value) { case 0: result = ZERO_DIGIT; break; case 1: case 3: case 5: case 7: case 9: result = ODD_DIGIT; break; case 2: case 4: case 6: case 8: result = EVEN_DIGIT; break; } 

Ma se hai un’etichetta del caso seguita da un codice che ricade su un’altra etichetta del caso, lo considererei quasi sempre il male. Forse spostare il codice comune in una funzione e chiamare da entrambi i posti sarebbe un’idea migliore.

E per favore nota che io uso la definizione delle FAQ del C ++ di “male”

È un’arma a doppio taglio. A volte molto utile, spesso pericoloso.

Quando va bene? Quando vuoi 10 casi tutti elaborati allo stesso modo …

 switch (c) { case 1: case 2: ... do some of the work ... /* FALLTHROUGH */ case 17: ... do something ... break; case 5: case 43: ... do something else ... break; } 

L’unica regola che mi piace è che, se mai fai qualcosa di speciale in cui escludi la pausa, hai bisogno di un commento chiaro / * FALLTHROUGH * / per indicare che era tua intenzione.

Fallthrough è davvero una cosa utile, a seconda di cosa stai facendo. Considera questo modo pulito e comprensibile di organizzare le opzioni:

 switch ($someoption) { case 'a': case 'b': case 'c': // do something break; case 'd': case 'e': // do something else break; } 

Immagina di farlo con if / else. Sarebbe un casino.

Hai sentito parlare del dispositivo di Duff ? Questo è un ottimo esempio di utilizzo di switch fallthrough.

È una funzionalità che può essere utilizzata e può essere sfruttata, come quasi tutte le funzionalità del linguaggio.

Può essere molto utile un paio di volte, ma in generale, il no-fallthrough è il comportamento desiderato. Il fallthrough dovrebbe essere permesso, ma non implicito.

Un esempio, per aggiornare vecchie versioni di alcuni dati:

 switch (version) { case 1: // update some stuff case 2: // update more stuff case 3: // update even more stuff case 4: // and so on } 

Mi piacerebbe una syntax diversa per fallbacks in switch, qualcosa come, errr ..

 switch(myParam) { case 0 or 1 or 2: // do something; break; case 3 or 4: // do something else; break; } 

Nota: questo sarebbe già ansible con le enumerazioni, se dichiari tutti i casi sulla tua enumerazione usando le bandiere giusto? Non sembra neanche così grave, i casi potrebbero (dovrebbe?) Essere ben parte del tuo enum già.

Forse questo sarebbe un bel caso (nessun gioco di parole) per un’interfaccia fluente usando i metodi di estensione? Qualcosa come, errr …

 int value = 10; value.Switch() .Case(() => { /* do something; */ }, new {0, 1, 2}) .Case(() => { /* do something else */ } new {3, 4}) .Default(() => { /* do the default case; */ }); 

Anche se questo è probabilmente ancora meno leggibile: P

Come con qualsiasi altra cosa: se usato con cura, può essere uno strumento elegante.

Tuttavia, penso che gli inconvenienti più che giustificano NON usarlo e, infine, non consentirlo più (C #). Tra i problemi ci sono:

  • è facile “dimenticare” una pausa
  • non è sempre ovvio per i manutentori del codice che una rottura omessa è stata intenzionale

buon uso di un passaggio / caso:

 switch (x) { case 1: case 2: case 3: do something break; } 

BAAAAAD uso di un passaggio / caso:

 switch (x) { case 1: some code case 2: some more code case 3: even more code break; } 

Questo può essere riscritto usando se / else costruisce senza alcuna perdita a mio avviso.

La mia ultima parola: stai lontano dalle etichette caso-caso come nell’esempio BAD, a meno che tu non stia mantenendo il codice legacy in cui questo stile è usato e ben compreso.

Potente e pericoloso. Il più grande problema con il fall-through è che non è esplicito. Ad esempio, se ti imbatti in un codice modificato di frequente che ha un interruttore con fall-through, come fai a sapere che è intenzionale e non un bug?

Ovunque lo usi, mi assicuro che sia correttamente commentato:

 switch($var) { case 'first': // fall-through case 'second': i++; break; } 

Non mi piace che le mie istruzioni sugli switch vengano ignorate: è troppo incline agli errori e difficile da leggere. L’unica eccezione è quando più istruzioni case fanno esattamente la stessa cosa.

Se esiste un codice comune che più rami di un’istruzione switch vogliono utilizzare, lo estrao in una funzione comune separata che può essere chiamata in qualsiasi ramo.

Usare il fall-through come nel primo esempio è chiaramente OK, non lo considererei un vero e proprio fall-through.

Il secondo esempio è pericoloso e (se non commentato estesamente) non ovvio. Insegno ai miei studenti a non usare tali costrutti A MENO CHE ritengano che valga la pena di dedicare un blocco di commenti ad esso, il che descrive che si tratta di una svolta intenzionale e perché questa soluzione è migliore delle alternative. Questo scoraggia l’uso sciatto, ma lo rende comunque consentito nei casi in cui è utilizzato in modo vantaggioso.

Questo è più o meno equivalente a ciò che abbiamo fatto nei progetti spaziali quando qualcuno voleva violare lo standard di codifica: dovevano fare domanda per la dispensa (e sono stato chiamato a dare consigli sulla sentenza).

il pensiero di caduta dovrebbe essere usato solo quando è usato come tabella di salto in un blocco di codice. Se c’è qualche parte del codice con un’interruzione incondizionata prima di più casi, tutti i gruppi di casi dovrebbero finire in questo modo. Qualsiasi altra cosa è “ctriggers”.

In alcuni casi, l’uso di fall-through è un atto di pigrizia da parte del programmatore – potrebbero usare una serie di || dichiarazioni, ad esempio, ma invece utilizzare una serie di casi di switch ‘catch-all’.

Detto questo, li ho trovati particolarmente utili quando so che alla fine avrò comunque bisogno delle opzioni (ad esempio in una risposta al menu), ma non ho ancora implementato tutte le scelte. Allo stesso modo, se stai facendo un fall-through sia per la ‘a’ che per la ‘A’, trovo che sia sostanzialmente più pulito usare l’interruttore dell’intervento rispetto a un’istruzione composta.

Probabilmente è una questione di stile e di come pensano i programmatori, ma in genere non mi piace rimuovere i componenti di una lingua in nome della “sicurezza” – ed è per questo che tendo verso C e le sue varianti / discendenti più di, diciamo, Giava. Mi piace essere in grado di scimmiottare con i puntatori e simili, anche quando non ho una “ragione” per farlo.