while tutorial true loop for else while-loop readability

while loop - tutorial - ¿Es cierto(verdad) con la práctica de programación de romper mal?



while arduino tutorial (23)

A menudo uso este patrón de código:

while(true) { //do something if(<some condition>) { break; } }

Otro programador me dijo que esto era una mala práctica y que debería reemplazarla por la más estándar:

while(!<some condition>) { //do something }

Su razonamiento era que podía "olvidar el descanso" con demasiada facilidad y tener un ciclo infinito. Le dije que en el segundo ejemplo podrías poner fácilmente una condición que nunca regresó verdadera y así tener un bucle infinito, por lo que ambas son prácticas igualmente válidas.

Además, a menudo prefiero el primero ya que hace que el código sea más fácil de leer cuando tienes múltiples puntos de quiebre, es decir, múltiples condiciones que salen del ciclo.

¿Alguien puede enriquecer este argumento agregando evidencia para un lado o el otro?


Él es probablemente correcto.

Funcionalmente, los dos pueden ser idénticos.

Sin embargo, para la legibilidad y el flujo del programa de comprensión, el tiempo (condición) es mejor. El descanso huele más de un goto de tipo. El tiempo (condición) es muy claro en las condiciones que continúan el ciclo, etc. Eso no significa que el corte sea incorrecto, solo puede ser menos legible.


Algunas ventajas de usar el último constructo que viene a mi mente:

  • es más fácil entender lo que está haciendo el bucle sin buscar interrupciones en el código del bucle.

  • Si no utiliza otros cortes en el código del bucle, solo hay un punto de salida en su bucle y esa es la condición while ().

  • generalmente termina siendo menos código, lo que aumenta la legibilidad.


Creo que el beneficio de usar "while (true)" es probablemente permitir que las condiciones de salida múltiples sean más fáciles de escribir, especialmente si estas condiciones de salida deben aparecer en diferentes ubicaciones dentro del bloque de código. Sin embargo, para mí, podría ser caótico tener que ejecutar el código en seco para ver cómo interactúa el código.

Personalmente intentaré evitarlo mientras sea (verdad). La razón es que cada vez que miro hacia atrás en el código escrito previamente, generalmente encuentro que necesito averiguar cuándo se ejecuta / termina más de lo que realmente hace. Por lo tanto, tener que ubicar los "descansos" primero es un poco problemático para mí.

Si hay una necesidad de múltiples condiciones de salida, tiendo a refactorizar la condición que determina la lógica en una función separada para que el bloque de bucles se vea limpio y sea más fácil de entender.


Cuando puedes escribir tu código en la forma

while (condition) { ... }

o

while (!condition) { ... }

sin salidas ( break , continue o goto ) en el cuerpo , se prefiere esa forma, porque alguien puede leer el código y comprender la condición de terminación simplemente mirando el encabezado . Eso es bueno.

Pero muchos bucles no se ajustan a este modelo, y el bucle infinito con salida (s) explícita (s) en el medio es un modelo honorable . (Los bucles con continue son generalmente más difíciles de entender que los bucles con break ). Si desea alguna evidencia o autoridad para citar, no busque más allá del famoso artículo de Don Knuth sobre Programación Estructurada con Declaraciones Goto ; encontrará todos los ejemplos, argumentos y explicaciones que podría desear.

Un punto menor de expresión idiomática: escribir while (true) { ... } marcas como un programador de Pascal o tal vez en estos días un programador de Java. Si escribe en C o C ++, el idioma preferido es

for (;;) { ... }

No hay una buena razón para esto, pero debes escribirlo de esta manera porque esta es la forma en que los programadores C esperan verla.


Depende de lo que trates de hacer, pero en general prefiero poner el condicional por un tiempo.

  • Es más simple, ya que no necesita otra prueba en el código.
  • Es más fácil de leer, ya que no tienes que buscar un descanso dentro del ciclo.
  • Estás reinventando la rueda. El objetivo de while es hacer algo siempre que una prueba sea verdadera. ¿Por qué subvertir eso poniendo la condición de ruptura en otro lugar?

Utilizaría un ciclo while (verdadero) si estuviera escribiendo un daemon u otro proceso que debería ejecutarse hasta que se mate.


El primero es correcto si hay muchas formas de romper el bucle, o si la condición de ruptura no se puede expresar fácilmente en la parte superior del bucle (por ejemplo, el contenido del bucle debe ejecutarse hasta la mitad pero la otra mitad no debe ejecutarse) , en la última iteración).

Pero si puede evitarlo, debería hacerlo, porque la programación debe consistir en escribir cosas muy complejas de la manera más obvia posible, y al mismo tiempo implementar las funciones de manera correcta y eficaz. Es por eso que su amigo es, en el caso general, correcto. La manera en que su amigo escribe constructos en bucle es mucho más obvio (suponiendo que no se obtienen las condiciones descritas en el párrafo anterior).


El problema es que no todos los algoritmos se adhieren al modelo "while (cond) {action}".

El modelo de bucle general es así:

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

Cuando no hay acción_A, puede reemplazarlo por:

loop_prepare while(cond) action_B after_loop_code

Cuando no hay action_B puede reemplazarlo por:

loop_prepare do action_A while(cond) after_loop_code

En el caso general, action_A se ejecutará n veces y action_B se ejecutará (n-1) veces.

Un ejemplo de la vida real es: imprimir todos los elementos de una tabla separados por comas. Queremos todos los n elementos con (n-1) comas.

Siempre puede hacer algunos trucos para seguir el modelo while-loop, pero esto siempre repetirá el código o verificará dos veces la misma condición (para cada bucle) o agregará una nueva variable. Por lo tanto, siempre será menos eficiente y menos legible que el modelo while-true-break loop.

Ejemplo de "truco" (malo): agregar variable y condición

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

Ejemplo de "truco" (malo): repite el código. El código repetido no debe olvidarse al modificar una de las dos secciones.

loop_prepare action_A while(cond): action_B action_A after_loop_code

Nota: en el último ejemplo, el programador puede ofuscar (voluntariamente o no) el código al mezclar el "loop_prepare" con el primer "action_A", y action_B con el segundo action_A. Entonces puede tener la sensación de que no está haciendo esto.


En algún momento necesita bucle infinito, por ejemplo, escuchar en el puerto o esperar la conexión.

Entonces, mientras (verdadero) ... no debería categorizarse como bueno o malo, deja que la situación decida qué usar


Hay una discrepancia entre los dos ejemplos. El primero ejecutará el "hacer algo" al menos una vez cada vez, incluso si la declaración nunca es verdadera. El segundo solo "hará algo" cuando la declaración se evalúe como verdadera.

Creo que lo que estás buscando es un ciclo do-while. Estoy 100% de acuerdo en que while (true) no es una buena idea porque hace que sea difícil mantener este código y la forma en que se está escapando del ciclo es muy goto que se considera una mala práctica.

Tratar:

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

Verifique la documentación de su idioma individual para la sintaxis exacta. Pero mira este código, básicamente hace lo que está en el do, luego verifica la porción while para ver si debería volver a hacerlo.


Javier hizo un comentario interesante sobre mi respuesta anterior (la que cita a Wordsworth):

Creo que while (true) {} es una construcción más ''pura'' que while (condition) {}.

y no pude responder adecuadamente en 300 caracteres (¡lo siento!)

En mi enseñanza y tutoría, he definido informalmente "complejidad" como "¿Cuánto del resto del código necesito tener en mi cabeza para poder entender esta única línea o expresión?" Cuantas más cosas tengo que tener en cuenta, más complejo es el código. Cuanto más me dice explícitamente el código, menos complejo es.

Entonces, con el objetivo de reducir la complejidad, permítanme responder a Javier en términos de integridad y fortaleza en lugar de pureza.

Pienso en este fragmento de código:

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

como expresar dos cosas simultáneamente:

  1. el cuerpo (entero) se repetirá siempre que c1 permanezca verdadero, y
  2. en el punto 1, donde se realiza a1 , se garantiza que c1 se mantenga.

La diferencia es una de perspectiva; el primero de ellos tiene que ver con el comportamiento externo y dinámico de todo el ciclo en general, mientras que el segundo es útil para comprender la garantía interna y estática con la que puedo contar al pensar en a1 en particular. Por supuesto, el efecto neto de a1 puede invalidar c1 , lo que requiere que piense más sobre lo que puedo contar en el punto 2, etc.

Pongamos un ejemplo específico (minúsculo) en su lugar para pensar sobre la condición y la primera acción:

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

El problema "externo" es que el ciclo claramente está haciendo algo dentro de alguna someString que solo puede hacerse mientras el index esté posicionado en la someString . Esto establece la expectativa de que modificaremos el index o someString dentro del cuerpo (en una ubicación y forma desconocida hasta que examine el cuerpo) para que finalmente se produzca la terminación. Eso me da contexto y expectativa para pensar sobre el cuerpo.

El problema "interno" es que tenemos la garantía de que la acción que sigue al punto 1 será legal, así que mientras leo el código en el punto 2 puedo pensar qué se está haciendo con un valor de char que sé que se ha obtenido legalmente. (No podemos siquiera evaluar la condición si someString es una referencia nula, ¡pero también estoy asumiendo que hemos guardado eso en el contexto de este ejemplo!)

Por el contrario, un bucle de la forma:

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

me decepciona en ambos temas. En el nivel externo, me pregunto si esto significa que realmente debería esperar que este ciclo funcione para siempre (por ejemplo, el bucle de envío de eventos principales de un sistema operativo), o si hay algo más que esté pasando. Esto no me proporciona ni un contexto explícito para leer el cuerpo, ni una expectativa de lo que constituye el progreso hacia la terminación (incierta).

En el nivel interno, no tengo absolutamente ninguna garantía explícita sobre cualquier circunstancia que pueda sostenerse en el punto 1. La condición true , que es por supuesto cierta en todas partes, es la declaración más débil posible sobre lo que podemos saber en cualquier punto del programa. ¡Comprender las condiciones previas de una acción es información muy valiosa cuando intentamos pensar en lo que la acción logra!

Entonces, sugiero que while (true) ... expresión idiomática es mucho más incompleta y débil, y por lo tanto más compleja, que while (c1) ... acuerdo con la lógica que he descrito arriba.


La respuesta perfecta del consultor: depende. La mayoría de los casos, lo correcto es utilizar un ciclo while

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

o una "repetición hasta" que se realiza en un lenguaje tipo C con

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

Si cualquiera de estos casos funciona, úselos.

A veces, como en el bucle interno de un servidor, realmente quieres decir que un programa debería continuar hasta que algo externo lo interrumpa. (Considere, por ejemplo, un daemon httpd: no va a detenerse a menos que se bloquee o se interrumpa por un apagado).

LUEGO Y SOLO LUEGO use un tiempo (1):

while(1) { accept connection fork child process }

El caso final es la rara ocasión en la que desea realizar una parte de la función antes de finalizar. En ese caso, use:

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


Lo que tu amigo recomienda es diferente de lo que hiciste. Tu propio código es más parecido a

do{ // do something }while(!<some condition>);

que siempre ejecuta el ciclo al menos una vez, independientemente de la condición.

Pero hay momentos en que los descansos están perfectamente bien, como lo mencionaron otros. En respuesta a la preocupación de su amigo de "olvidar el descanso", a menudo escribo de la siguiente forma:

while(true){ // do something if(<some condition>) break; // continue do something }

Con una buena sangría, el punto de quiebre es claro para el lector que usa el código por primera vez, se ve tan estructural como los códigos que se rompen al principio o al final de un ciclo.


Me gusta for bucles mejor:

for (;;) { ... }

o incluso

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

Pero si la función init() parece al cuerpo del bucle, es mejor con

do { ... } while(condition);


No es tanto la parte verdadera (verdadera) lo que es malo, sino el hecho de que tienes que romper o salir de ella, ese es el problema. break and goto no son métodos realmente aceptables de control de flujo.

Yo tampoco realmente veo el punto. Incluso en algo que recorre toda la duración de un programa, al menos puede tener un booleano llamado Salir o algo que establezca en verdadero para salir del ciclo correctamente en un ciclo como cuando (! Salir) ... No simplemente llamando al break en algún punto arbitrario y saltando,


No, no está mal, ya que es posible que no siempre conozca la condición de salida cuando configura el circuito o puede tener varias condiciones de salida. Sin embargo, requiere más cuidado para evitar un ciclo infinito.


Para citar a ese destacado desarrollador de días pasados, Wordsworth:

...
En verdad la prisión, a la que condenamos
Nosotros mismos, no hay prisión; y por lo tanto, para mí,
En diversos estados de ánimo, era pasatiempo estar atado
Dentro del escaso terreno del Soneto;
Satisfecho si algunas almas (para tales necesidades deben ser)
¿Quién sintió el peso de demasiada libertad?
Debería encontrar un breve consuelo allí, como he encontrado.

Wordsworth aceptó los requisitos estrictos del soneto como un marco liberador, más que como una camisa de fuerza. Sugeriría que el corazón de la "programación estructurada" es renunciar a la libertad de construir gráficos de flujo arbitrariamente complejos a favor de una facilidad liberadora de comprensión.

Estoy de acuerdo en que a veces una salida temprana es la forma más simple de expresar una acción. Sin embargo, mi experiencia ha sido que cuando me obligo a usar las estructuras de control más simples posibles (y realmente pienso en diseñar dentro de esas restricciones), a menudo encuentro que el resultado es un código más simple y claro. El inconveniente con

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

es que es fácil dejar que action0 y action1 conviertan en trozos de código cada vez más grandes, o que agreguen "solo una" secuencia de prueba-pausa-acción, hasta que sea difícil apuntar a una línea específica y responder la pregunta, "¿Qué condiciones? ¿Sé que esperar en este punto? Entonces, sin hacer reglas para otros programadores, trato de evitar el idioma while (true) {...} en mi propio código siempre que sea posible.


Prefiero el enfoque while (!) Porque transmite de forma más clara e inmediata la intención del ciclo.


Se ha hablado mucho sobre la legibilidad aquí y está muy bien construido, pero como con todos los bucles que no están fijos en el tamaño (es decir, mientras y mientras tanto) se corre el riesgo.

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

Dentro de un bucle while, de hecho, estás pidiendo un proceso que se ejecute indefinidamente a menos que ocurra algo, y si ese algo no sucede dentro de un cierto parámetro, obtendrás exactamente lo que querías ... un ciclo sin fin.


Si hay una (y solo una) condición de ruptura no excepcional, es preferible colocar esa condición directamente en la construcción de flujo de control (el tiempo). Ver mientras (verdadero) {...} me hace pensar que no hay una manera simple de enumerar las condiciones de interrupción y me hace pensar "mire cuidadosamente esto y piense cuidadosamente sobre las condiciones de ruptura (lo que está establecido antes) ellos en el ciclo actual y lo que podría haberse establecido en el ciclo anterior) "

En resumen, estoy con su colega en el caso más simple, pero mientras (verdadero) {...} no es raro.


Ya hay una pregunta sustancialmente idéntica en SO en IS WHILE TRUE ... BREAK ... END WHILE un buen diseño? . @Glomek respondió (en una publicación subestimada):

A veces es un muy buen diseño. Ver Programación estructurada con declaraciones Goto por Donald Knuth para algunos ejemplos. Utilizo esta idea básica a menudo para bucles que se ejecutan "n y medio veces", especialmente bucles de lectura / proceso. Sin embargo, generalmente intento tener solo una declaración de interrupción. Esto hace que sea más fácil razonar sobre el estado del programa después de que termine el ciclo.

Algo más tarde, respondí con el comentario relacionado, y lamentablemente subestimado, (en parte porque no noté que Glomek era la primera vez, creo):

Un artículo fascinante es Knuth''s "Programación estructurada con ir a declaraciones" de 1974 (disponible en su libro "Programación alfabetizada", y probablemente también en otros lugares). Discute, entre otras cosas, las formas controladas de salir de los bucles, y (sin usar el término) la instrucción de bucle y medio.

Ada también proporciona construcciones de bucle, que incluyen

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

El código de la pregunta original es similar a este en la intención.

Una diferencia entre el elemento de SO referenciado y este es el ''salto final''; ese es un bucle de disparo único que usa break para salir temprano del bucle. Ha habido preguntas sobre si ese es un buen estilo también, no tengo la referencia cruzada a la mano.


mientras que (verdadero) podría tener sentido si tiene muchas declaraciones y desea detener si falla alguna

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

es mejor que

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


usando loops como

mientras (1) {hace cosas}

es necesario en algunas situaciones. Si realiza alguna programación de sistemas integrados (piense en microcontroladores como PIC, MSP430 y programación DSP), entonces casi la totalidad de su código estará en un ciclo while (1). Al codificar para DSP, a veces solo necesita un tiempo (1) {} y el resto del código es una rutina de servicio de interrupción (ISR).


yo prefiero

while(!<some condition>) { //do something }

pero creo que es más una cuestión de legibilidad, en lugar de la posibilidad de "olvidar el descanso". Creo que olvidar el break es un argumento bastante débil, ya que sería un error y lo encontrarías y lo arreglarías de inmediato.

El argumento que tengo en contra del uso de un break para salir de un bucle sin fin es que esencialmente estás usando la sentencia break como un goto . No estoy religiosamente en contra de usar goto (si el lenguaje lo admite, es un juego limpio), pero trato de reemplazarlo si hay una alternativa más legible.

En el caso de muchos puntos de break reemplazaría con

while( !<some condition> || !<some other condition> || !<something completely different> ) { //do something }

Consolidar todas las condiciones de parada de esta manera hace que sea mucho más fácil ver qué es lo que va a terminar con este ciclo. declaraciones de break podrían ser rociadas, y eso es todo menos legible.