Istruzione Switch con ritorni - correttezza del codice


91

Diciamo che ho codice in C con approssimativamente questa struttura:

switch (something)
{
    case 0:
      return "blah";
      break;

    case 1:
    case 4:
      return "foo";
      break;

    case 2:
    case 3:
      return "bar";
      break;

    default:
      return "foobar";
      break;
}

Ora ovviamente, le breaks non sono necessarie affinché il codice funzioni correttamente, ma sembra una cattiva pratica se non me le metto lì.

Cosa ne pensi? Va bene rimuoverli? O le manterresti per una maggiore "correttezza"?

Risposte:


137

Rimuovi le breakdichiarazioni. Non sono necessari e forse alcuni compilatori emetteranno avvisi "Codice irraggiungibile" .


2
Lo stesso vale con altre istruzioni di salto di controllo incondizionate, come continueo goto- è idiomatico usarle al posto di break.
caf

31

Prenderei una strada completamente diversa. Non RITORNO nel mezzo del metodo / funzione. Invece, metti semplicemente il valore di ritorno in una variabile locale e invialo alla fine.

Personalmente, trovo che quanto segue sia più leggibile:

String result = "";

switch (something) {
case 0:
  result = "blah";
  break;
case 1:
  result = "foo";
  break;
}

return result;

24
La filosofia "One Exit" ha senso in C, dove è necessario assicurarsi che le cose siano pulite correttamente. Considerando che in C ++ puoi essere escluso dalla funzione in qualsiasi momento da un'eccezione, la filosofia dell'uscita non è molto utile in C ++.
Billy ONeal

29
In teoria questa è un'ottima idea, ma spesso richiede blocchi di controllo aggiuntivi che possono ostacolare la leggibilità.
mikerobi

8
Il motivo principale per cui faccio le cose in questo modo è che nelle funzioni più lunghe è molto facile perdere il vettore di uscita durante la scansione del codice. Tuttavia, quando l'uscita è sempre alla fine, è più facile capire rapidamente cosa sta succedendo.
NotMe

7
Bene, quello, e restituire i blocchi nel mezzo del codice mi ricorda solo l'abuso di GOTO.
NotMe

5
@ Chris: Nelle funzioni più lunghe, sono totalmente d'accordo - ma questo di solito parla di problemi più grandi, quindi rifattorizzalo. In molti casi, è una funzione banale che ritorna su un interruttore ed è molto chiaro cosa dovrebbe accadere, quindi non sprecare la potenza del cervello per tracciare una variabile aggiuntiva.
Stephen

8

Personalmente rimuoverei i resi e manterrei le pause. Userei l'istruzione switch per assegnare un valore a una variabile. Quindi restituisci quella variabile dopo l'istruzione switch.

Sebbene questo sia un punto discutibile, ho sempre pensato che un buon design e incapsulamento significhino un modo per entrare e un modo per uscire. È molto più semplice garantire la logica e non perderti accidentalmente il codice di pulizia basato sulla complessità ciclomatica della tua funzione.

Un'eccezione: la restituzione anticipata va bene se viene rilevato un parametro errato all'inizio di una funzione, prima dell'acquisizione di qualsiasi risorsa.


Potrebbe essere buono per questo particolare switch, ma non necessariamente migliore in generale. Bene, diciamo che l'istruzione switch all'interno di una funzione precede altre 500 righe di codice che sono valide solo quando lo sono determinati casi true. Qual è lo scopo di eseguire tutto quel codice extra per i casi che non hanno bisogno di eseguirlo? non è meglio solo returndentro switchper quelle cases?
Fr0zenFyr

6

Mantieni le interruzioni: è meno probabile che tu incorra in problemi se / quando modifichi il codice in un secondo momento se le interruzioni sono già presenti.

Detto questo, è considerato da molti (me compreso) come una cattiva pratica tornare dalla metà di una funzione. Idealmente una funzione dovrebbe avere un punto di ingresso e un punto di uscita.


5

Rimuovili. È idiomatico tornare dalle casedichiarazioni, altrimenti è rumore di "codice irraggiungibile".


5

Li rimuoverei. Nel mio libro, un codice morto del genere dovrebbe essere considerato un errore perché ti fa fare un doppio tentativo e chiediti "Come potrei mai eseguire quella riga?"


4

Normalmente scriverei il codice senza di loro. IMO, il codice morto tende a indicare trascuratezza e / o mancanza di comprensione.

Naturalmente, prenderei anche in considerazione qualcosa come:

char const *rets[] = {"blah", "foo", "bar"};

return rets[something];

Modifica: anche con il post modificato, questa idea generale può funzionare bene:

char const *rets[] = { "blah", "foo", "bar", "bar", "foo"};

if ((unsigned)something < 5)
    return rets[something]
return "foobar";

Ad un certo punto, soprattutto se i valori di input sono sparsi (ad esempio, 1, 100, 1000 e 10000), si desidera invece un array sparse. Puoi implementarlo come un albero o una mappa ragionevolmente bene (anche se, ovviamente, un interruttore funziona ancora anche in questo caso).


Modificato il post per riflettere il motivo per cui questa soluzione non funzionerebbe bene.
houbysoft

@ your edit: Sì, ma ci vorrebbe più memoria, ecc. IMHO l'interruttore è il modo più semplice, che è quello che voglio in una funzione così piccola (fa solo l'interruttore).
houbysoft

3
@houbysoft: guarda attentamente prima di concludere che ci vorrà memoria extra. Con un tipico compilatore, l'uso di "foo" due volte (ad esempio) utilizzerà due puntatori a un singolo blocco di dati, non replicherà i dati. Puoi anche aspettarti un codice più breve. A meno che tu non abbia molti valori ripetuti, spesso salverà la memoria in generale.
Jerry Coffin

vero. Mi atterrò comunque alla mia soluzione perché l'elenco dei valori è molto lungo e un interruttore sembra più bello.
houbysoft

1
In effetti, la risposta più elegante ed efficiente a un problema come questo, quando i valori dello switch sono contigui e il tipo di dati omogeneo, è utilizzare un array per evitare del tutto uno switch.
Dwayne Robinson

2

Direi rimuoverli e definire un valore predefinito: branch.


Se non esiste un valore predefinito ragionevole, non è necessario definire un valore predefinito.
Billy ONeal

ce n'è uno, e ne ho definito uno, semplicemente non l'ho inserito nella domanda, l'ho modificato ora.
houbysoft

2

Non sarebbe meglio avere un array con

arr[0] = "blah"
arr[1] = "foo"
arr[2] = "bar"

e fare return arr[something];?

Se riguarda la pratica in generale, dovresti mantenere le breakdichiarazioni nell'interruttore. Se non hai bisogno di returndichiarazioni in futuro, diminuisce la possibilità che passi alla successiva case.


Modificato il post per riflettere il motivo per cui questa soluzione non funzionerebbe bene.
houbysoft

2

Per "correttezza", i blocchi a entrata singola e uscita singola sono una buona idea. Almeno lo erano quando ho conseguito la laurea in informatica. Quindi probabilmente dichiarerei una variabile, assegnerei ad essa nello switch e tornerei una volta alla fine della funzione


2

Cosa ne pensi? Va bene rimuoverli? O le manterresti per una maggiore "correttezza"?

Va bene rimuoverli. L'utilizzo return è esattamente lo scenario in cui breaknon dovrebbe essere utilizzato.


1

Interessante. Il consenso dalla maggior parte di queste risposte sembra essere che l' breakaffermazione ridondante è un disordine inutile. D'altra parte, ho letto la breakdichiarazione in un interruttore come la "chiusura" di un caso. casei blocchi che non finiscono in una breaktendono a saltarmi addosso come potenziale caduta attraverso bug.

So che non è così quando c'è un returninvece di a break, ma è così che i miei occhi 'leggono' i blocchi del case in un interruttore, quindi personalmente preferirei che ciascuno casefosse accoppiato con a break. Ma molti compilatori si lamentano del breakdopo returnessere superfluo / irraggiungibile, e apparentemente mi sembra comunque di essere in minoranza.

Quindi sbarazzarsi di quanto breaksegue a return.

NB: tutto questo sta ignorando se violare la regola di entrata / uscita singola sia una buona idea o meno. Per quanto riguarda ciò, ho un'opinione che purtroppo cambia a seconda delle circostanze ...


0

Dico rimuoverli. Se il tuo codice è così illeggibile che devi inserire delle interruzioni "per essere al sicuro", dovresti riconsiderare il tuo stile di codifica :)

Inoltre ho sempre preferito non mescolare interruzioni e ritorni nell'istruzione switch, ma piuttosto attenermi a uno di essi.


0

Personalmente tendo a perdere la breaks. Forse una fonte di questa abitudine proviene dalle procedure della finestra di programmazione per le app di Windows:

LRESULT WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_SIZE:
            return sizeHandler (...);
        case WM_DESTROY:
            return destroyHandler (...);
        ...
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Personalmente trovo questo approccio molto più semplice, succinto e flessibile rispetto alla dichiarazione di una variabile di ritorno impostata da ciascun gestore, per poi restituirla alla fine. Dato questo approccio, i messaggi di posta breakelettronica sono ridondanti e quindi dovrebbero scomparire: non servono a scopi utili (sintatticamente o visivamente IMO) e gonfiano solo il codice.


0

Penso che le * pause * siano lì per uno scopo. Serve a mantenere viva l '"ideologia" della programmazione. Se dobbiamo solo "programmare" il nostro codice senza coerenza logica, forse sarebbe leggibile ora, ma provateci domani. Prova a spiegarlo al tuo capo. Prova a eseguirlo su Windows 3030.

Bleah, l'idea è molto semplice:


Switch ( Algorithm )
{

 case 1:
 {
   Call_911;
   Jump;
 }**break**;
 case 2:
 {
   Call Samantha_28;
   Forget;
 }**break**;
 case 3:
 {
   Call it_a_day;
 }**break**;

Return thinkAboutIt?1:return 0;

void Samantha_28(int oBed)
{
   LONG way_from_right;
   SHORT Forget_is_my_job;
   LONG JMP_is_for_assembly;
   LONG assembly_I_work_for_cops;

   BOOL allOfTheAbove;

   int Elligence_says_anyways_thinkAboutIt_**break**_if_we_code_like_this_we_d_be_monkeys;

}
// Sometimes Programming is supposed to convey the meaning and the essence of the task at hand. It is // there to serve a purpose and to keep it alive. While you are not looking, your program is doing  // its thing. Do you trust it?
// This is how you can...
// ----------
// **Break**; Please, take a **Break**;

/ * Solo una piccola domanda però. Quanto caffè hai bevuto leggendo quanto sopra? A volte rompe il sistema * /


0

Codice di uscita a un certo punto. Ciò fornisce una migliore leggibilità al codice. L'aggiunta di istruzioni di ritorno (più uscite) in mezzo renderà difficile il debug.


0

Se hai un tipo di codice "lookup", potresti impacchettare la clausola switch-case in un metodo a parte.

Ne ho alcuni in un sistema "hobby" che sto sviluppando per divertimento:

private int basePerCapitaIncomeRaw(int tl) {
    switch (tl) {
        case 0:     return 7500;
        case 1:     return 7800;
        case 2:     return 8100;
        case 3:     return 8400;
        case 4:     return 9600;
        case 5:     return 13000;
        case 6:     return 19000;
        case 7:     return 25000;
        case 8:     return 31000;
        case 9:     return 43000;
        case 10:    return 67000;
        case 11:    return 97000;
        default:    return 130000;
    }
}

(Sì. Questo è lo spazio GURPS ...)

Sono d'accordo con altri sul fatto che nella maggior parte dei casi dovresti evitare più di un ritorno in un metodo, e riconosco che questo potrebbe essere stato implementato meglio come array o qualcos'altro. Ho appena scoperto che switch-case-return è una corrispondenza piuttosto semplice con una tabella di ricerca con una correlazione 1-1 tra input e output, come la cosa sopra (i giochi di ruolo ne sono pieni, sono sicuro che esistono in altri anche "imprese"): D

D'altra parte, se la clausola case è più complessa, o succede qualcosa dopo l'istruzione switch, non consiglierei di usare return in essa, ma piuttosto impostare una variabile nello switch, terminarla con una pausa e return il valore della variabile alla fine.

(Sulla ... terza? Mano ... puoi sempre rifattorizzare uno switch nel suo metodo ... dubito che avrà un effetto sulle prestazioni, e non mi sorprenderebbe se i compilatori moderni potessero persino riconoscerlo come qualcosa che potrebbe essere inline ...)

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.