¿Bucles de prueba en la parte superior o inferior? (mientras que vs. do while)

Cuando estaba tomando CS en la universidad (mediados de los 80), una de las ideas que se repetía constantemente era escribir loops que prueban en la parte superior (mientras …) en lugar de en la parte inferior (do … while) del lazo. Estas nociones a menudo se respaldaron con referencias a estudios que demostraron que los bucles que se probaron en la parte superior fueron estadísticamente mucho más probables de ser correctos que sus contrapartes de pruebas de fondo.

Como resultado, casi siempre escribo loops que prueban en la parte superior. No lo hago si introduce complejidad adicional en el código, pero ese caso parece raro. Noté que algunos progtwigdores tienden a escribir casi exclusivamente bucles que se prueban en la parte inferior. Cuando veo construcciones como:

if (condition) { do { ... } while (same condition); } 

o el inverso ( if dentro del while ), me pregunto si realmente lo escribieron de esa manera o si agregaron la statement if cuando se dieron cuenta de que el ciclo no manejaba el caso nulo.

He hecho algunas búsquedas en Google, pero no he podido encontrar ninguna literatura sobre este tema. ¿Cómo pueden ustedes (y chicas) escribir sus loops?

Siempre sigo la regla de que si se ejecuta cero o más veces, prueba al principio, si debe ejecutarse una vez o más, prueba al final. No veo ninguna razón lógica para usar el código que enumeró en su ejemplo. Solo agrega complejidad.

Use while loops cuando quiera probar una condición antes de la primera iteración del ciclo.

Use bucles do-while cuando desee probar una condición después de ejecutar la primera iteración del ciclo.

Por ejemplo, si te encuentras haciendo algo como cualquiera de estos fragmentos:

 func(); while (condition) { func(); } //or: while (true){ func(); if (!condition) break; } 

Deberías reescribirlo como:

 do{ func(); } while(condition); 

La diferencia es que el ciclo do ejecuta “hacer algo” una vez y luego verifica la condición para ver si debe repetir “hacer algo” mientras que el ciclo while verifica la condición antes de hacer cualquier cosa

¿Evitar do / while realmente ayuda a que mi código sea más legible?

No.

Si tiene más sentido usar un ciclo do / while, entonces hazlo. Si necesita ejecutar el cuerpo de un bucle una vez antes de probar la condición, entonces un bucle do / while es probablemente la implementación más directa.

Primero, uno no puede ejecutar en absoluto si la condición es falsa. Otro se ejecutará al menos una vez, luego verifica la conidición.

En aras de la legibilidad, parece sensato probar en la parte superior. El hecho de que sea un ciclo es importante; la persona que lee el código debe conocer las condiciones del bucle antes de tratar de comprender el cuerpo del bucle.

Aquí hay un buen ejemplo del mundo real que encontré recientemente. Supongamos que tiene varias tareas de procesamiento (como procesar elementos en una matriz) y desea dividir el trabajo entre una secuencia por núcleo de CPU presente. ¡Debe haber al menos un núcleo para ejecutar el código actual! Entonces puedes usar un do... while algo como:

 do { get_tasks_for_core(); launch_thread(); } while (cores_remaining()); 

Es casi insignificante, pero podría valer la pena considerar el beneficio de rendimiento: podría escribirse igualmente como un bucle while estándar, pero eso siempre haría una comparación inicial innecesaria que siempre evaluaría true , y en un solo núcleo, el do-while la condición se ramifica de manera más predecible (siempre es falsa, en lugar de alternar verdadera / falsa para un estándar).

El primero prueba la condición antes de realizar, por lo que es posible que su código nunca ingrese el código debajo. El segundo realizará el código dentro antes de probar la condición.

El ciclo while verificará primero “condición”; si es falso, nunca “hará algo”. Pero el do … while loop “hará algo” primero, luego verifica “condición”.

Yaa … es cierto … mientras que se ejecutará al menos una vez. Esa es la única diferencia. Nada más para debatir sobre esto

Sí, al igual que usar for en lugar de while, o foreach en lugar de para mejorar la legibilidad. Dicho esto, algunas circunstancias necesitan hacerlo y estoy de acuerdo en que sería tonto forzar esas situaciones en un ciclo de tiempo.

Es más útil pensar en términos de uso común. La gran mayoría de los bucles while funcionan de forma bastante natural con el while , incluso si pudieran funcionar con ” do...while , básicamente, debes usarlo cuando la diferencia no importa. De este modo usaría do...while para los escenarios raros donde proporciona una mejora notable en la legibilidad.

Los casos de uso son diferentes para los dos. Esta no es una pregunta de “mejores prácticas”.

Si desea que se ejecute un bucle en función de la condición exclusivamente que usar para o mientras

Si desea hacer algo una vez independientemente de la condición y luego continúe haciéndolo, basó la evaluación de la condición. hacer … mientras

Un rato () comprueba la condición antes de cada ejecución del cuerpo del bucle y un do … while () comprueba la condición después de cada ejecución del cuerpo del bucle.

Por lo tanto, ** do … while () ** s siempre ejecutará el cuerpo del bucle al menos una vez.

Funcionalmente, un tiempo () es equivalente a

 startOfLoop: if (!condition) goto endOfLoop; //loop body goes here goto startOfLoop; endOfLoop: 

y a do … while () es equivalente a

 startOfLoop: //loop body //goes here if (condition) goto startOfLoop; 

Tenga en cuenta que la implementación es probablemente más eficiente que esto. Sin embargo, un do … while () implica una comparación menos que un while () por lo que es un poco más rápido. Use un do … while () si:

  • usted sabe que la condición siempre será cierta la primera vez, o
  • desea que el ciclo se ejecute una vez, incluso si la condición es falsa para comenzar.

Aquí está la traducción:

 do { y; } while(x); 

Igual que

 { y; } while(x) { y; } 

Tenga en cuenta que el conjunto extra de llaves es para el caso en que tenga definiciones de variables en y . El scope de estos debe mantenerse local como en el caso do-loop. Entonces, un ciclo do-while solo ejecuta su cuerpo al menos una vez. Aparte de eso, los dos bucles son idénticos. Entonces, si aplicamos esta regla a su código

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

El loop while correspondiente para su do-loop se ve como

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

Sí, ves el tiempo correspondiente para tu ciclo Do diferente de tu tiempo 🙂

Tiendo a preferir bucles do-while, yo mismo. Si la condición siempre será verdadera al comienzo del ciclo, prefiero probarlo al final. En mi opinión, el objective de las condiciones de prueba (aparte de las afirmaciones) es que no se conoce el resultado de la prueba. Si veo un ciclo while con la prueba de condición en la parte superior, mi inclinación es considerar el caso en que el ciclo se ejecute cero veces. Si eso nunca puede suceder, ¿por qué no codificar de una manera que lo demuestre claramente?

En realidad, es para cosas diferentes. En C, puede usar do – while construir para lograr ambos escenarios (se ejecuta al menos una vez y se ejecuta en modo verdadero). Pero PASCAL ha repetido – hasta ahora y para cada escenario, y si no recuerdo mal, ADA tiene otro constructo que le permite salir en el medio, pero por supuesto que no es lo que está preguntando. Mi respuesta a su pregunta: me gusta mi ciclo con las pruebas en la parte superior.

Para cualquiera que no pueda pensar en una razón para tener un ciclo de una o más veces:

 try { someOperation(); } catch (Exception e) { do { if (e instanceof ExceptionIHandleInAWierdWay) { HandleWierdException((ExceptionIHandleInAWierdWay)e); } } while ((e = e.getInnerException())!= null); } 

Lo mismo podría usarse para cualquier tipo de estructura jerárquica.

en el nodo de la clase:

 public Node findSelfOrParentWithText(string text) { Node node = this; do { if(node.containsText(text)) { break; } } while((node = node.getParent()) != null); return node; } 

Ambas convenciones son correctas si sabes cómo escribir el código correctamente 🙂

Por lo general, el uso de la segunda convención ( do {} while () ) pretende evitar tener una statement duplicada fuera del ciclo. Considere el siguiente ejemplo (más simplificado):

 a++; while (a < n) { a++; } 

puede escribirse de manera más concisa

 do { a++; } while (a < n) 

Por supuesto, este ejemplo particular se puede escribir de una manera aún más concisa como (suponiendo la syntax C)

 while (++a < n) {} 

Pero creo que puedes ver el punto aquí.

Como señala Piemasons, la diferencia es si el ciclo se ejecuta una vez antes de hacer la prueba, o si la prueba se realiza primero para que el cuerpo del ciclo nunca se ejecute.

La pregunta clave es cuál tiene sentido para su aplicación.

Para tomar dos ejemplos simples:

  1. Digamos que está recorriendo los elementos de una matriz. Si la matriz no tiene elementos, no desea procesar el número uno de cero. Entonces deberías usar WHILE.

  2. Desea mostrar un mensaje, aceptar una respuesta y, si la respuesta no es válida, volver a preguntar hasta que obtenga una respuesta válida. Entonces siempre quieres preguntar una vez. No puede probar si la respuesta es válida hasta que obtenga una respuesta, por lo que debe pasar por el cuerpo del ciclo una vez antes de poder probar la condición. Deberías usar DO / WHILE.

Yo escribo el mío casi exclusivamente probando en la parte superior. Es menos código, por lo que para mí, al menos, es menos probable que se arruine algo (por ejemplo, copiar y pegar hace dos lugares donde siempre tienes que actualizarlo)

Realmente depende de que haya situaciones en las que desee realizar la prueba en la parte superior, otras cuando desee realizar una prueba en la parte inferior, y otras cuando desee realizar la prueba en el medio.

Sin embargo, el ejemplo dado parece absurdo. Si va a hacer una prueba en la parte superior, no use una statement if y pruebe en la parte inferior, solo use una statement while, para eso está hecha.

Primero debe pensar en la prueba como parte del código de bucle. Si la prueba lógicamente pertenece al inicio del procesamiento del ciclo, entonces es una prueba de nivel superior. Si la prueba lógicamente pertenece al final del ciclo (es decir, decide si el ciclo debe seguir ejecutándose), entonces es probable que sea una prueba de nivel inferior.

Tendrá que hacer algo elegante si la prueba lógicamente pertenece a ellos en el medio. 🙂

Supongo que algunas personas prueban en la parte inferior porque podrías salvar uno o unos pocos ciclos de máquina haciendo eso hace 30 años.

Para escribir el código que es correcto, uno básicamente necesita realizar una prueba mental, tal vez informal, de corrección.

Para demostrar que un bucle es correcto, la forma estándar es elegir un bucle invariante y una prueba de inducción. Pero omita las palabras complicadas: lo que hace, informalmente, es descubrir algo que es cierto para cada iteración del ciclo, y que cuando termina el ciclo, lo que quería lograr ahora es verdadero. El bucle invariante es falso al final, para que el bucle termine.

Si las condiciones de bucle se asignan con bastante facilidad al invariante, y el invariante está en la parte superior del bucle, y uno deduce que el invariante es verdadero en la siguiente iteración del bucle trabajando a través del código del bucle, entonces es fácil para descubrir que el ciclo es correcto.

Sin embargo, si el invariante está en la parte inferior del ciclo, entonces, a menos que tengas una afirmación justo antes del ciclo (una buena práctica), entonces se vuelve más difícil porque esencialmente debes inferir qué debería ser ese invariante, y que cualquier código que se ejecutó antes de que el bucle haga que el bucle invariante sea verdadero (dado que no existe precondición de bucle, el código se ejecutará en el bucle). Simplemente se vuelve más difícil de demostrar que es correcto, incluso si es una prueba informal en la cabeza.

Esto no es realmente una respuesta, sino una reiteración de algo que uno de mis profesores dijo y me interesó en ese momento.

Los dos tipos de bucle while..do y do … while son en realidad instancias de un tercer bucle más genérico, que tiene la prueba en algún lugar en el medio.

 begin loop  loop condition  end loop 

El bloque de código A se ejecuta al menos una vez y B se ejecuta cero o más veces, pero no se ejecuta en la última iteración (fallida). un ciclo while es cuando el bloque de código a está vacío y un do … mientras que el bloque de código b está vacío. Pero si está escribiendo un comstackdor, podría interesarle generalizar ambos casos en un ciclo como este.

 while( someConditionMayBeFalse ){ // this will never run... } // then the alternative do{ // this will run once even if the condition is false while( someConditionMayBeFalse ); 

La diferencia es obvia y le permite tener código ejecutado y luego evaluar el resultado para ver si tiene que “hacerlo de nuevo” y el otro método de while le permite tener un bloque de script ignorado si el condicional no se cumple.

En una clase típica de Estructuras discretas en informática, es una prueba fácil de que hay un mapeo de equivalencia entre los dos.

Estilísticamente, prefiero while (easy-expr) {} cuando easy-expr se conoce desde el principio y está listo para funcionar, y el bucle no tiene muchas sobrecargas / inicializaciones repetidas. Prefiero do {} while (algo-menos-fácil-expr); cuando hay más sobrecarga repetida y la condición puede no ser tan simple de configurar con anticipación. Si escribo un bucle infinito, siempre uso while (true) {}. No puedo explicar por qué, pero simplemente no me gusta escribir para (;;) {}.

Diría que es una mala práctica escribir if..do..while loops, por la simple razón de que esto aumenta el tamaño del código y causa duplicaciones de código. Las duplicaciones de código son propensas a errores y deben evitarse, ya que cualquier cambio en una parte debe realizarse en el duplicado también, lo que no siempre es el caso. Además, un código más grande significa un tiempo más difícil en la memoria caché de la CPU. Finalmente, maneja casos nulos y resuelve dolores de cabeza.

Solo cuando el primer bucle sea fundamentalmente diferente, debe usarse uno, mientras que, por ejemplo, si el código que le hace pasar la condición de bucle (como la inicialización) se realiza en el bucle. De lo contrario, si está seguro de que ese bucle nunca caerá en la primera iteración, entonces sí, haga … mientras sea apropiado.

A partir de mi conocimiento limitado de la generación de código, creo que puede ser una buena idea escribir loops de prueba desde abajo, ya que permiten que el comstackdor optimice mejor los bucles. Para los bucles de prueba del fondo, se garantiza que el bucle se ejecute al menos una vez. Esto significa que el código invariante de bucle “domina” el nodo de salida. Y así se puede mover con seguridad justo antes de que comience el ciclo.