¿Son tan malos los bucles "while (verdadero)"? [cerrado]


218

He estado programando en Java durante varios años, pero recientemente regresé a la escuela para obtener un título formal. Me sorprendió bastante saber que, en mi última tarea, perdí puntos por usar un bucle como el siguiente.

do{
     //get some input.
     //if the input meets my conditions, break;
     //Otherwise ask again.
} while(true)

Ahora, para mi prueba, solo estoy buscando información de la consola, pero me dijeron que se desaconseja este tipo de bucle porque el uso breakes similar goto, simplemente no lo hacemos.

Entiendo completamente las trampas gotoy su primo de Java break:label, y tengo el buen sentido de no usarlas. También me doy cuenta de que un programa más completo proporcionaría otros medios de escape, por ejemplo, para terminar el programa, pero esa no fue una razón por la que mi profesor lo citó, así que ...

¿Qué tiene de malo do-while(true)?


23
Pregúntale a tu maestro, ese tipo de cosas es bastante subjetivo.
Chris Eberle

18
Este artículo me ha resultado útil para comprender qué es perjudicial para goto . La comparación con breakes probablemente bien intencionada, pero en realidad mal entendida. Quizás pueda educar a su profesor sobre esto;) En mi experiencia, los profesores no saben mucho sobre el oficio de la programación.
Magnus Hoff

100
Lo único realmente malo e indiscutible de esto es en mi mente, el hecho de que do {} while (true)es equivalente while(true) {}y este último es, con mucho, la forma más convencional y mucho más clara.
Voo

11
Si alguien no aprecia el simple poder expresivo de break, debería intentar programar en un lenguaje sin él. ¡No toma muchos bucles antes de que lo desee!
Steven

16
No estoy de acuerdo con la etiqueta de tarea.
aaaa bbbb

Respuestas:


220

No diría que es malo , pero igualmente al menos normalmente buscaría una alternativa.

En situaciones donde es lo primero que escribo, casi siempre trato de refactorizarlo para que sea más claro. A veces no se puede evitar (o la alternativa es tener una boolvariable que no haga nada significativo excepto indicar el final del ciclo, con menos claridad que una breakdeclaración), pero al menos vale la pena intentarlo.

Como un ejemplo de dónde es más fácil de usar breakque una bandera, considere:

while (true)
{
    doStuffNeededAtStartOfLoop();
    int input = getSomeInput();
    if (testCondition(input))
    {
        break;
    }
    actOnInput(input);
}

Ahora forcémoslo a usar una bandera:

boolean running = true;
while (running)
{
    doStuffNeededAtStartOfLoop();
    int input = getSomeInput();
    if (testCondition(input))
    {
        running = false;
    }
    else
    {
        actOnInput(input);
    }
}

Veo este último como más complicado de leer: tiene un elsebloque adicional , actOnInputestá más sangrado, y si estás tratando de averiguar qué sucede cuando testConditionregresa true, debes mirar cuidadosamente el resto del bloque para verificar que haya no es algo después del elsebloque que ocurriría si runningse ha establecido falseo no.

La breakdeclaración comunica la intención con mayor claridad y permite que el resto del bloque continúe con lo que necesita hacer sin preocuparse por las condiciones anteriores.

Tenga en cuenta que este es exactamente el mismo tipo de argumento que las personas tienen sobre múltiples declaraciones de retorno en un método. Por ejemplo, si puedo calcular el resultado de un método dentro de las primeras líneas (por ejemplo, porque alguna entrada es nula, vacía o cero), me resulta más claro devolver esa respuesta directamente que tener una variable para almacenar el resultado , luego un bloque completo de otro código, y finalmente una returndeclaración.


3
Estoy de acuerdo en que no es la primera herramienta que busco, pero parece que solucionó el problema de manera tan limpia, y me gusta el código limpio.
JHarnach

3
@ X-Zero: Sí, a veces. Si puede convertir el "descanso" en un "retorno", eso a menudo es bueno ... aunque aún podría terminar con unwhile (true)
Jon Skeet

21
@trutheality: sin embargo, prefiero que la mayor parte de mi código sea lo menos desenfadado posible. Y una condición con una asignación compuesta me parece más complicada.
Jon Skeet

3
@Vince: Sí, y eso es genial si puedes expresar la condición fácilmente al comienzo del ciclo . Pero a veces no puedes, y esa es la situación de la que estamos hablando. Tenga en cuenta las primeras oraciones de mi respuesta.
Jon Skeet

66
@Ismail: espero que no. Las publicaciones deben juzgarse únicamente por su contenido, no por el autor. Demonios, incluso desestimaría una publicación de Eric Lippert si sintiera que era inexacta / inútil. Eso no ha sucedido todavía :)
Jon Skeet

100

AFAIK nada, de verdad. Los maestros simplemente son alérgicos goto, porque escucharon en algún lugar que es realmente malo. De lo contrario, simplemente escribirías:

bool guard = true;
do
{
   getInput();
   if (something)
     guard = false;
} while (guard)

Que es casi lo mismo.

Tal vez esto sea más limpio (porque toda la información de bucle está contenida en la parte superior del bloque):

for (bool endLoop = false; !endLoop;)
{

}

44
Me gustan sus sugerencias ya que la condición de terminación es mucho más visible. Eso significa que al leer el código, pronto comprenderá lo que está tratando de hacer. Nunca usaría un bucle infinito, pero usaría sus dos versiones con frecuencia.
Trineo

44
¿El consenso es que una bandera es mejor que romperse en gran medida porque expresa intención? Puedo ver que es beneficioso. Todas son respuestas sólidas, pero marcaré esto como aceptado.
JHarnach

20
Ambos padecen la contaminación del resto del bucle (al menos en algunas situaciones) al tener que continuar hasta el final del cuerpo del bucle, incluso si sabes que estás rompiendo . Pondré un ejemplo en mi respuesta ...
Jon Skeet

8
Me gusta más la respuesta de Jon Skeet. Además, while (true) {} es MUCHO mejor que {} while (true)
aaaa bbbb

1
Odio que Dijkstra haya escrito ese artículo de GoTo. Si bien GoTo ciertamente fue abusado en el pasado, no significa que nunca debas usarlo. Además, Exit For, Break, Exit While, Try / Catch son solo formas especializadas de Goto. Gotos a menudo puede hacer que el código sea más legible. Y sí, soy consciente de que se puede hacer cualquier cosa sin Goto. Eso no significa que deba hacerlo.
Kibbee

39

Douglas Crockford hizo un comentario sobre cómo deseaba JavaScript contuviera una loopestructura:

loop
{
  ...code...
}

Y tampoco creo que Java sea ​​peor por tener una loopestructura.

No hay nada inherentemente malo con while(true)bucles, pero no es una tendencia a que los maestros para desalentar ellos. Desde la perspectiva de la enseñanza, es muy fácil hacer que los estudiantes creen bucles interminables y no entiendan por qué nunca se escapa el bucle.

Pero lo que rara vez mencionan es que todos los mecanismos de bucle se pueden replicar con while(true)bucles.

while( a() )
{
  fn();
}

es lo mismo que

loop
{
  if ( !a() ) break;
  fn();
}

y

do
{
  fn();
} while( a() );

es lo mismo que:

loop
{
  fn();
  if ( !a() ) break;
}

y

for ( a(); b(); c() )
{
  fn();
}

es lo mismo que:

a();
loop
{
  if ( !b() ) break;
  fn();
  c();
}

Siempre que pueda configurar sus bucles de una manera que funcione, la construcción que elija usar no es importante. Si resulta que encaja en un forbucle, use unfor bucle.

Una última parte: mantenga sus bucles simples. Si hay mucha funcionalidad que debe suceder en cada iteración, póngala en una función. Siempre puede optimizarlo una vez que lo tiene funcionando.


2
+1: Existen complejidades adicionales con forbucles cuando las continuedeclaraciones están involucradas, pero no son una extensión importante.
Donal Fellows

Por lo general, uso un bucle for cuando hay una secuencia que quiero ejecutar y un bucle while cuando hay una condición que quiero cumplir. Esto mantiene el código más legible de la OMI.
dascandy

44
¿Nadie escribe for (;;) {más? (Pronunciado "para siempre"). Esto solía ser muy popular.
Dawood ibn Kareem

16

En 1967, Edgar Dijkstra escribió un artículo en una revista comercial sobre por qué debería eliminarse el goto de los lenguajes de alto nivel para mejorar la calidad del código. Todo esto surgió de un paradigma de programación llamado "programación estructurada", aunque ciertamente no todos están de acuerdo en que goto significa automáticamente código incorrecto.

El quid de la programación estructurada es esencialmente que la estructura del código debe determinar su flujo en lugar de tener problemas o interrupciones o continuar determinando el flujo, siempre que sea posible. De manera similar, tener múltiples puntos de entrada y salida a un bucle o función también se desaconseja en ese paradigma.

Obviamente, este no es el único paradigma de programación, pero a menudo se puede aplicar fácilmente a otros paradigmas como la programación orientada a objetos (ala Java).

Probablemente a sus maestros se les haya enseñado, y está tratando de enseñarle a su clase que es mejor que evitemos el "código de espagueti" asegurándose de que nuestro código esté estructurado y siguiendo las reglas implícitas de programación estructurada.

Si bien no hay nada intrínsecamente "incorrecto" con una implementación que usa break, algunos consideran que es mucho más fácil leer el código donde la condición para el bucle se especifica explícitamente dentro de la condición while (), y elimina algunas posibilidades de ser demasiado complicado. Definitivamente, existen dificultades para usar una condición while (verdadera) que parece aparecer con frecuencia en el código por parte de programadores novatos, como el riesgo de crear accidentalmente un bucle infinito o hacer que el código sea difícil de leer o innecesariamente confuso.

Irónicamente, el manejo de excepciones es un área donde la desviación de la programación estructurada ciertamente surgirá y se esperará a medida que avance en la programación en Java.

También es posible que su instructor haya esperado que demuestre su capacidad para usar una estructura de bucle o sintaxis particular que se enseña en ese capítulo o lección de su texto, y aunque el código que escribió es funcionalmente equivalente, es posible que no haya demostrado habilidad particular que se suponía que estabas aprendiendo en esa lección.


2
Aquí está el artículo de Edsger Dijkstra . Es una buena lectura.
Roy Prins

14

La convención habitual de Java para leer entradas es:

import java.io.*;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String strLine;

while ((strLine = br.readLine()) != null) {
  // do something with the line
}

Y la convención habitual de C ++ para leer entradas es:

#include <iostream>
#include <string>
std::string data;
while(std::readline(std::cin, data)) {
  // do something with the line
}

Y en C, es

#include <stdio.h>
char* buffer = NULL;
size_t buffer_size;
size_t size_read;
while( (size_read = getline(&buffer, &buffer_size, stdin)) != -1 ){
  // do something with the line
}
free(buffer);

o si está convencido de saber cuánto dura la línea de texto más larga en su archivo, puede hacerlo

#include <stdio.h>
char buffer[BUF_SIZE];
while (fgets(buffer, BUF_SIZE, stdin)) {
  //do something with the line
}

Si está probando para ver si su usuario ingresó un quitcomando, es fácil extender cualquiera de estas 3 estructuras de bucle. Lo haré en Java por ti:

import java.io.*;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;

while ((line = br.readLine()) != null  && !line.equals("quit") ) {
  // do something with the line
}

Entonces, aunque ciertamente hay casos en los que breako gotoestá justificado, si todo lo que está haciendo es leer un archivo o la consola línea por línea, entonces no debería necesitar un while (true)bucle para lograrlo: su lenguaje de programación ya le ha proporcionado con un lenguaje apropiado para usar el comando de entrada como condición de bucle.


De hecho, si está utilizando un while (true)bucle en lugar de uno de estos bucles de entrada convencionales, es posible que se olvide de verificar el final del archivo.
Ken Bloom

3
Estás logrando esto de manera efectiva al insertar la primera parte del bucle en el whilecondicional. En la condición final de Java ya se está volviendo bastante fuerte, y si tuvo que hacer una manipulación extensa para decidir si continuar o no, puede ser bastante largo. Podría dividirlo en una función separada, y eso podría ser lo mejor. Pero si lo desea en una función, y hay trabajo no trivial que hacer antes de decidir si continuar, while(true)puede ser lo mejor.
Poolie

@poolie: Entonces probablemente sea mejor usar el comando de lectura como la condición del bucle (que verifica si hay EOF), y verifica tus otras condiciones dentro del bucle como declaraciones de interrupción.
Ken Bloom

1
Por lo que vale, gets()no es una convención común. Es altamente propicio para desbordamientos de búfer. fgets(buffer, BUFFER_SIZE, file)es mucho más como la práctica estándar.
Dave

@Dave: ahora he editado la respuesta para usar fgets.
Ken Bloom

12

No es algo tan terrible, pero debe tener en cuenta a otros desarrolladores al codificar. Incluso en la escuela.

Sus compañeros desarrolladores deberían poder ver la cláusula de salida para su ciclo, en la declaración del ciclo. No hiciste eso. Ocultaste la cláusula de salida en el medio del bucle, haciendo más trabajo para alguien que viene y trata de entender tu código. Esta es la misma razón por la que se evitan cosas como "descanso".

Dicho esto, todavía verás cosas como esta en MUCHO código en el mundo real.


44
Si el ciclo comienza con while (true), es bastante obvio que habrá un breako returndentro de él, o que se ejecutará para siempre. Ser directo es importante, pero while(true)no es especialmente malo, en sí mismo. Las variables que tienen invariantes complejos a través de iteraciones de bucle serían un ejemplo de algo que causa mucha más angustia.
Poolie

44
Intenta ir a tres niveles de profundidad en una búsqueda y luego explotar de alguna manera. El código será mucho peor si no regresas desde ese punto.
dascandy

11

Es tu arma, tu bala y tu pie ...

Es malo porque estás pidiendo problemas. No serán usted ni ninguno de los otros pósters de esta página los que tengan ejemplos de bucles while cortos / simples.

El problema comenzará en un momento muy aleatorio en el futuro. Puede ser causado por otro programador. Puede ser la persona que instala el software. Puede ser el usuario final.

¿Por qué? Tenía que averiguar por qué una aplicación de 700K LOC comenzaría a quemar gradualmente el 100% del tiempo de CPU hasta que cada CPU estuviera saturada. Fue un ciclo increíble (cierto). Era grande y desagradable, pero se reducía a:

x = read_value_from_database()
while (true) 
 if (x == 1)
  ...
  break;
 else if (x ==2)
  ...
  break;
and lots more else if conditions
}

No había otra rama final. Si el valor no coincide con una condición if, el bucle siguió ejecutándose hasta el final de los tiempos.

Por supuesto, el programador culpó a los usuarios finales por no elegir un valor que el programador esperaba. (Luego eliminé todas las instancias de while (verdadero) en el código).

En mi humilde opinión, no es una buena programación defensiva utilizar construcciones como while (verdadero). Volverá para atormentarte.

(Pero sí recuerdo a los profesores que se gradúan si no comentamos cada línea, incluso para i ++;)


3
+1 por el comentario sobre programación defensiva.
JHarnach

3
En su ejemplo, el código es estúpido no por la elección del while (verdadero) sino por la idea de poner un bucle alrededor del código.
Florian F

De hecho, ¿cuál fue el punto de ese ciclo? :)
juzzlin

6

Es malo en el sentido de que se prefieren las construcciones de programación estructurada a las declaraciones (algo no estructuradas) de romper y continuar. En comparación, se prefieren "ir a" según este principio.

Siempre recomendaría hacer su código lo más estructurado posible ... aunque, como señala Jon Skeet, ¡no lo haga más estructurado que eso!


5

Según mi experiencia en la mayoría de los casos, los bucles tienen la condición "principal" para continuar. Esta es la condición en la que debería escribirse el operador while (). Todas las demás condiciones que pueden romper el ciclo son secundarias, no tan importantes, etc. Pueden escribirse como if() {break}declaraciones adicionales .

while(true) a menudo es confuso y es menos legible.

Creo que estas reglas no cubren el 100% de los casos, pero probablemente solo el 98% de ellos.


Bien dicho. Es como utilizar un bucle for con: while (verdadero) {i ++; ...}. Está enterrando la condición del bucle dentro del bucle en lugar de en la 'firma'.
LegendLength

3

Si bien no es necesariamente una respuesta de por qué no usarlo while (true), siempre he encontrado este cómic y la declaración del autor que lo acompaña una explicación sucinta de por qué hacer el tiempo en lugar de hacerlo.

Con respecto a su pregunta: no hay ningún problema inherente con

while(true) {
   do_stuff();
   if(exit_time) {
      break;
   }
}

... si sabes lo que estás haciendo y asegurándote de que exit_timeen algún momento evaluarástrue .

Los maestros te desalientan de usar while(true)porque hasta y a menos que estés en el punto de que sabes exactamente lo que estás haciendo, es una manera fácil de cometer un error crítico.


Creo que configurar exit_time = false; while(!exit_time) { execute_stuff(); }y do { execute_stuff(); } while(! exit_time );son mucho más claros que tener un if( condition ) { break; }al final de un ciclo con un while(true). Los descansos son cortocircuitos en bucles, perfectamente bien cuando se usan como cortocircuitos en el medio de un bucle, pero solo debe evaluar una condición en la declaración while frente a tener un descanso al final de un bucle.
dr jimbob

Con respecto a ese cómic: mientras que do-while no puede hacer todo lo que while puede hacer, do-while a veces es mejor hacer lo que do-while puede hacer, incluso mientras while también puede hacer.
Theraot

3

Puede usar una bandera booleana para indicar cuándo finalizar el ciclo while. Breakygo to eran razones para que el software fuera demasiado difícil de mantener, la crisis del software (tm), y debería evitarse, y fácilmente puede serlo también.

Es una pregunta si eres pragmático o no. Los codificadores pragmáticos podrían usar el descanso en esa situación simple.

Pero es bueno obtener el hábito de no usarlos, de lo contrario, puede usarlos fuera del hábitat en situaciones inadecuadas, como en complicados bucles anidados donde la legibilidad y la facilidad de mantenimiento de su código se vuelven más difíciles de usar break.


8
¿Porque mantener una bandera booleana probablemente no sea más claro, y puede ser menos claro, en mi experiencia?
Jon Skeet

3
@ Jon Skeet Bueno, es una cuestión de ir con un buen habbit o entrenar a uno malo usando break. ¿Un bool llamado "correr" no está claro para ti? Es obvio, fácil de depurar, y como mencioné antes, un buen habbit es algo bueno para mantener. ¿Dónde está el problema?
Daniel Leschkowski

44
Un bool llamado running que luego requiere que tenga un if (running)bucle dentro del bucle, sangrando todo el resto del código cuando todo lo que quiero es salir del bucle definitivamente es menos claro para mí que una simple declaración de interrupción que establece exactamente lo que quiero hacer. Parece que está considerando usar el descanso como un mal hábito axiomáticamente , no lo considero así.
Jon Skeet

2
¿Por qué tendrías un if (en ejecución) dentro del bucle? Al igual que con break al final del ciclo, verifica si desea salir y voltear la bandera en lugar de usar break y usar la bandera en while (running) en lugar de while (true). No entiendo tu punto, en serio. Estoy de acuerdo en que un codificador pragmático podría usar el descanso en ciertas situaciones, pero realmente no recibo los comentarios que hizo bajo mi sugerencia
Daniel Leschkowski

3
Está asumiendo que no necesita hacer nada dentro del ciclo después de haber determinado si debe abandonar o no. Por ejemplo, en la situación del OP, necesita pedir más información si no está renunciando.
Jon Skeet

3

Quizás tengo mala suerte. O tal vez solo me falta una experiencia. Pero cada vez que recuerdo tratar con while(true)tener breakadentro, era posible mejorar el código aplicando el método de extracción al bloque while , que mantenía el while(true)pero (¿por coincidencia?) Transformaba todos los breakmensajes enreturn s.

En mi experiencia, while(true)sin pausas (es decir, con devoluciones o lanzamientos) son bastante cómodas y fáciles de entender.


  void handleInput() {
      while (true) {
          final Input input = getSomeInput();
          if (input == null) {
              throw new BadInputException("can't handle null input");
          }
          if (input.isPoisonPill()) {
              return;
          }
          doSomething(input);
      }
  }

3

Creo que sí, es bastante malo ... o al menos, para muchos desarrolladores. Es sintomático de los desarrolladores que no piensan en sus condiciones de bucle. En consecuencia, hay errores propensos.


2

No hay ningún problema importante while(true)con las breakdeclaraciones, sin embargo, algunos pueden pensar que disminuye ligeramente la legibilidad del código. Trate de dar nombres significativos a las variables, evalúe las expresiones en el lugar apropiado.

Para su ejemplo, parece mucho más claro hacer algo como:

do {
   input = get_input();
   valid = check_input_validity(input);    
} while(! valid)

Esto es especialmente cierto si el ciclo do while se alarga: usted sabe exactamente dónde se realiza la comprobación para ver si hay una iteración adicional. Todas las variables / funciones tienen nombres apropiados a nivel de abstracción. loswhile(true) declaración dice que el procesamiento no está en el lugar que pensabas.

Tal vez desee una salida diferente la segunda vez a través del ciclo. Algo como

input = get_input();
while(input_is_not_valid(input)) {
    disp_msg_invalid_input();
    input = get_input();
}

me parece más legible entonces

do {
    input = get_input();
    if (input_is_valid(input)) {
        break;
    }
    disp_msg_invalid_input();
} while(true);

De nuevo, con un ejemplo trivial, ambos son bastante legibles; pero si el bucle se hizo muy grande o profundamente anidado (lo que significa que probablemente ya debería haber refactorizado), el primer estilo puede ser un poco más claro.


1

Utilizo algo similar, pero con lógica opuesta, en muchas de mis funciones.

DWORD dwError = ERROR_SUCCESS;

do
{
    if ( (dwError = SomeFunction()) != ERROR_SUCCESS )
    {
         /* handle error */
         continue;
    }

    if ( (dwError = SomeOtherFunction()) != ERROR_SUCCESS )
    {
         /* handle error */
         continue;
    }
}
while ( 0 );

if ( dwError != ERROR_SUCCESS )
{
    /* resource cleanup */
}

1

Es más una cuestión de estética, mucho más fácil de leer código donde sabes explícitamente por qué el bucle se detendrá justo en la declaración del bucle.


1

Yo diría que, en general, la razón por la que no se considera una buena idea es que no está utilizando la construcción en todo su potencial. Además, tiendo a pensar que a muchos instructores de programación no les gusta cuando sus estudiantes entran con "equipaje". Con eso quiero decir que creo que les gusta ser la influencia principal en el estilo de programación de sus estudiantes. Entonces, tal vez eso sea solo una manía del instructor.


1

Para mí, el problema es la legibilidad.

Una declaración while con una condición verdadera no le dice nada sobre el ciclo. Hace que el trabajo de entenderlo sea mucho más difícil.

¿Qué sería más fácil de entender de estos dos fragmentos?

do {
  // Imagine a nice chunk of code here
} while(true);

do {
  // Imagine a nice chunk of code here
} while(price < priceAllowedForDiscount);

0

Supongo que usar break para tu maestro es como romper una rama de árbol para obtener la fruta, usa otros trucos (inclina la rama) para que obtengas la fruta y la rama siga viva. :)


0

1) No hay nada malo con un do -while(true)

2) Tu maestro está equivocado.

NSFS !!:

3) La mayoría de los maestros son maestros y no programadores.


@Connell ¡espera a que tu maestra escuche eso!
Pacerier

1
Soy autodidacta en el frente de programación. Todo lo que tengo a juzgar por mi TI es maestro en la escuela secundaria, que nos enseñó a hacer sitios web con Dreamweaver de tal manera que todo era un div con posición absoluta ...
Connell

@Conecte mi publicación para mostrar su acuerdo
Pacerier

2
"Los que no pueden hacer, enseñan". - Esa es una generalización bastante grande, ¿no te parece? ¿Deben los doctores, ingenieros, pilotos, etc. todos ser autodidactas ya que sus instructores son incompetentes según usted?
filip-fku

@ filip-fku wow wow ~ ¡genial, genial!
Pacerier

0

Puede ser malo si su bucle se ejecuta en un subproceso en segundo plano, por lo que cuando cierre su aplicación al terminar un subproceso de interfaz de usuario, ese fragmento de código continuará ejecutándose. Como ya han dicho otros, siempre debe usar algún tipo de cheque para proporcionar una forma de cancelación.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.