Cambia istruzione fall-through ... dovrebbe essere consentito? [chiuso]


120

Per tutto il tempo che posso ricordare ho evitato di utilizzare la fall-through delle istruzioni switch. In realtà, non riesco a ricordare che sia mai entrato nella mia coscienza come un possibile modo di fare le cose poiché mi è stato perforato nella testa all'inizio che non era altro che un bug nella dichiarazione di cambio. Tuttavia, oggi mi sono imbattuto in un codice che lo utilizza in base alla progettazione, il che mi ha fatto chiedere immediatamente cosa pensano tutti nella comunità del fall-through dell'istruzione switch.

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

Modifica: non ero abbastanza specifico per ciò che intendevo per fall-through. 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. L'ho visto in base alla progettazione e non so se sono d'accordo che le istruzioni switch dovrebbero essere usate in questo modo. Penso che il primo esempio di codice sia molto utile e sicuro. Il secondo sembra piuttosto pericoloso.



51
Non sono d'accordo sul non costruttivo. 27 voti positivi sulla domanda dicono che ci sono altri che la pensano allo stesso modo.
Modalità Wes

2
"non costruttivo" è un motivo vicino vecchio e un po 'vago. Al giorno d'oggi, tali domande possono e dovrebbero essere contrassegnate per la chiusura come puramente basate sull'opinione. SO è per domande a cui è possibile rispondere sul codice o sul design, non "qual è l'opinione della" comunità "[qualunque essa sia] sulla mia opinione sulla caratteristica X". Programmers.SE sarebbe più adatto, ma anche in questo caso, il valore di una domanda così ampia e guidata dall'opinione pubblica è ancora altamente dubbio.
underscore_d

1
Ci sono casi d'uso molto chiari in cui viene utilizzato il fallthrough, quindi non sono sicuro del motivo per cui sarebbe basato sull'opinione. Sono d'accordo che questa domanda sia un po 'poco chiara, ma non avrebbe dovuto essere chiusa in quanto non costruttiva. Forse solo modificato per essere un caso più chiaro di: "Quando viene utilizzato il fallthrough?" Che ha una risposta chiara. I buoni testi C ++ vanno oltre questo, è fonte di confusione per i nuovi programmatori (o anche per programmatori più esperti di altri linguaggi), quindi sembra una buona domanda per SO. In effetti, sembra una domanda SO prototipicamente buona. Anche se il modo in cui è stato chiesto non era perfetto.
eric

Risposte:


89

Può dipendere da ciò che consideri fallimentare. 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 di case seguita da codice che ricade su un'altra etichetta di case, lo considererei quasi sempre male. Forse spostare il codice comune in una funzione e chiamare da entrambi i punti sarebbe un'idea migliore.

E tieni presente che utilizzo la definizione di "male" delle FAQ in C ++


Sono d'accordo con te: non penso al tuo esempio come fallthrough, e se c'è un caso in cui voglio eseguire più blocchi di codice tramite fallthrough, probabilmente c'è un modo migliore per farlo.
Dave DuPlantis,

10
+1 semplicemente per quella definizione di "male". Lo prenderò in prestito, grazie ;-)
Joachim Sauer

Capisco il tuo punto e hai ragione, ma il tuo esempio è davvero pessimo. Nessuno dovrebbe testare cifre di prova come questa. Scnr, la tua risposta è comunque giusta.
Tim Büthe

Caso in questione, questa è proprio la cosa che C # non consente. Probabilmente per una ragione :-)
Joey

Purtroppo la definizione di male sembra essere andata offline
HopefullyHelpful

57

È un'arma a doppio taglio. A volte è molto utile, ma spesso pericoloso.

Quando va bene? Quando vuoi che 10 casi vengano elaborati tutti 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 fai qualcosa di stravagante dove escludi l'interruzione, hai bisogno di un commento chiaro / * FALLTHROUGH * / per indicare che era la tua intenzione.


3
+1! Sono d'accordo sul fatto che a volte sia la risposta giusta, e sono DOPPIO d'accordo che quando è in base alla progettazione, dovrebbe essere commentata. (In una revisione del codice, farei in modo che l'altro sviluppatore commenti un fall-through non vuoto di progettazione. Non che accada; siamo un negozio C # in cui non è supportato :)
John Rudy

4
Io stesso uso un commento / * FALL THROUGH * / nel mio codice dove appropriato. =]
strager

2
Se utilizzi PC-Lint, devi inserire / * - fallthrough * /, altrimenti si lamenta.
Steve Melnikoff

1
tuttavia, se vuoi chiarire che il comportamento è intenzionale, potresti anche if(condition > 1 && condition <10) {condition = 1}; switch(...salvarti casi (per un aspetto chiaro) e tutti possono vedere che volevi davvero che quei casi attivassero la stessa azione.
Marco

1
Questa è ancora una pessima codifica. Il primo caso dovrebbe chiamare una funzione, il secondo dovrebbe chiamare la stessa funzione quindi chiamare una seconda funzione.
BlueRaja - Danny Pflughoeft

21

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

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


Impara qualcosa di nuovo ogni giorno - grazie! Mi dispiace per i poveri sviluppatori che affrontano tali contorsioni però.
Justin R.

Wow, è qualcosa su cui avvolgere il tuo cervello.
Fostah

6
Nota il commento citato di Duff in quell'articolo: "Questo codice forma una sorta di argomento in quel dibattito, ma non sono sicuro che sia a favore o contro."
John Carter

Il dispositivo di Duff è chiaramente malvagio
Marjeta

21

Il fall-through è davvero una cosa utile, a seconda di cosa stai facendo. Considera questo modo chiaro e comprensibile per 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.


1
Trovo che questo tipo di fallthrough sia molto utile
Mitchel Sellers

se "fare qualcosa" è più di un piccolo interruttore, piuttosto sarà il pasticcio che se / altro. Con if / else è ovunque chiaro quale variabile viene testata. Anche questo piccolo esempio non starebbe così male con if / else ai miei occhi
grenix

10

Può essere molto utile alcune volte, ma in generale, nessun fall-through è il comportamento desiderato. Il fall-through dovrebbe essere consentito, 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
}

6

Mi piacerebbe una sintassi diversa per i fallback negli interruttori, 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à possibile con le enumerazioni, se dichiari tutti i casi sulla tua enumerazione usando i flag, giusto? Neanche suona così male; i casi potrebbero (dovrebbero?) essere già parte della tua enumerazione.

Forse questo sarebbe un bel caso (nessun gioco di parole) per un'interfaccia fluente che utilizza metodi di estensione? Qualcosa come, ehm ...

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 probabilmente è ancora meno leggibile: P


1
Mi piace molto il primo suggerimento. Si legge molto bene e salva alcune righe di codice. Il secondo ha impiegato un minuto per avvolgere il mio cervello, ma ha senso, ma sembra pronto per essere un disastro.
Fostah

1
Sì, è quello che pensavo anch'io, era solo una scoreggia mentale casuale che ho messo giù, nel tentativo di trovare un modo diverso di cambiare usando i costrutti linguistici esistenti.
Erik van Brakel

Ho risposto a questa domanda e mi piace davvero! Avevo un'idea su come migliorare la leggibilità di questo, ma SO non mi permette di pubblicare commenti su più righe :( Qualcuno dovrebbe implementarlo proprio come POC, sembra divertente da implementare e utilizzare (:
Victor Elias

5

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

Tuttavia, penso che gli svantaggi più che giustificano il 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 un'interruzione omessa fosse intenzionale

Buon uso di un interruttore / caso di caduta:

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

Baaaaad uso di un interruttore / caso di caduta:

switch (x)
{
case 1:
    Some code
case 2:
    Some more code
case 3:
    Even more code
    break;
}

Questo può essere riscritto usando i costrutti if / else senza alcuna perdita a mio parere.

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


3
Puoi riscrivere la maggior parte dei costrutti del linguaggio usando if () e goto ... che però non giustifica l'abbandono di un costrutto esplicito.
Shog9

2
Lo so, ma costrutti switch / case che usano esplicitamente il "caso 1: un po 'di codice caso 2: un po' di più caso di codice 3: interruzione di codice finale;" può e deve essere riscritto usando if / else if (la mia opinione).
steffenj

1
Nota che lo switch può essere molte volte più veloce di un elenco di if-elses. Switch utilizza una tabella di salto o, quando ciò non è possibile, i suoi salti condizionali saranno strutturati in un albero decisionale invece che in un elenco lineare di salti condizionali. Le istruzioni If-else nidificate ben strutturate saranno, nella migliore delle ipotesi, altrettanto veloci.
Jochem Kuijpers

4

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

Ovunque lo uso, mi assicuro che sia commentato correttamente:

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

2
L'intento è ovvio se non esiste un codice per "first". Se si codifica lì e si desidera eseguire anche il codice per il caso "secondo", il commento è importante. Ma eviterei comunque quello scenario.
Joel Coehoorn,

1
@ Joel - Sono completamente d'accordo. Nel mio negozio, in questi casi, le persone hanno inserito commenti casuali e penso che riduca la leggibilità. Se c'è del codice lì, però, inserisci il commento autunnale.
Fred Larson

Cosa si intende per "non esplicito". Questo è il motivo per cui ci rompiamo; Altre lingue lo fanno. È solo un problema per le persone che non hanno imparato le lingue in ordine. C # lo richiede per il caso predefinito ffs, senza motivo per imho.
mckenzm

3

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

Il secondo esempio è pericoloso e (se non ampiamente commentato) non ovvio. Insegno ai miei studenti a non utilizzare tali costrutti a meno che non ritengano che valga la pena dedicarvi un blocco di commenti, che descrive che si tratta di un errore intenzionale e perché questa soluzione è migliore delle alternative. Questo scoraggia l'uso sciatto, ma lo rende comunque consentito nei casi in cui viene utilizzato a vantaggio.

Questo è più o meno equivalente a quello che abbiamo fatto nei progetti spaziali quando qualcuno voleva violare lo standard di codifica: doveva chiedere la dispensa (e io sono stato chiamato a consigliare sulla sentenza).


2

Non mi piace che le mie switchaffermazioni falliscano: sono troppo soggette a errori e difficili da leggere. L'unica eccezione è quando più caseistruzioni fanno tutte esattamente la stessa cosa.

Se c'è del codice comune che più rami di un'istruzione switch vogliono usare, lo estraggo in una funzione comune separata che può essere chiamata in qualsiasi ramo.


1

In alcuni casi, utilizzare i fall-through è un atto di pigrizia da parte del programmatore: potrebbero utilizzare una serie di || istruzioni, 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 di menu), ma non ho ancora implementato tutte le scelte. Allo stesso modo, se stai facendo un fall-through sia per 'a' che per 'A', trovo sostanzialmente più pulito usare il fall-through dell'interruttore rispetto a un'istruzione if composta.

Probabilmente è una questione di stile e di come pensano i programmatori, ma generalmente non mi piace rimuovere i componenti di un linguaggio in nome della 'sicurezza' - motivo per cui tendo più al C e alle sue varianti / discendenti che, diciamo, Giava. Mi piace essere in grado di fare le scimmie con suggerimenti e simili, anche quando non ho "motivo" per farlo.


In senso stretto. Le istruzioni switch passano tramite un valore impostato a un gruppo di posizioni di codice specifiche. Sebbene il compilatore probabilmente catturerebbe tali cose, c'è un valore di velocità e correttezza per un'istruzione switch su una serie di istruzioni o. Se p è 0 o p è 1 o p è 2. In realtà dovevo valutare se p era 0 ep era 1, piuttosto che saltare alla posizione p = 2 nel codice. Che è risultato essere identico a p0 e p1. Ma non ho mai dovuto controllarli.
Tatarize

1

Il fall-through dovrebbe essere utilizzato solo quando viene utilizzato come tabella di salto in un blocco di codice. Se c'è una parte del codice con un'interruzione incondizionata prima di più casi, tutti i gruppi di casi dovrebbero terminare in questo modo.

Qualsiasi altra cosa è "cattiva".

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.