`Break` e` continue` sono cattive pratiche di programmazione?


191

Il mio capo continua a menzionare con disinvoltura che i programmatori cattivi usano breake continuenei loop.

Li uso sempre perché hanno un senso; lascia che ti mostri l'ispirazione:

function verify(object) {
    if (object->value < 0) return false;
    if (object->value > object->max_value) return false;
    if (object->name == "") return false;
    ...
}

Il punto qui è che prima la funzione verifica che le condizioni siano corrette, quindi esegue la funzionalità effettiva. Lo stesso IMO si applica ai loop:

while (primary_condition) {
    if (loop_count > 1000) break;
    if (time_exect > 3600) break;
    if (this->data == "undefined") continue;
    if (this->skip == true) continue;
    ...
}

Penso che questo renda più semplice la lettura e il debug; ma non vedo nemmeno un aspetto negativo.


2
Non ci vuole molto a dimenticare chi fa cosa.

57
No. Neanche è andare. Sapere quando usarli è la chiave. Sono strumenti nella casella degli strumenti. Li usi quando forniscono codice chiaro e sintetico.
orj

67
Non posso esprimere il mio supporto per questo stile di programmazione abbastanza fortemente. Livelli multipli di condizionali nidificati sono molto peggio di questo approccio. Di solito non sono militante nello stile di programmazione, ma questo è quasi un punto di rottura per me.
Emil H,

9
Ovviamente il tuo capo non scrive (abbastanza) codice. Se lo facesse, saprebbe che tutte le parole chiave (sì, anche goto) sono utili in alcuni casi.
sakisk,

57
I programmatori sbagliati usano break e continue non significa che i bravi programmatori no. I programmatori errati usano se e mentre pure.
mouviciel,

Risposte:


240

Se utilizzati all'inizio di un blocco, come i primi controlli effettuati, si comportano come precondizioni, quindi è buono.

Se usati nel mezzo del blocco, con un po 'di codice in giro, si comportano come trappole nascoste, quindi è male.


6
@Klaim: si può sostenere che qualsiasi routine che ha diversi punti di uscita è una routine scarsamente ponderata. Una routine adeguatamente ponderata dovrebbe fare solo una cosa e una cosa.
bit-twiddler

79
@ bit-twiddler: questa è una mentalità molto C-ish. Una variabile temporanea può essere modificata in seguito, quindi un singolo errore di battitura a 20 righe da qui potrebbe cancellare quel risultato accuratamente realizzato. Un ritorno immediato (o interruzione, o continua) è tuttavia estremamente chiaro: posso smettere di leggere ora perché so che non può essere modificato più in basso . Fa bene al mio piccolo cervello, rende davvero più facile arrancare attraverso il codice.
Matthieu M.

15
@Matthieu sono d'accordo. Esci da un blocco quando ricevi un risultato che soddisfa lo scopo del blocco.
Evan Plaice,

8
@ bit-twiddler: il principio del punto di uscita singolo è separato dal principio della responsabilità singola. Non sono d'accordo sul fatto che il controllo sia sempre separato dall'agire WRT con la sola responsabilità. In effetti "responsabilità singola" mi sembra sempre un termine soggettivo. Ad esempio, in un risolutore quadratico, il calcolo del discriminante dovrebbe essere una responsabilità separata? O l'intera formula quadratica può essere una singola responsabilità? Direi che dipende dal fatto che tu abbia un uso separato per il discriminante - altrimenti, trattarlo come una responsabilità separata è probabilmente eccessivo.
Steve314,

10
Quella risposta è una regola empirica, non una regola difficile. Funziona nella maggior parte dei casi, sentiti libero di romperlo se ha senso nel tuo contesto.
Klaim,

87

Puoi leggere il documento di Programmazione strutturata del 1974 di Donald Knuth con andare a Dichiarazioni , in cui discute vari usi di quelli go tostrutturalmente desiderabili. Includono l'equivalente di breake le continuedichiarazioni (molti degli usi di go toin sono stati sviluppati in costrutti più limitati). Il tuo capo è il tipo da chiamare Knuth un cattivo programmatore?

(Gli esempi forniti mi interessano. In genere, breake continuenon amano le persone a cui piacciono una voce e un'uscita da qualsiasi codice, e quel tipo di persona si acciglia anche di più returndichiarazioni.)


8
La maggior parte delle persone a cui piacciono le funzioni e le procedure per avere singoli punti di entrata e uscita sono cresciute su Pascal. Pascal non è stata la prima lingua che ho imparato, ma ha avuto un profondo impatto su come strutturare il codice fino ad oggi. Le persone commentano sempre quanto sia facile leggere il mio codice Java. Questo perché evito più punti di uscita e si mescolano dichiarazioni con il codice. Faccio del mio meglio per dichiarare ogni variabile locale utilizzata in un metodo all'inizio del metodo. Questa pratica evita la confusione del codice costringendomi a mantenere i metodi concisi.
bit-twiddler,

5
Pascal aveva anche funzioni nidificate.
Sto

6
Da quello che ricordo, nel passato, il motivo principale per cui alla gente non piacevano più dichiarazioni di ritorno nelle funzioni era perché i debugger non le gestivano correttamente. È stato davvero doloroso impostare un breakpoint alla fine di una funzione, ma non l'hai mai raggiunto a causa di una precedente dichiarazione di ritorno. Per ogni compilatore che uso al giorno d'oggi non è più un problema.
Dunk

28
@ bit-twiddler: non ne sono così sicuro. Sto ancora usando Pascal oggi, e generalmente considero la "singola uscita singola uscita", o almeno la sua parte singola uscita, come programmazione di culto del carico. Ho appena considero Break, Continuee Exitcome strumenti nella mia cassetta degli attrezzi; Li uso dove rende più facile seguire il codice e non li uso dove renderebbe le cose più difficili da leggere.
Mason Wheeler,

2
@ bit-twiddler: amen a quello. Aggiungerò anche che una volta scesi ai blocchi che si adattano facilmente sullo schermo, più punti di uscita diventano molto meno problematici.
Shog9

44

Non credo che siano cattivi. L'idea che siano cattivi viene dai giorni della programmazione strutturata. È correlato all'idea che una funzione deve avere un singolo punto di ingresso e un singolo punto di uscita, cioè solo uno returnper funzione.

Questo ha senso se la tua funzione è lunga e se hai più loop nidificati. Tuttavia, le tue funzioni dovrebbero essere brevi e dovresti avvolgere i circuiti e i loro corpi in funzioni brevi. Generalmente, forzare una funzione ad avere un singolo punto di uscita può comportare una logica molto contorta.

Se la tua funzione è molto breve, se hai un singolo loop o, nel peggiore dei casi, due loop nidificati e se il corpo del loop è molto breve, allora è molto chiaro cosa fa uno breako uno continue. È anche chiaro cosa fanno più returndichiarazioni.

Questi problemi vengono affrontati in "Codice pulito" di Robert C. Martin e in "Refactoring" di Martin Fowler.


12
"Rendi le tue funzioni più piccole. Quindi riducine le dimensioni" -Robert C. Martin. Ho scoperto che funziona sorprendentemente bene. Ogni volta che vedi un blocco di codice in una funzione che necessita di un commento che spiega cosa fa, racchiudilo in una funzione separata con un nome descrittivo. Anche se sono solo poche righe e anche se viene usato una sola volta. Questa pratica elimina la maggior parte dei problemi con interruzioni / continue o restituzioni multiple.
Dima,

2
@Mikhail: la complessità ciclomatica è generalmente abbastanza fortemente correlata con SLOC, il che significa che il consiglio può essere semplificato per "non scrivere lunghe funzioni".
John R. Strohm,

3
L'idea di un singolo punto di uscita è ampiamente fraintesa. Una volta, le funzioni non dovevano restituire il chiamante. Potrebbero tornare in qualche altro posto. Questo è stato comunemente fatto in linguaggio assembly. Fortran aveva un costrutto speciale per questo; è possibile passare un numero di istruzione preceduto da una e commerciale CALL P(X, Y, &10)e, in caso di errore, la funzione potrebbe passare il controllo a tale istruzione, invece di tornare al punto della chiamata.
Kevin Cline,

@kevincline visto con Lua, per esempio.
Qix

1
@cjsimon hai capito. Non solo le funzioni dovrebbero essere piccole. Anche le lezioni dovrebbero essere piccole.
Dima,

39

I cattivi programmatori parlano in modo assoluto (proprio come Sith). I bravi programmatori usano la soluzione più chiara possibile (a parità di tutte le altre cose ).

L'uso frequente e continua spesso rende il codice difficile da seguire. Ma se sostituirli rende il codice ancora più difficile da seguire, allora è un brutto cambiamento.

L'esempio che hai dato è sicuramente una situazione in cui le interruzioni e le continue dovrebbero essere sostituite con qualcosa di più elegante.


Sì, ho esagerato il numero di condizioni per fornire esempi di casi per uscire.
Mikhail,

9
Qual è un esempio del codice di sostituzione che hai suggerito? Ho pensato che fosse un esempio abbastanza ragionevole di dichiarazioni di guardia.
simgineer

Mi sembra che "la soluzione più chiara possibile" sia sempre ... possibile? Come potrebbe non esserci una soluzione "più chiara"? Ma poi, non sono uno per gli assoluti, quindi forse hai ragione.

@nocomprende Non sono sicuro di cosa stai arrivando. Il "possibile" qui non indica che la migliore soluzione non esiste - solo quella perfetta, assoluta chiarezza non è una cosa. Dopotutto è soggettivo.
Matthew Leggi il

22

Molte persone pensano che sia una cattiva idea perché il comportamento non è facilmente prevedibile. Se stai leggendo il codice e vedi while(x < 1000){}che supponi che funzionerà fino a x> = 1000 ... Ma se ci sono interruzioni nel mezzo, allora non è vero, quindi non puoi davvero fidarti del tuo looping ...

È lo stesso motivo per cui alle persone non piace GOTO: certo, può essere usato bene, ma può anche portare a un divino codice spaghetti, in cui il codice salta casualmente da una sezione all'altra.

Per quanto mi riguarda, se avessi fatto un ciclo che si era rotto su più di una condizione, avrei fatto while(x){}X su falso quando avevo bisogno di scoppiare. Il risultato finale sarebbe lo stesso e chiunque leggesse il codice saprebbe guardare più da vicino le cose che hanno cambiato il valore di X.


2
+1 molto ben detto e +1 (se potessi fare un altro) per l' while(notDone){ }approccio.
FrustratedWithFormsDesigner

5
Mikhail: Il problema con l'interruzione è che la condizione finale per il loop non viene mai semplicemente dichiarata in un posto. Ciò rende difficile prevedere la post-condizione dopo il ciclo. In questo banale caso (> = 1000) non è difficile. Aggiungi molte istruzioni if ​​e diversi livelli di annidamento può diventare molto, molto difficile determinare la post-condizione del ciclo.
S.Lott

S. Lott ha colpito l'unghia esattamente sulla testa. L'espressione che controlla l'iterazione dovrebbe includere tutte le condizioni che devono essere soddisfatte per continuare l'iterazione.
bit-twiddler

6
La sostituzione break;con x=false;non rende il codice più chiaro. Devi ancora cercare nel corpo quella frase. E nel caso x=false;dovresti controllare che non colpisca x=true;ulteriormente.
Sjoerd,

14
Quando la gente dice "Vedo x e presumo y, ma se fai z quell'assunto non regge" Tendo a pensare "quindi non fare quella stupida assunzione". Molte persone lo semplificherebbero a "quando vedo while (x < 1000)suppongo che funzionerà 1000 volte". Bene, ci sono molte ragioni per cui è falso, anche se xinizialmente è zero. Ad esempio, chi dice che xviene incrementato esattamente una volta durante il ciclo e mai modificato in nessun altro modo? Anche per tua stessa ipotesi, solo perché qualcosa imposta x >= 1000non significa che il ciclo finirà - potrebbe essere ripristinato nell'intervallo prima che la condizione venga verificata.
Steve314,

14

Sì, puoi [ri] scrivere programmi senza istruzioni di break (o ritorna dalla metà dei loop, che fanno la stessa cosa). Tuttavia, potrebbe essere necessario introdurre ulteriori variabili e / o duplicazione del codice, che in genere rendono il programma più difficile da comprendere. Pascal (il linguaggio di programmazione) è stato molto male soprattutto per i programmatori principianti per questo motivo. Il tuo capo vuole fondamentalmente che tu programmi nelle strutture di controllo di Pascal. Se Linus Torvalds fosse nei tuoi panni, probabilmente mostrerebbe al tuo capo il dito medio!

C'è un risultato informatico chiamato la gerarchia delle strutture di controllo di Kosaraju, che risale al 1973 e che è menzionata nel (più) famoso documento di Knuth sulle foto del 1974. (Questo documento di Knuth era già stato raccomandato sopra da David Thornley, tra l'altro .) Ciò che S. Rao Kosaraju ha dimostrato nel 1973 è che non è possibile riscrivere tutti i programmi che hanno interruzioni a più livelli di profondità n in programmi con profondità di interruzione inferiore a n senza introdurre variabili aggiuntive. Ma diciamo che è solo un risultato puramente teorico. (Basta aggiungere alcune variabili extra ?! Sicuramente puoi farlo per compiacere il tuo capo ...)

Ciò che è molto più importante dal punto di vista dell'ingegneria del software è un recente documento del 1995 di Eric S. Roberts intitolato Loop Exits and Structured Programming: Reopening the Debate ( http://cs.stanford.edu/people/eroberts/papers/SIGCSE- 1995 / LoopExits.pdf ). Roberts riassume diversi studi empirici condotti da altri prima di lui. Ad esempio, quando a un gruppo di studenti del tipo CS101 è stato chiesto di scrivere codice per una funzione che implementa una ricerca sequenziale in un array, l'autore dello studio ha dichiarato quanto segue su quegli studenti che hanno usato una pausa / return / goto per uscire da il ciclo di ricerca sequenziale quando è stato trovato l'elemento:

Devo ancora trovare una sola persona che ha tentato un programma usando [questo stile] che ha prodotto una soluzione errata.

Roberts afferma inoltre che:

Gli studenti che hanno tentato di risolvere il problema senza utilizzare un ritorno esplicito dal ciclo for sono andati molto meno bene: solo sette dei 42 studenti che hanno tentato questa strategia sono riusciti a generare soluzioni corrette. Tale cifra rappresenta un tasso di successo inferiore al 20%.

Sì, potresti essere più esperto degli studenti CS101, ma senza usare l'istruzione break (o equivalentemente restituire / andare dal centro dei loop), alla fine scriverai un codice che mentre nominalmente ben strutturato è abbastanza peloso in termini di logica aggiuntiva variabili e duplicazione del codice che qualcuno, probabilmente te stesso, inserirà in esso dei bug logici mentre cerca di seguire lo stile di codifica del tuo capo.

Dirò anche qui che l'articolo di Roberts è molto più accessibile al programmatore medio, quindi una prima lettura migliore di quella di Knuth. È anche più breve e copre un argomento più ristretto. Probabilmente potresti persino consigliarlo al tuo capo, anche se è il management piuttosto che il tipo CS.


2
Sebbene la programmazione strutturata sia stata il mio approccio per molti anni, negli ultimi pochi sono passati a utilizzare le uscite esplicite alla prima occasione possibile. Ciò rende l'esecuzione più rapida ed elimina quasi gli errori logici che in passato causavano loop infiniti (blocco).
DocSalvager,

9

Non prendo in considerazione l'utilizzo di nessuna di queste cattive pratiche, ma usarle troppo all'interno dello stesso loop dovrebbe giustificare un ripensamento della logica utilizzata nel loop. Usali con parsimonia.


:) sì, l'esempio che ho fornito era concettuale
Mikhail,

7

L'esempio che hai fornito non necessita di interruzioni né continua:

while (primary-condition AND
       loop-count <= 1000 AND
       time-exec <= 3600) {
   when (data != "undefined" AND
           NOT skip)
      do-something-useful;
   }

Il mio "problema" con le 4 righe del tuo esempio è che sono tutte sullo stesso livello ma fanno cose diverse: alcune si interrompono, altre continuano ... Devi leggere ogni riga.

Nel mio approccio nidificato, più approfondisci, più il codice diventa "utile".

Ma, se dentro di te troverai un motivo per fermare il loop (diverso dalla condizione primaria), una pausa o un ritorno avrebbero bisogno. Preferirei questo rispetto all'uso di un flag aggiuntivo che deve essere testato nelle condizioni di massimo livello. L'interruzione / ritorno è più diretta; indica meglio l'intento che impostare un'altra variabile.


+1 Ma in realtà i tuoi <confronti devono <=corrispondere alla soluzione dei PO
El Ronnoco,

3
Nella maggior parte dei casi, se si è così profondi che è necessario utilizzare break / return per gestire il controllo del flusso, la propria funzione / metodo è troppo complessa.
bit-twiddler,

6

La "cattiveria" dipende da come li usi. In genere uso interruzioni nei costrutti di loop SOLO quando mi salveranno cicli che non possono essere salvati attraverso un refactoring di un algoritmo. Ad esempio, scorrere una raccolta in cerca di un elemento con un valore in una proprietà specifica impostato su true. Se tutto ciò che devi sapere è che una delle voci aveva questa proprietà impostata su true, una volta raggiunto quel risultato, una pausa è buona per terminare il loop in modo appropriato.

Se l'utilizzo di un'interruzione non renderà il codice specificamente più facile da leggere, più breve da eseguire o salvare i cicli nell'elaborazione in modo significativo, è meglio non usarli. Tendo a programmare il "minimo comune denominatore" quando possibile per assicurarmi che chiunque mi segua possa facilmente guardare il mio codice e capire cosa sta succedendo (non ci riesco sempre). Le interruzioni lo riducono perché introducono strani punti di entrata / uscita. Abusati possono comportarsi in modo molto simile a un'affermazione "goto" fuori di testa.


Sono d'accordo con entrambi i punti! Per quanto riguarda il tuo secondo punto, penso che sia più facile seguire il mio post originale perché sembra inglese. Se hai una combinazione di condizioni in 1 if, allora è quasi una decifrazione capire cosa deve accadere affinché il if esegua true.
Mikhail,

@Mikhail: I campioni che hai fornito sono un po 'altruistici per il realismo specifico. A mio modo di vedere, questi esempi sono chiari, concisi, più facili da leggere. La maggior parte dei loop non è così. La maggior parte dei loop hanno una sorta di altra logica che stanno eseguendo e potenzialmente condizionali molto più complicati. È in questi casi in cui l'interruzione / continua potrebbe non essere l'uso migliore perché confonde la logica durante la lettura.
Joel Etherton,

4

Assolutamente no ... Sì, l'uso di gotoè negativo perché deteriora la struttura del programma ed è anche molto difficile comprendere il flusso di controllo.

Ma l'uso di dichiarazioni come breake continuesono assolutamente necessarie in questi giorni e non è considerato affatto una cattiva pratica di programmazione.

Inoltre, non è così difficile comprendere il flusso di controllo in uso di breake continue. In costrutti come switchl' breakaffermazione è assolutamente necessario.


2
Non ho usato "continue" da quando ho imparato C per la prima volta nel 1981. È una caratteristica del linguaggio non necessaria, poiché il codice che viene bypassato dall'istruzione continue può essere racchiuso da un'istruzione di controllo condizionale.
bit-twiddler,

12
Preferisco usare continue in questi casi poiché si assicura che il mio codice non diventi codice freccia. Odio il codice freccia più che andare alle dichiarazioni di tipo. L'ho anche letto come "se questa affermazione è vera, salta il resto di questo ciclo e continua con l'iterazione successiva". Molto utile quando è all'inizio di un ciclo for (meno utile nei cicli while).
jsternberg,

@jsternberg Per la vittoria! :-)
Notinlist,

3

L'idea essenziale deriva dalla capacità di analizzare semanticamente il tuo programma. Se si dispone di una singola voce e di una singola uscita, la matematica necessaria per indicare i possibili stati è notevolmente più semplice rispetto alla gestione dei percorsi di biforcazione.

In parte, questa difficoltà si riflette nella capacità di ragionare concettualmente sul tuo codice.

Francamente, il tuo secondo codice non è ovvio. Cosa sta facendo? Continue 'continue' o 'next' il loop? Non ne ho idea. Almeno il tuo primo esempio è chiaro.


Quando lavoro in un progetto che il manager obbliga a usarli, ho dovuto usare un diagramma di flusso per ricordare il suo utilizzo, e i vari percorsi di uscita, hanno reso il codice più confuso ...
Umlcat

il tuo commento "Frankly" è sintattico - "next" è una cosa perl; le lingue normali usano "continua" per indicare "salta questo ciclo"
Mikhail

@Mik, forse "saltare questa iterazione" sarebbe una descrizione migliore. Cordiali saluti, Dr. IM Pedantic
Pete Wilson,

@Mikhail: Sicuro. Ma quando si lavora in molte lingue, può portare a errori quando la sintassi non è comune tra le lingue.
Paul Nathan,

Ma la matematica non sta programmando. Il codice è più espressivo della matematica. Ho capito che il single-entry / single-exit può rendere i diagrammi di flusso aspetto molto più gradevole, ma a quale prezzo (Ie. Dove pausa / ritorno può rendere il codice un rendimento migliore)?
Evan Plaice,

3

Sostituirei il tuo secondo frammento di codice con

while (primary_condition && (loop_count <= 1000 && time_exect <= 3600)) {
    if (this->data != "undefined" && this->skip != true) {
        ..
    }
}

non per alcun motivo di terseness - in realtà penso che sia più facile da leggere e che qualcuno capisca cosa sta succedendo. In generale, le condizioni per i tuoi loop dovrebbero essere contenute esclusivamente in quelle condizioni di loop non disseminate in tutto il corpo. Tuttavia ci sono alcune situazioni in cui breake continuepuò aiutare la leggibilità. breakpiù di quanto continuepotrei aggiungere: D


Anche se non sono d'accordo con "Penso che sia più facile da leggere", questo ha esattamente lo stesso scopo del codice sopra. Finora non ho mai pensato di "rompere" come operatore di corto circuito, ma ha perfettamente senso. Per quanto riguarda "In generale, le condizioni per i tuoi loop dovrebbero essere contenute esclusivamente all'interno di quelle condizioni del loop", cosa fai quando elabori un ciclo foreach poiché non c'è davvero modo di inserire la logica condizionale nella definizione del loop?
Evan Plaice,

@Evan La condizione non è applicabile a un foreachciclo poiché questo ripeterà semplicemente tutti gli elementi in una raccolta. Un forloop è simile in quanto non dovrebbe avere un end point condizionale. Se è necessario un endpoint condizionale, è necessario utilizzare un whileciclo.
El Ronnoco,

1
@Evan Capisco però il tuo punto - vale a dire "cosa succede se è necessario uscire da un ciclo foreach?" - Beh, dovrebbe esserci solo un breakmassimo secondo me dal loop.
El Ronnoco,

2

Non sono d'accordo con il tuo capo. Ci sono posti adeguati per breake continueda usare. In effetti il ​​motivo per cui le esecuzioni e la gestione delle eccezioni sono state introdotte nei moderni linguaggi di programmazione è che non è possibile risolvere ogni problema usando semplicemente structured techniques.

Da un lato , non voglio iniziare una discussione religiosa qui, ma potresti ristrutturare il tuo codice per renderlo ancora più leggibile in questo modo:

while (primary_condition) {
    if (loop_count > 1000) || (time_exect > 3600) {
        break;
    } else if ( ( this->data != "undefined") && ( !this->skip ) ) {
       ... // where the real work of the loop happens
    }
}

In un'altra nota a margine

Personalmente non mi piace l'uso di ( flag == true )in conditionals perché se la variabile è già un valore booleano, stai introducendo un ulteriore confronto che deve verificarsi quando il valore del valore booleano ha la risposta che desideri - a meno che, naturalmente, non sei sicuro che il tuo compilatore lo farà ottimizzare quel confronto extra via.


Mi sono confuso su questa domanda per anni. Il tuo modo ('if (flag) {...}') è molto più conciso e forse sembra più "professionale" o "esperto". Ma, sai, spesso significa che un lettore / manutentore deve interrompersi brevemente per ricordare cosa significa il costrutto; e per essere sicuri del senso del test. Attualmente sto usando 'if (flag == true) {...}' solo perché sembra essere una migliore documentazione. Il prossimo mese? Saben tranquillo?
Pete Wilson,

2
@Pete - Il termine non è conciso ma elegante . Puoi confondere tutto ciò che vuoi, ma se sei preoccupato che il lettore / manutentore non capisca cosa booleansia o cosa significhi la terminologia concisa / elegante è allora forse assumi meglio alcuni manutentori più intelligenti ;-)
Zeke Hansell

@Pete, attendo anche la mia dichiarazione sul codice generato. Stai facendo un altro confronto confrontando un flag con un valore costante prima di valutare il valore booleano dell'espressione. Perché renderlo più difficile di quanto deve essere, la variabile flag ha già il valore desiderato!
Zeke Hansell,

+1 buon lavoro. Sicuramente molto più elegante del precedente esempio.
Evan Plaice,

2

Sono d'accordo con il tuo capo. Sono cattivi perché producono metodi con elevata complessità ciclomatica. Tali metodi sono difficili da leggere e difficili da testare. Fortunatamente c'è una soluzione semplice. Estrarre il corpo del ciclo in un metodo separato, in cui "continua" diventa "ritorno". "Ritorno" è meglio perché dopo "ritorno" è finita - non ci sono preoccupazioni per lo stato locale.

Per "break" estrai il loop stesso in un metodo separato, sostituendo "break" con "return".

Se i metodi estratti richiedono un gran numero di argomenti, questa è un'indicazione per estrarre una classe - o raccoglierli in un oggetto di contesto.


0

Penso che sia solo un problema quando nidificato in profondità all'interno di più loop. È difficile sapere in quale loop ti stai interrompendo. Potrebbe essere difficile seguire anche un proseguimento, ma penso che il vero dolore provenga dalle interruzioni - la logica può essere difficile da seguire.


2
In realtà, è facile vedere se indentate correttamente, se la semantica di quelle dichiarazioni è interiorizzata e non è troppo assonnata.

@delnan - sono molti i presupposti;)
davidhaskins,

2
Sì, soprattutto l'ultimo.
Michael K,

Bene, il numero 1 è necessario per una programmazione seria, il numero 2 è previsto da tutti coloro che sono chiamati programmatore e il numero 3 è abbastanza utile in generale;)

Alcune lingue (solo script che conosco) supportano break 2; per altri, immagino che vengano utilizzate bandiere bool temporanee
Mikhail,

0

Finché non vengono utilizzati come goto mascherati come nell'esempio seguente:

do
{
      if (foo)
      {
             /*** code ***/
             break;
      }

      if (bar)
      {
             /*** code ***/
             break;
      }
} while (0);

Sto bene con loro. (Esempio visto nel codice di produzione, meh)


Consiglieresti casi come questi per creare funzioni?
Mikhail,

Sì e, se non possibile, un semplice andare. Quando vedo un do, penso alla ripetizione, non al contesto per simulare il salto.
SuperBloup

oh sono d'accordo, stavo solo chiedendo come lo faresti :) Gli informatici lo fanno ripetutamente
Mikhail

0

Non mi piace nessuno di questi stili. Ecco cosa preferirei:

function verify(object)
{
    if not (object->value < 0) 
       and not(object->value > object->max_value)
       and not(object->name == "") 
       {
         do somethign important
       }
    else return false; //probably not necessary since this function doesn't even seem to be defined to return anything...?
}

Non mi piace davvero usare returnper interrompere una funzione. Sembra un abuso di return.

Anche l'utilizzo breaknon è sempre chiaro da leggere.

Meglio ancora potrebbe essere:

notdone := primarycondition    
while (notDone)
{
    if (loop_count > 1000) or (time_exect > 3600)
    {
       notDone := false; 
    }
    else
    { 
        skipCurrentIteration := (this->data == "undefined") or (this->skip == true) 

        if not skipCurrentIteration
        {
           do something
        } 
        ...
    }
}

meno annidamento e le condizioni complesse vengono trasformate in variabili (in un vero programma dovresti avere nomi migliori, ovviamente ...)

(Tutto il codice sopra è pseudo-codice)


11
Preferiresti davvero 3 livelli di annidamento rispetto a quello che ho digitato sopra?
Mikhail,

@Mikhail: Sì, oppure assegnerei il risultato della condizione a una variabile. Trovo molto più facile da capire della logica di breake continue. Terminare in modo anomalo un loop sembra strano e non mi piace.
FrustratedWithFormsDesigner

1
Ironia della sorte, hai letto male le mie condizioni. continuesignifica saltare la funzionalità andare al ciclo successivo; non "continuare con l'esecuzione"
Mikhail,

@Mikhail: Ah. Non lo uso spesso e quando lo leggo mi confondo dal significato. Un altro motivo per cui non mi piace. : P Dammi un minuto per l'aggiornamento ...
FrustratedWithFormsDesigner

7
Troppi annidamenti distruggono la leggibilità. E a volte, evitare l'interruzione / continua introduce la necessità di invertire la logica nei test condizionali, il che può portare a un'interpretazione errata di ciò che il codice sta facendo - sto solo dicendo
Zeke Hansell

0

No. È un modo per risolvere un problema e ci sono altri modi per risolverlo.

Molti linguaggi mainstream attuali (Java, .NET (C # + VB), PHP, scrivi il tuo) usano "break" e "continue" per saltare i loop. Entrambe le frasi "goto strutturato (s)".

Senza di loro:

String myKey = "mars";

int i = 0; bool found = false;
while ((i < MyList.Count) && (not found)) {
  found = (MyList[i].key == myKey);
  i++;   
}
if (found)
  ShowMessage("Key is " + i.toString());
else
  ShowMessage("Not found.");

Con loro:

String myKey = "mars";

for (i = 0; i < MyList.Count; i++) {
  if (MyList[i].key == myKey)
    break;
}
ShowMessage("Key is " + i.toString());

Si noti che, il codice "break" e "continue" è più breve e di solito trasforma "mentre" frasi in frasi "for" o "foreach".

Entrambi i casi sono una questione di stile di codifica. Preferisco non usarli , perché lo stile dettagliato mi permette di avere un maggiore controllo del codice.

In realtà, lavoro in alcuni progetti, dove era obbligatorio usare quelle frasi.

Alcuni sviluppatori potrebbero pensare che non siano necessariamente necessari, ma ipotetici, se dovessimo rimuoverli, dobbiamo rimuovere anche "while" e "do while" ("ripeti fino a", ragazzi pascal) ;-)

Conclusione, anche se preferisco non usarle, penso che sia un'opzione, non una cattiva pratica di programmazione.


Mi dispiace essere schizzinoso, ma nel tuo secondo esempio manca l'output per quando la chiave non viene trovata (quindi ovviamente sembra molto più breve).
FrustratedWithFormsDesigner

2
per non parlare del fatto che il primo esempio funziona solo se la chiave è l'ultima nell'elenco.
Mikhail,

@FrustratedWithFormsDesigner ESATTAMENTE. Mi sono messo in mostra per indicare perché è preferibile quel metodo ;-)
umlcat,

tuttavia, hai due routine con semantica diversa; pertanto, non sono logicamente equivalenti.
bit-twiddler

2
il tuo secondo esempio ha due bug, uno sintattico e uno logico. 1. Non verrà compilato perché l'iteratore non è dichiarato al di fuori dell'ambito del ciclo for (quindi non è disponibile nell'output della stringa). 2. Anche se l'iteratore è stato dichiarato all'esterno del ciclo, se la chiave non viene trovata nella raccolta, l'output della stringa stampa la chiave dell'ultimo elemento nell'elenco.
Evan Plaice,

0

Non sono contrario continuee breakin linea di principio, ma penso che siano costrutti di livello molto basso che molto spesso possono essere sostituiti da qualcosa di ancora migliore.

Sto usando C # come esempio qui, considera il caso di voler iterare su una raccolta, ma vogliamo solo gli elementi che soddisfano un predicato e non vogliamo fare più di un massimo di 100 iterazioni.

for (var i = 0; i < collection.Count; i++)
{
    if (!Predicate(item)) continue;
    if (i >= 100) break; // at first I used a > here which is a bug. another good thing about the more declarative style!

    DoStuff(item);
}

Questo sembra REASONABLY pulito. Non è molto difficile da capire. Penso che sarebbe molto utile essere più dichiarativi però. Confrontalo con il seguente:

foreach (var item in collection.Where(Predicate).Take(100))
    DoStuff(item);

Forse le chiamate Where and Take non dovrebbero nemmeno essere in questo metodo. Forse questo filtro dovrebbe essere fatto PRIMA che la raccolta sia passata a questo metodo. Ad ogni modo, allontanandoci dalle cose di basso livello e concentrandoci maggiormente sulla reale logica di business, diventa più chiaro ciò a cui siamo REALMENTE interessati. Diventare più facile separare il nostro codice in moduli coerenti che aderiscono maggiormente alle buone pratiche di progettazione e così sopra.

Roba di basso livello esisterà ancora in alcune parti del codice, ma vogliamo nasconderlo il più possibile, perché invece ci vuole energia mentale che potremmo usare per ragionare sui problemi aziendali.


-1

Code Complete ha una bella sezione sull'uso gotoe sui ritorni multipli dalla routine o dal loop.

In generale non è una cattiva pratica. breako continuedire esattamente cosa succede dopo. E sono d'accordo con questo.

Steve McConnell (autore di Code Complete) usa quasi gli stessi esempi per dimostrare i vantaggi dell'utilizzo di varie gotodichiarazioni.

Tuttavia uso eccessivo breako continuepotrebbe portare a software complessi e non mantenibili.

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.