È una cattiva pratica usare l'interruzione in un ciclo for? [chiuso]


123

È una cattiva pratica usare l' breakistruzione all'interno di un forciclo ?

Dire, sto cercando un valore in un array. Confronta all'interno di un ciclo for e quando viene trovato il valore, break;per uscire dal ciclo for.

È una cattiva pratica? Ho visto l'alternativa utilizzata: definire una variabile vFounde impostarla su true quando il valore viene trovato e controllare vFoundla forcondizione dell'istruzione. Ma è necessario creare una nuova variabile proprio a questo scopo?

Sto chiedendo nel contesto di un normale ciclo C o C ++ for.

PS: Le linee guida di codifica MISRA sconsigliano l'uso di break.


28
Non breakgoto
piazzarti

2
Ehm, non li ho inseriti nella stessa lega in cui sono passati a ... Le regole MISRA specificano il break, se ricordo bene.
kiki

1
L'unico motivo per cui riesco a pensare di non usare l'interruzione all'interno di un ciclo è quando devi ancora elaborare più elementi che potrebbero cambiare il risultato di ciò che stai facendo ...
Younes

4
Le regole MISRA sono state allentate: misra.org.uk/forum/viewtopic.php?f=75&t=298
Johan Kotlinski

Risposte:


122

Molte risposte qui, ma non ho ancora visto questo menzionato:

La maggior parte dei "pericoli" associati all'uso breako continuein un ciclo for vengono annullati se si scrivono cicli ordinati e facilmente leggibili. Se il corpo del tuo ciclo si estende su diverse lunghezze dello schermo e ha più sottoblocchi annidati, sì, potresti facilmente dimenticare che parte del codice non verrà eseguito dopo l'interruzione. Se, tuttavia, il ciclo è breve e pertinente, lo scopo dell'istruzione break dovrebbe essere ovvio.

Se un ciclo sta diventando troppo grande, utilizzare invece una o più chiamate di funzione ben denominate all'interno del ciclo. L'unico vero motivo per evitare di farlo è l'elaborazione dei colli di bottiglia.


11
Abbastanza vero. Ovviamente se il loop è così grande e complesso che è difficile vedere cosa sta succedendo al suo interno, è un problema che tu abbia una pausa o meno.
Jay il

1
Un altro problema è che l'interruzione e la continuazione causano problemi con il refactoring. Questo potrebbe essere un segno che questa è una cattiva pratica. L'intento del codice è anche più chiaro quando si usano istruzioni if.
Ed Greaves

Quelle "chiamate di funzione ben denominate" possono essere funzioni lambda definite localmente invece di funzioni private definite esternamente che non verranno utilizzate altrove.
DavidRR

135

No, la rottura è la soluzione corretta.

L'aggiunta di una variabile booleana rende il codice più difficile da leggere e aggiunge una potenziale fonte di errori.


2
concordato. Soprattutto se stai cercando di uscire dal ciclo in più di una singola condizione. Il valore booleano per lasciare un ciclo può diventare davvero confuso.
Rontologo

2
Questo è il motivo per cui uso gotoper rompere i loop nidificati di Livello 2+ - perché non ce ne sonobreak(level)
Петър Петров

3
Preferisco inserire il codice del ciclo in una funzione e restituirlo. Ciò evita il goto e suddivide il codice in blocchi più piccoli.
Dave Branton

53

Puoi trovare tutti i tipi di codice professionale con istruzioni "break". Ha perfettamente senso usarlo ogni volta che è necessario. Nel tuo caso questa opzione è migliore della creazione di una variabile separata solo allo scopo di uscire dal ciclo.


47

L'uso breakcosì come continuein un forciclo va perfettamente bene.

Semplifica il codice e ne migliora la leggibilità.


Sì .. Per un po 'l'idea non mi è piaciuta e ho deciso di codificarla, ma non ci è voluto molto prima che mi rendessi conto che ... la "soluzione alternativa" era spesso un incubo di manutenzione. Beh, non un incubo, ma più incline agli errori.
MetalMikester

24

Lungi dalla cattiva pratica, Python (e altri linguaggi?) Ha esteso la struttura del forciclo in modo che una parte di essa verrà eseguita solo se il ciclo non lo fa break .

for n in range(5):
    for m in range(3):
        if m >= n:
            print('stop!')
            break
        print(m, end=' ')
    else:
        print('finished.')

Produzione:

stop!
0 stop!
0 1 stop!
0 1 2 finished.
0 1 2 finished.

Codice equivalente senza breake pratico else:

for n in range(5):
    aborted = False
    for m in range(3):
        if not aborted:
            if m >= n:
                print('stop!')
                aborted = True
            else:            
                print(m, end=' ')
    if not aborted:
        print('finished.')

2
Oooh, mi piace e a volte l'ho desiderato anche in C. Bella sintassi, che potrebbe essere adattata in un futuro linguaggio simile al C senza ambiguità.
supercat

"Linguaggio simile al C": P
nirvanaswap

15

Non c'è nulla di intrinsecamente sbagliato nell'usare un'istruzione break, ma i cicli annidati possono creare confusione. Per migliorare la leggibilità, molti linguaggi ( almeno Java lo fa ) supportano l'interruzione delle etichette che miglioreranno notevolmente la leggibilità.

int[] iArray = new int[]{0,1,2,3,4,5,6,7,8,9};
int[] jArray = new int[]{0,1,2,3,4,5,6,7,8,9};

// label for i loop
iLoop: for (int i = 0; i < iArray.length; i++) {

    // label for j loop
    jLoop: for (int j = 0; j < jArray.length; j++) {

        if(iArray[i] < jArray[j]){
            // break i and j loops
            break iLoop;
        } else if (iArray[i] > jArray[j]){  
            // breaks only j loop
            break jLoop;
        } else {
            // unclear which loop is ending
            // (breaks only the j loop)
            break;
        }
    }
}

Dirò che le istruzioni break (e return) spesso aumentano la complessità ciclomatica, il che rende più difficile dimostrare che il codice sta facendo la cosa corretta in tutti i casi.

Se stai pensando di utilizzare un'interruzione durante l'iterazione di una sequenza per un elemento particolare, potresti voler riconsiderare la struttura dei dati utilizzata per conservare i tuoi dati. Usare qualcosa come un Set o una Mappa può fornire risultati migliori.


4
+1 per complessità ciclomatica.
sp00m

I programmatori .NET potrebbero voler esplorare l'uso di LINQ come potenziale alternativa ai forcicli complessi .
DavidRR

14

break è un'istruzione completamente accettabile da usare (così è continue , btw). È tutta una questione di leggibilità del codice: finché non si hanno loop complicati e simili, va bene.

Non è come se fossero della stessa lega di goto . :)


Ho visto che ha sostenuto che un'istruzione break è effettivamente un goto .
glenatron

3
Il problema con goto è che puoi puntarlo ovunque. Entrambi rompono e continuano a preservare la modularità del codice. Questo argomento è allo stesso livello di avere un unico punto di uscita per ogni funzione.
Zachary Yates

1
Ho sentito sostenere che avendo più punti di uscita per una funzione è effettivamente un goto . ; D
glenatron

2
@glenatron Il problema con goto non è l'istruzione goto, è l'introduzione dell'etichetta a cui salta goto che è il problema. Quando leggi un'istruzione goto, non c'è incertezza sul flusso di controllo. Quando leggi un'etichetta, c'è molta incertezza sul flusso di controllo (dove sono tutti i gotos che trasferiscono il controllo a questa etichetta?). Una dichiarazione di interruzione non introduce un'etichetta, quindi non introduce nuove complicazioni.
Stephen C. Steel

1
La "pausa" è un goto speciale che può solo lasciare blocchi. I problemi storici con "goto" erano (1) poteva essere, e spesso era, usato per costruire blocchi di loop sovrapposti in un modo che non è possibile con strutture di controllo adeguate; (2) un modo comune per eseguire un costrutto "se-allora" che di solito era "falso" era quello di fare in modo che il caso vero si diramasse in una posizione non correlata nel codice, e poi tornasse indietro. Ciò costerebbe due rami nel raro caso e zero nel caso comune, ma rendeva il codice orribile.
supercat

14

Regola generale: se seguire una regola richiede di fare qualcosa di più imbarazzante e difficile da leggere quindi infrangere la regola, infrangere la regola.

Nel caso del looping finché non trovi qualcosa, ti imbatti nel problema di distinguere trovato e non trovato quando esci. Questo è:

for (int x=0;x<fooCount;++x)
{
  Foo foo=getFooSomehow(x);
  if (foo.bar==42)
    break;
}
// So when we get here, did we find one, or did we fall out the bottom?

Quindi va bene, puoi impostare un flag o inizializzare un valore "trovato" su null. Ma

Ecco perché in generale preferisco spingere le mie ricerche in funzioni:

Foo findFoo(int wantBar)
{
  for (int x=0;x<fooCount;++x)
  {
    Foo foo=getFooSomehow(x);
    if (foo.bar==wantBar)
      return foo;
  }
  // Not found
  return null;
}

Questo aiuta anche a liberare il codice. Nella riga principale, "find" diventa una singola istruzione e, quando le condizioni sono complesse, vengono scritte solo una volta.


1
Esattamente quello che volevo dire. Spesso quando mi trovo a usare break, cerco di trovare un modo per refactoring del codice in una funzione, in modo da poterlo usare returninvece.
Rene Saarsoo

Sono d'accordo al 100% con te, non c'è niente di intrinsecamente sbagliato nell'usare break. Tuttavia, generalmente indica che il codice in cui si trova potrebbe dover essere estratto in una funzione in cui break sarebbe stato sostituito con return. Dovrebbe essere considerato un "odore di codice" piuttosto che un vero e proprio errore.
vascowhite

La tua risposta potrebbe spingere alcuni a esplorare l'opportunità di utilizzare più dichiarazioni di reso .
DavidRR

13

Dipende dalla lingua. Anche se puoi eventualmente controllare una variabile booleana qui:

for (int i = 0; i < 100 && stayInLoop; i++) { ... }

non è possibile farlo durante l'iterazione su un array:

for element in bigList: ...

Comunque, breakrenderebbe entrambi i codici più leggibili.


7

Nel tuo esempio non conosci il numero di iterazioni per il ciclo for . Perché non utilizzare invece il ciclo while , che consente al numero di iterazioni di essere indeterminato all'inizio?

Non è quindi necessario utilizzare break statemement in generale, poiché il ciclo può essere meglio definito come un ciclo while .


1
Un ciclo "for" è spesso valido se esiste un numero massimo noto di iterazioni. Detto questo, un ciclo while (1) a volte è migliore in uno scenario in cui si sta cercando un elemento e si prevede di crearlo se non esiste. In tal caso, se il codice trova l'elemento, esegue un'interruzione diretta e se raggiunge la fine dell'array crea un nuovo elemento e quindi esegue un'interruzione.
supercat

2
Nell'esempio fornito, cercando una chiave in un array, il ciclo for è il costrutto idiomatico appropriato. Non usi un ciclo while per esplorare un array. Usi un ciclo for. (o un ciclo for-each se disponibile). Lo fai come cortesia agli altri programmatori, poiché riconoscono il costrutto "for (int i = 0; i <ra.length; i ++) {}" immediatamente come "Walk through the array" Aggiungendo un'interruzione, a questo costrutto è semplicemente una dichiarazione "Esci in anticipo se puoi".
Chris Cudmore

7

Sono d'accordo con altri che consigliano di utilizzare break. L'ovvia domanda consequenziale è perché qualcuno dovrebbe raccomandare diversamente? Bene ... quando usi break, salti il ​​resto del codice nel blocco e le iterazioni rimanenti. A volte questo causa bug, ad esempio:

  • una risorsa acquisita nella parte superiore del blocco può essere rilasciata nella parte inferiore (questo vale anche per i blocchi all'interno dei forcicli), ma quel passaggio di rilascio può essere saltato accidentalmente quando un'uscita "prematura" è causata da breakun'istruzione (in "moderno" C ++, "RAII" viene utilizzato per gestire questo in modo affidabile e sicuro rispetto alle eccezioni: in pratica, i distruttori di oggetti liberano le risorse in modo affidabile indipendentemente da come si esce dall'ambito)

  • qualcuno può modificare il test condizionale nella fordichiarazione senza accorgersi che esistono altre condizioni di uscita delocalizzate

  • La risposta di ndim osserva che alcune persone potrebbero evitare breaks per mantenere un tempo di esecuzione del ciclo relativamente coerente, ma stavi confrontando breakcon l'uso di una variabile di controllo di uscita anticipata booleana dove ciò non vale

Ogni tanto le persone che osservano tali bug si rendono conto che possono essere prevenute / mitigate da questa regola "senza interruzioni" ... in effetti, esiste un'intera strategia correlata per una programmazione "più sicura" chiamata "programmazione strutturata", dove ogni funzione dovrebbe avere anche un unico punto di entrata e di uscita (cioè nessun goto, nessun ritorno anticipato). Può eliminare alcuni bug, ma senza dubbio ne introduce altri. Perché lo fanno?

  • hanno un framework di sviluppo che incoraggia un particolare stile di programmazione / codice e hanno prove statistiche che questo produce un vantaggio netto in quel framework limitato, o
  • sono stati influenzati dalle linee guida di programmazione o dall'esperienza in tale quadro, o
  • sono solo idioti dittatoriali, o
  • una delle precedenti + inerzia storica (rilevante in quanto le giustificazioni sono più applicabili al C rispetto al C ++ moderno).

Bene, sì, ma poi di nuovo ho visto bug nel codice da parte di persone che cercavano religiosamente di evitarlo break. L'hanno evitato ma non sono riusciti a riflettere sulle condizioni che hanno sostituito l'uso della pausa.
Tomáš Zato - Ripristina Monica il

5

È perfettamente valido da usare break- come altri hanno sottolineato, non è da nessuna parte nella stessa lega di goto.

Anche se potresti voler usare la vFoundvariabile quando vuoi controllare fuori dal ciclo se il valore è stato trovato nell'array. Anche dal punto di vista della manutenibilità, potrebbe essere utile avere un flag comune che segnali i criteri di uscita.


5

Ho fatto alcune analisi sulla base di codice su cui sto attualmente lavorando (40.000 righe di JavaScript).

Ho trovato solo 22 breakaffermazioni, di quelle:

  • Sono state utilizzate 19 switchistruzioni all'interno (abbiamo solo 3 istruzioni switch in totale!).
  • 2 sono stati utilizzati all'interno di forloop - un codice che ho immediatamente classificato come refactoring in funzioni separate e sostituito con returnstatement.
  • Per quanto riguarda il ciclo breakinterno finale while... sono corso git blamea vedere chi ha scritto questa merda!

Quindi, secondo le mie statistiche: se breakviene utilizzato al di fuori di switch, è un odore di codice.

Ho anche cercato continuedichiarazioni. Nessuno trovato.


4

Non vedo alcun motivo per cui sarebbe una cattiva pratica A condizione che si desideri completare l'INTERRUZIONE dell'elaborazione a quel punto.


4

Nel mondo embedded, c'è molto codice là fuori che utilizza il seguente costrutto:

    while(1)
    { 
         if (RCIF)
           gx();
         if (command_received == command_we_are_waiting_on)
           break;
         else if ((num_attempts > MAX_ATTEMPTS) || (TickGet() - BaseTick > MAX_TIMEOUT))
           return ERROR;
         num_attempts++;
    }
    if (call_some_bool_returning_function())
      return TRUE;
    else
      return FALSE;

Questo è un esempio molto generico, molte cose stanno accadendo dietro le quinte, le interruzioni in particolare. Non usarlo come codice boilerplate, sto solo cercando di illustrare un esempio.

La mia opinione personale è che non c'è niente di sbagliato nello scrivere un loop in questo modo purché si prenda la dovuta cura per evitare di rimanere nel loop indefinitamente.


1
Potresti approfondire questo? Mi sembra che il ciclo non faccia assolutamente nulla ... a meno che non ci siano continueistruzioni all'interno della logica dell'applicazione. Ci sono?
Rene Saarsoo

1
Le istruzioni continue non sono necessarie poiché sei già vincolato da un ciclo infinito. Fondamentalmente si esegue la logica necessaria per raggiungere un obiettivo e se tale obiettivo non viene raggiunto in n numero di tentativi o una condizione di timeout scade, si esce dal ciclo tramite un costrutto di interruzione e si intraprende un'azione correttiva o si informa il codice chiamante di un errore. Vedo spesso questo tipo di codice negli UC che comunicano tramite un bus comune (RS-485, ecc.) Fondamentalmente cerchi di prendere la linea e comunicare senza collisioni, se si verificano collisioni, aspetti una durata determinata da un numero casuale e provi ancora.
Nate

1
Al contrario, se le cose vanno bene, usi anche l'istruzione break per uscire una volta che la condizione è stata soddisfatta. In questo caso, viene eseguito il codice che segue il ciclo e tutti sono contenti.
Nate

"if (call_some_bool_returning_function ()) restituisce TRUE; altrimenti restituisce FALSE;" potrebbe essere semplicemente "return call_some_bool_returning_function ()"
Frankster

3

Dipende dal tuo caso d'uso. Ci sono applicazioni in cui il tempo di esecuzione di un ciclo for deve essere costante (ad esempio per soddisfare alcuni vincoli temporali o per nascondere i dati interni da attacchi basati sul tempo).

In questi casi avrà anche senso impostare un flag e controllare il valore del flag solo DOPO che tutte le foriterazioni del ciclo sono state effettivamente eseguite. Ovviamente, tutte le iterazioni del ciclo for devono eseguire codice che richiede ancora lo stesso tempo.

Se non ti interessa il tempo di esecuzione ... usa break;e continue;per rendere il codice più facile da leggere.


1
Se il tempismo è importante, potrebbe essere meglio fare un po 'di sonno e risparmiare risorse di sistema piuttosto che lasciare che il programma esegua operazioni note per essere inutili.
Bgs

2

Sulle regole MISRA 98, che vengono utilizzate nella mia azienda in C dev, non deve essere utilizzata l'istruzione break

Modifica: l'interruzione è consentita in MISRA '04


2
Esattamente perché ho posto questa domanda! Non trovo una buona ragione per cui una regola del genere sia presente come linea guida per la codifica. Inoltre, come hanno detto tutti gli altri qui, rompi; sembra migliore.
kiki

1
Non so esattamente perché sia ​​vietato (persone più intelligenti di me ci hanno pensato ...) ma il break (e il ritorno multiplo) sono migliori per la leggibilità (secondo me, ma le mie opinioni non sono regole aziendali ... )
Benoît

1
Uh, le regole MISRA consentono l'uso di dichiarazioni di interruzione: misra.org.uk/forum/viewtopic.php?f=75&t=298
luis.espinal

Abbiamo usato le regole MISRA 98 ... È esatto che MISRA 2004 cambia le regole
Benoît

0

Ovviamente, break;è la soluzione per fermare il ciclo for o il ciclo foreach. L'ho usato in php in foreach e for loop e ho trovato funzionante.


0

Non sono d'accordo!

Perché ignoreresti la funzionalità incorporata di un ciclo for per crearne uno tuo? Non è necessario reinventare la ruota.

Penso che abbia più senso avere i controlli nella parte superiore del ciclo for in questo modo

for(int i = 0; i < myCollection.Length && myCollection[i].SomeValue != "Break Condition"; i++)
{
//loop body
}

o se devi prima elaborare la riga

for(int i = 0; i < myCollection.Length && (i == 0 ? true : myCollection[i-1].SomeValue != "Break Condition"); i++)
{
//loop body
}

In questo modo puoi scrivere una funzione per eseguire tutto e creare codice molto più pulito.

for(int i = 0; i < myCollection.Length && (i == 0 ? true : myCollection[i-1].SomeValue != "Break Condition"); i++)
{
    DoAllThatCrazyStuff(myCollection[i]);
}

O se la tua condizione è complicata puoi spostare anche quel codice!

for(int i = 0; i < myCollection.Length && BreakFunctionCheck(i, myCollection); i++)
{
    DoAllThatCrazyStuff(myCollection[i]);
}

Il "codice professionale" che è pieno di interruzioni non mi suona come un codice professionale. Sembra una codifica pigra;)


4
la codifica pigra è un codice professionale :)
Jason

Solo se non è un
rompicoglioni

2
Tendo a praticare "GTFO ASAP". Cioè, se posso uscire da una struttura in modo pulito, dovrei farlo il prima possibile e il più vicino possibile alla condizione che specifica l'uscita.
Chris Cudmore

Bene, anche questo funziona. Penso che l'argomento che sto cercando di trasmettere sia che se stai usando un ciclo for non fa male usare la sua funzionalità incorporata per rendere il tuo codice leggibile e modulare. Se hai assolutamente bisogno di avere dichiarazioni di interruzione all'interno del tuo ciclo, mi siedo e penso al motivo per cui è necessario quel livello di complessità.
Biff MaGriff

1
Haha. Considera il fatto che la maggior parte delle persone che scoraggiano breaksostengono che sia per la leggibilità del codice. Ora guarda di nuovo la folle condizione di 5 miglia nei loop sopra ...
Tomáš Zato - Reinstate Monica
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.