È mentre (vero) con pratica di programmazione di rottura ctriggers?

Uso spesso questo modello di codice:

while(true) { //do something if() { break; } } 

Un altro programmatore mi ha detto che questa era una ctriggers pratica e che dovevo sostituirla con la più standard:

 while(!) { //do something } 

Il suo ragionamento era che si poteva “dimenticare la pausa” troppo facilmente e avere un ciclo infinito. Gli dissi che nel secondo esempio si poteva facilmente mettere in una condizione che non è mai tornata vera e quindi altrettanto facilmente avere un ciclo infinito, quindi entrambe sono pratiche ugualmente valide.

Inoltre, spesso preferisco il primo in quanto rende il codice più facile da leggere quando si hanno più punti di interruzione, cioè più condizioni che escono dal ciclo.

Qualcuno può arricchire questo argomento aggiungendo prove per una parte o l’altra?

C’è una discrepanza tra i due esempi. Il primo eseguirà il “fai qualcosa” almeno una volta ogni volta, anche se l’affermazione non è mai vera. Il secondo farà “qualcosa” solo quando l’affermazione sarà vera.

Penso che quello che stai cercando sia un ciclo do-while. Sono al 100% d’accordo sul fatto che while (true) non è una buona idea perché rende difficile mantenere questo codice e il modo in cui stai sfuggendo al loop è molto goto esque che è considerato una ctriggers pratica.

Provare:

 do { //do something } while (!something); 

Controlla la documentazione della tua lingua per la syntax esatta. Ma guarda questo codice, fondamentalmente fa ciò che è nel do, poi controlla la parte while per vedere se dovrebbe farlo di nuovo.

Per citare quel noto sviluppatore di giorni passati, Wordsworth:


In verità la prigione, alla quale condanniamo
Noi stessi, nessuna prigione è; e quindi per me,
In vari stati d’animo, era un passatempo da bind
All’interno della scarsa trama del terreno del Sonetto;
Soddisfatto se alcune anime (per tali necessità devono essere)
Chi ha sentito il peso di troppa libertà,
Dovrei trovare una breve consolazione lì, come ho trovato.

Wordsworth accettava i rigidi requisiti del sonetto come una struttura liberatoria, piuttosto che come una camicia di forza. Suggerirei che il cuore della “programmazione strutturata” consiste nel rinunciare alla libertà di build diagrammi di stream arbitrariamente complessi in favore di una liberatoria facilità di comprensione.

Sono d’accordo sul fatto che a volte un’uscita anticipata è il modo più semplice per esprimere un’azione. Tuttavia, la mia esperienza è stata che quando mi costringo a usare le strutture di controllo più semplici possibili (e penso davvero a progettare all’interno di quei vincoli), trovo più spesso che il risultato sia un codice più semplice e più chiaro. Lo svantaggio con

 while (true) { action0; if (test0) break; action1; } 

è facile lasciare che action0 e action1 diventino blocchi di codice più grandi e più grandi, o aggiungere “solo un altro” sequenza test-break-action, finché diventa difficile puntare a una linea specifica e rispondere alla domanda, “Quali condizioni so mantenere a questo punto? ” Quindi, senza fare regole per altri programmatori, cerco di evitare l’idioma while (true) {...} nel mio codice, quando ansible.

Quando puoi scrivere il tuo codice nel modulo

 while (condition) { ... } 

o

 while (!condition) { ... } 

senza uscite ( break , continue o goto ) nel corpo , quella forma è preferita, perché qualcuno può leggere il codice e comprendere la condizione di terminazione semplicemente guardando l’intestazione . Quello è buono.

Ma molti loop non si adattano a questo modello e il ciclo infinito con uscite esplicite nel mezzo è un modello onorevole . (I loop con continue sono di solito più difficili da capire rispetto ai loop con break .) Se vuoi qualche prova o autorità da citare, non guardare oltre il famoso documento di Don Knuth sulla Programmazione Strutturata con Goto Statements ; troverai tutti gli esempi, gli argomenti e le spiegazioni che potresti desiderare.

Un piccolo punto di idioma: scrivere while (true) { ... } ti marca come un vecchio programmatore Pascal o forse in questi giorni un programmatore Java. Se stai scrivendo in C o C ++, l’idioma preferito è

 for (;;) { ... } 

Non c’è una buona ragione per questo, ma dovresti scriverlo in questo modo perché questo è il modo in cui i programmatori C si aspettano di vederlo.

preferisco

 while(!) { //do something } 

ma penso che sia più una questione di leggibilità, piuttosto che il potenziale per “dimenticare la pausa”. Penso che dimenticare la break sia un argomento piuttosto debole, dato che sarebbe un bug e lo troverai e lo sistemerai subito.

L’argomento che ho contro l’uso di break per uscire da un ciclo infinito è che essenzialmente stai usando l’istruzione break come goto . Non sono religioso contro l’uso di goto (se il linguaggio lo supporta, è un gioco leale), ma cerco di sostituirlo se c’è un’alternativa più leggibile.

Nel caso di molti punti di break li sostituirò con

 while( ! || ! || ! ) { //do something } 

Il consolidamento di tutte le condizioni di arresto in questo modo rende molto più facile vedere cosa finirà questo ciclo. break dichiarazioni di break potrebbero essere sparse in giro, e questo è tutto tranne che leggibile.

while (true) potrebbe avere senso se hai molte affermazioni e vuoi fermarti se falliscono

  while (true) { if (!function1() ) return; if (!function2() ) return; if (!function3() ) return; if (!function4() ) return; } 

è meglio di

  while (!fail) { if (!fail) { fail = function1() } if (!fail) { fail = function2() } ........ } 

Javier ha fatto un commento interessante sulla mia precedente risposta (quella che citava Wordsworth):

Penso che mentre (true) {} è un costrutto più “puro” di while (condizione) {}.

e non ho potuto rispondere adeguatamente in 300 caratteri (mi dispiace!)

Nel mio insegnamento e tutoraggio, ho definito informalmente la “complessità” come “Quanto del resto del codice che devo avere nella mia testa per essere in grado di comprendere questa singola linea o espressione?” Più cose devo tenere a mente, più il codice è complesso. Più il codice mi dice esplicitamente, il meno complesso.

Quindi, con l’objective di ridurre la complessità, vorrei rispondere a Javier in termini di completezza e forza piuttosto che di purezza.

Penso a questo frammento di codice:

 while (c1) { // p1 a1; // p2 ... // pz az; } 

come esprimere due cose contemporaneamente:

  1. il corpo (intero) sarà ripetuto fintanto che c1 rimane vero, e
  2. al punto 1, dove viene eseguita a1 , c1 è garantito da tenere.

La differenza è una di prospettiva; il primo di questi ha a che fare con il comportamento dinamico esterno dell’intero ciclo in generale, mentre il secondo è utile per comprendere la garanzia interiore, statica su cui posso contare pensando a1 in particolare. Ovviamente l’effetto netto di a1 può invalidare c1 , richiedendo che io pensi più a fondo su cosa posso contare al punto 2, ecc.

Mettiamo un esempio (piccolo) specifico per pensare alla condizione e alla prima azione:

 while (index < length(someString)) { // p1 char c = someString.charAt(index++); // p2 ... } 

Il problema "esterno" è che il loop sta facendo chiaramente qualcosa all'interno di someString che può essere fatto solo finché l' index è posizionato in someString . Ciò stabilisce l'aspettativa che modificheremo o l' index o un someString all'interno del corpo (in una posizione e modo non noti fino a quando non esaminerò il corpo) in modo che alla fine si verifichi. Ciò mi dà sia il contesto che l'aspettativa per pensare al corpo.

La questione "interiore" è che siamo sicuri che l'azione che segue il punto 1 sarà legale, quindi leggendo il codice al punto 2 posso pensare a quello che viene fatto con un valore char che so è stato ottenuto legalmente. (Non possiamo nemmeno valutare la condizione se someString è un riferimento nullo, ma sto anche assumendo che abbiamo someString ciò nel contesto attorno a questo esempio!)

Al contrario, un ciclo del modulo:

 while (true) { // p1 a1; // p2 ... } 

mi delude su entrambi i problemi. A livello esterno, mi chiedo se questo significhi che dovrei davvero aspettarmi che questo ciclo continui a girare per sempre (es. Il ciclo di invio dell'evento principale di un sistema operativo), o se c'è qualcos'altro che sta succedendo. Questo non mi dà né un contesto esplicito per leggere il corpo, né un'aspettativa di ciò che costituisce un progresso verso una (incerta) conclusione.

A livello interiore, non ho assolutamente alcuna garanzia esplicita su nessuna circostanza che possa true al punto 1. La condizione true , che è ovviamente vera ovunque, è l'affermazione più debole ansible su ciò che possiamo sapere in qualsiasi punto del programma. Comprendere le precondizioni di un'azione è un'informazione preziosissima quando si cerca di pensare a ciò che l'azione realizza!

Quindi, suggerisco che il while (true) ... idioma sia molto più incompleto e debole, e quindi più complesso, rispetto a while (c1) ... secondo la logica che ho descritto sopra.

Il primo è OK se ci sono molti modi per interrompere il ciclo o se la condizione di rottura non può essere espressa facilmente all’inizio del ciclo (ad esempio, il contenuto del ciclo deve essere eseguito a metà corsa, ma l’altra metà non deve essere eseguita , sull’ultima iterazione).

Ma se puoi evitarlo, dovresti, perché la programmazione dovrebbe riguardare la scrittura di cose molto complesse nel modo più ovvio ansible, mentre si implementano anche le funzionalità in modo corretto e performante. Ecco perché il tuo amico è, nel caso generale, corretto. Il modo in cui il tuo amico scrive i costrutti del ciclo è molto più ovvio (supponendo che le condizioni descritte nel paragrafo precedente non si ottengano).

A volte hai bisogno di un ciclo infinito, ad esempio in ascolto sulla porta o in attesa di connessione.

Quindi mentre (vero) … non dovrebbe essere classificato come buono o cattivo, lascia che la situazione decida cosa usare

Dipende da cosa stai cercando di fare, ma in generale preferisco mettere il condizionale nel mentre.

  • È più semplice, dal momento che non è necessario un altro test nel codice.
  • È più facile da leggere, dal momento che non devi andare a caccia di una pausa all’interno del ciclo.
  • Stai reinventando la ruota. L’objective è di fare qualcosa finché un test è vero. Perché sovvertire ciò ponendo la condizione di rottura da qualche altra parte?

Userei un ciclo while (vero) se stavo scrivendo un demone o altro processo che dovrebbe essere eseguito finché non viene ucciso.

Se c’è una (e solo una) condizione di rottura non eccezionale, è preferibile porre tale condizione direttamente nel costrutto del stream di controllo (nel frattempo). Vedere (vero) {…} mi fa pensare che non ci sia un modo semplice per enumerare le condizioni di pausa e mi fa pensare “guarda attentamente a questo e pensa attentamente alle condizioni di pausa (cosa è stato impostato prima loro nel ciclo corrente e cosa potrebbe essere stato impostato nel ciclo precedente) ”

In breve, sono con il tuo collega nel caso più semplice, ma mentre (vero) {…} non è raro.

La risposta perfetta del consulente: dipende. La maggior parte dei casi, la cosa giusta da fare è utilizzare un ciclo while

 while (condition is true ) { // do something } 

o un “ripetere fino a” che è fatto in un linguaggio simile a C. con

 do { // do something } while ( condition is true); 

Se uno di questi casi funziona, usali.

A volte, come nel loop interno di un server, vuoi dire che un programma dovrebbe continuare fino a quando qualcosa di esterno lo interrompe. (Si consideri, ad esempio, un demone httpd: non si fermerà a meno che non si blocchi o venga arrestato da un arresto.)

POI E SOLO POI usare un istante (1):

 while(1) { accept connection fork child process } 

Il caso finale è la rara occasione in cui si desidera eseguire parte della funzione prima di terminare. In tal caso, utilizzare:

 while(1) { // or for(;;) // do some stuff if (condition met) break; // otherwise do more stuff. } 

C’è una domanda sostanzialmente identica già in SO a Is WHILE VERO … BREAK … END WHILE un buon design? . @Glomek ha risposto (in un messaggio sottovalutato):

A volte è un ottimo design. Vedi la programmazione strutturata con le dichiarazioni Goto di Donald Knuth per alcuni esempi. Uso spesso questa idea di base per i cicli che eseguono “n e mezza volte”, in particolare i cicli di lettura / elaborazione. Tuttavia, generalmente cerco di avere una sola frase break. Questo rende più facile ragionare sullo stato del programma dopo che il ciclo termina.

Un po ‘più tardi, ho risposto con il commento, e anche tristemente sottovalutato, (in parte perché non ho notato Glomek’s la prima volta, penso):

Un affascinante articolo è la “Programmazione strutturata con go to Statements” di Knuth del 1974 (disponibile nel suo libro “Literate Programming”, e probabilmente anche altrove). Discute, tra le altre cose, modi controllati di uscire da loop e (non usando il termine) l’istruzione loop-and-a-half.

Ada fornisce anche costrutti di looping, inclusi

 loopname: loop ... exit loopname when ...condition...; ... end loop loopname; 

Il codice della domanda originale è simile a questo nell’intento.

Una differenza tra l’elemento SO referenziato e questa è l’interruzione finale; questo è un ciclo a colpo singolo che usa la pausa per uscire presto dal ciclo. Ci sono state domande sul fatto che sia anche un buon stile – non ho il riferimento incrociato a portata di mano.

No, non è male dato che potresti non sapere sempre la condizione di uscita quando imposti il ​​loop o potresti avere più condizioni di uscita. Tuttavia richiede più attenzione per prevenire un ciclo infinito.

Non è tanto la parte (vera) del tempo che è male, ma il fatto che tu debba rompere o uscire da esso è il problema. break e goto non sono metodi accettabili per il controllo del stream.

Anche io non capisco il punto. Anche in qualcosa che scorre per l’intera durata di un programma, puoi almeno avere come un booleano chiamato Esci o qualcosa che hai impostato su true per uscire correttamente dal ciclo in un ciclo come quando (! Esci) … Non solo chiamando break ad un punto arbitrario e saltando fuori,

Il problema è che non tutti gli algoritmi si attaccano al modello “while (cond) {action}”.

Il modello di loop generale è come questo:

 loop_prepare loop: action_A if(cond) exit_loop action_B goto loop after_loop_code 

Quando non c’è azione_A puoi sostituirlo con:

 loop_prepare while(cond) action_B after_loop_code 

Quando non c’è azione_B puoi sostituirlo con:

 loop_prepare do action_A while(cond) after_loop_code 

Nel caso generale, action_A verrà eseguito n volte e action_B verrà eseguito (n-1) volte.

Un esempio di vita reale è: stampa tutti gli elementi di una tabella separati da virgole. Vogliamo tutti gli n elementi con (n-1) virgole.

Puoi sempre fare alcuni trucchi per attenersi al modello while-loop, ma questo ripeterà sempre il codice o controllerà due volte la stessa condizione (per ogni loop) o aggiungerà una nuova variabile. Quindi sarai sempre meno efficiente e meno leggibile rispetto al modello del ciclo while-true-break.

Esempio di “brutto” (cattivo): aggiungere variabile e condizione

 loop_prepare b=true // one more local variable : more complex code while(b): // one more condition on every loop : less efficient action_A if(cond) b=false // the real condition is here else action_B after_loop_code 

Esempio di “brutto” (cattivo): ripetere il codice. Il codice ripetuto non deve essere dimenticato durante la modifica di una delle due sezioni.

 loop_prepare action_A while(cond): action_B action_A after_loop_code 

Nota: nell’ultimo esempio, il programmatore può offuscare (volenti o nolenti) il codice mescolando “loop_prepare” con il primo “action_A” e action_B con il secondo action_A. Quindi può avere la sensazione che non lo stia facendo.

Probabilmente è corretto.

Funzionalmente i due possono essere identici.

Tuttavia, per la leggibilità e la comprensione del stream del programma, il tempo (condizione) è migliore. La pausa sa di più di una sorta di goto. Il tempo (condizione) è molto chiaro sulle condizioni che continuano il ciclo, ecc. Ciò non significa che la rottura è sbagliata, solo può essere meno leggibile.

Alcuni vantaggi dell’utilizzo di quest’ultimo costrutto che mi vengono in mente:

  • è più facile capire cosa sta facendo il ciclo senza cercare interruzioni nel codice del ciclo.

  • se non usi altre interruzioni nel codice del ciclo, c’è un solo punto di uscita nel tuo ciclo e questa è la condizione while ().

  • generalmente finisce per essere meno codice, che aggiunge alla leggibilità.

Preferisco l’approccio while (!) Perché trasmette più chiaramente e immediatamente l’intento del ciclo.

Si è parlato molto di leggibilità qui e la sua molto ben costruita, ma come con tutti i cicli che non sono fissi in termini di dimensioni (ad esempio fare mentre e mentre) si corre a rischio.

 His reasoning was that you could "forget the break" too easily and have an endless loop. 

All’interno di un ciclo while si sta infatti richiedendo un processo che funziona a tempo indeterminato a meno che qualcosa non accada, e se quel qualcosa non accade all’interno di un certo parametro, otterrete esattamente quello che volevate … un ciclo infinito.

Penso che il vantaggio dell’uso di “while (true)” sia probabilmente quello di consentire a più condizioni di uscita di scrivere più facilmente, specialmente se queste condizioni di uscita devono apparire in una posizione diversa all’interno del blocco di codice. Tuttavia, per me, potrebbe essere caotico quando devo eseguire l’esecuzione a secco del codice per vedere come il codice interagisce.

Personalmente cercherò di evitare mentre (vero). Il motivo è che ogni volta che guardo indietro al codice scritto in precedenza, di solito trovo che ho bisogno di capire quando corre / termina più di quello che effettivamente fa. Pertanto, per prima cosa individuare i “break” è un po ‘fastidioso per me.

Se è necessaria una condizione di uscita multipla, tendo a rifattorizzare la condizione che determina la logica in una funzione separata in modo che il blocco del ciclo appaia pulito e più facile da capire.

Ciò che consiglia il tuo amico è diverso da quello che hai fatto. Il tuo codice è più simile a

 do{ // do something }while(!); 

che eseguono sempre il ciclo almeno una volta, indipendentemente dalla condizione.

Ma ci sono momentjs in cui le pause sono perfettamente adatte, come menzionato da altri. In risposta alla preoccupazione del tuo amico di “dimenticare la pausa”, scrivo spesso nel seguente modulo:

 while(true){ // do something if() break; // continue do something } 

Con una buona indentazione, il punto di interruzione è chiaro per chi legge per la prima volta il codice, sembra strutturale come i codici che si rompono all’inizio o alla fine di un ciclo.

usando loop come

while (1) {do stuff}

è necessario in alcune situazioni. Se si esegue una programmazione di sistemi embedded (si pensi ai microcontrollori come PIC, MSP430 e programmazione DSP), quasi tutto il codice sarà in un ciclo while (1). Quando si codifica per i DSP a volte è sufficiente un po ‘(1) {} e il resto del codice è una routine di servizio di interruzione (ISR).

Mi piace for loops meglio:

 for (;;) { ... } 

o anche

 for (init();;) { ... } 

Ma se la funzione init() assomiglia al corpo del ciclo, stai meglio con

 do { ... } while(condition);