Dentro de un bucle for, ¿debería mover la condición de ruptura al campo de condición si es posible? [cerrado]

48

A veces necesito bucles que necesitan un descanso como este:

for(int i=0;i<array.length;i++){
    //some other code
    if(condition){
        break;
    }
}

Me siento incómodo con la escritura

if(condition){
    break;
}

porque consume 3 líneas de código. Y encontré que el bucle se puede reescribir como:

                                   ↓
for(int i=0;i<array.length && !condition;i++){
    //some other code
}

Mi pregunta es, ¿es una buena práctica mover la condición al campo de condición para reducir las líneas de códigos si es posible?

    
pregunta mmmaaa 27.02.2018 - 10:27

16 respuestas

154

Esos dos ejemplos que dio no son funcionalmente equivalentes. En el original, la prueba de condición se realiza después de la sección "algún otro código", mientras que en la versión modificada, se realiza primero, al comienzo del cuerpo del bucle.

El código nunca debe reescribirse con el único propósito de reducir el número de líneas. Obviamente, es una buena ventaja cuando funciona de esa manera, pero nunca debe hacerse a expensas de la legibilidad o corrección.

    
respondido por el Pete 27.02.2018 - 10:39
51

No compro el argumento de que "consume 3 líneas de código" y, por lo tanto, es malo. Después de todo, puedes escribirlo como:

if (condition) break;

y solo consumir una línea.

Si el if aparece en la mitad del ciclo, por supuesto, tiene que existir como una prueba separada. Sin embargo, si esta prueba aparece al final del bloque, ha creado un bucle que tiene una prueba continua en ambos extremos del bucle, lo que posiblemente se sume a la complejidad del código. Sin embargo, tenga en cuenta que, a veces, la prueba if (condition) solo puede tener sentido después de que se haya ejecutado el bloqueo.

Pero suponiendo que no sea así, adoptando el otro enfoque:

for(int i=0;i<array.length && !condition;i++){
    //some other code
}

las condiciones de salida se mantienen juntas, lo que puede simplificar las cosas (especialmente si " algún otro código " es largo).

No hay una respuesta correcta aquí, por supuesto. Por lo tanto, tenga en cuenta las expresiones idiomáticas del idioma, cuáles son las convenciones de codificación de su equipo, etc. Pero en general, adoptaría el enfoque de prueba única cuando tenga sentido funcional hacerlo.

    
respondido por el David Arno 27.02.2018 - 10:42
48

Este tipo de pregunta ha provocado un debate casi tanto como la programación como lo ha sido. Para meter mi sombrero en el anillo, me gustaría ir a por la versión con la condición break y he aquí por qué (para este caso específico ):

El bucle for está justo ahí para especificar los valores de iteración en el bucle. La codificación de la condición de ruptura dentro de ese solo confunde la lógica de la OMI.

Tener un break por separado deja en claro que durante el procesamiento normal, los valores son los que se especifican en el bucle for y el procesamiento debe continuar excepto donde entra la condición.

Como fondo, comencé a codificar un break , luego puse la condición en el bucle for durante años (bajo la dirección de un programador excepcional) y luego volví al estilo break porque se sentía mucho más limpio (y era el estilo predominante en los diversos tomos de código que estaba consumiendo).

Es otra de esas guerras santas al final del día. Algunos dicen que nunca debes romper un ciclo artificialmente, de lo contrario no es un circuito real. Haga lo que crea que sea legible (tanto para usted como para los demás). Si realmente lo tuyo es reducir las pulsaciones de teclas, haz un código de golf los fines de semana: cerveza opcional.

    
respondido por el Robbie Dee 27.02.2018 - 12:23
43

for loops son para la iteración sobre algo 1 - no son solo lol-vamos-tirar-algo-al-azar-cosas-del-cuerpo -y-put-them-in-the-loop-header - las tres expresiones tienen significados muy específicos:

  • El primero es para inicializar iterador . Puede ser un índice, un puntero, un objeto de iteración o lo que sea, siempre que se use para iteración .
  • El segundo es para verificar si llegamos al final.
  • El tercero es para avanzar el iterador.

La idea es separar el manejo de iteraciones ( cómo para iterar) de la lógica dentro del bucle ( qué hacer en cada iteración). Por lo general, muchos idiomas tienen un bucle para cada uno que lo libera de los detalles de la iteración, pero incluso si su idioma no tiene esa construcción, o si no puede usarse en su escenario, debe limitar el encabezado del bucle. para el manejo de iteraciones.

Entonces, debe preguntarse: ¿su condición es sobre el manejo de la iteración o sobre la lógica dentro del bucle? Lo más probable es que se trate de la lógica dentro del bucle, por lo que debe verificarse dentro del bucle en lugar de en el encabezado.

1 A diferencia de otros bucles, están "esperando" que algo suceda. Un bucle for / foreach debe tener el concepto de una "lista" de elementos para iterar, incluso si esa lista es perezosa o infinita o abstracta.

    
respondido por el Idan Arye 27.02.2018 - 12:02
8

Recomiendo usar la versión con if (condition) . Es más legible y más fácil de depurar. Escribir 3 líneas adicionales de código no le romperá los dedos, pero facilitará que la próxima persona entienda su código.

    
respondido por el Aleksander 27.02.2018 - 10:33
8

Si está iterando sobre una colección donde se deben omitir ciertos elementos, le recomiendo una nueva opción:

  • Filtra tu colección antes de iterar

Tanto Java como C # hacen que esto sea relativamente trivial. No me sorprendería si C ++ tuviera una forma de hacer que un iterador elegante omitiera ciertos elementos que no se aplican. Pero esta es solo una opción si su idioma de preferencia lo admite, y la razón por la que está utilizando break es porque existen condiciones sobre si procesa un elemento o no.

De lo contrario, hay una muy buena razón para que su condición sea parte del bucle for, suponiendo que su evaluación inicial sea correcta.

  • Es más fácil ver cuándo un bucle termina antes

He trabajado en el código donde tu bucle for toma unos pocos cientos de líneas con varios condicionales y algunas matemáticas complejas que ocurren allí. Los problemas que encuentras con esto son:

  • Los comandos de break enterrados en la lógica son difíciles de encontrar y, a veces, sorprendentes
  • La depuración requiere pasar por cada iteración del bucle para comprender realmente lo que está pasando
  • Gran parte del código no tiene refactorizaciones fácilmente reversibles disponibles o pruebas unitarias para ayudar con la equivalencia funcional después de una reescritura

En esa situación, recomiendo las siguientes pautas en orden de preferencia:

  • Filtre la colección para eliminar la necesidad de un break si es posible
  • Haga que la parte condicional de las condiciones del bucle for
  • Coloque los condicionales con el break en la parte superior del bucle si es posible
  • Agregue un comentario de varias líneas para llamar la atención sobre el descanso y por qué está allí

Estas directrices siempre están sujetas a la corrección de su código. Elija la opción que mejor represente la intención y mejore la claridad de su código.

    
respondido por el Berin Loritsch 27.02.2018 - 14:43
8

Recomiendo encarecidamente que adopte el enfoque de menos sorpresa a menos que obtenga un beneficio significativo si no lo hace.

Las personas no leen todas las letras cuando leen una palabra, y no leen cada palabra cuando leen un libro; si son expertos en la lectura, miran los contornos de las palabras y las oraciones y dejan que su cerebro se llene en el resto.

Así que es probable que el desarrollador ocasional asuma que esto es solo un estándar para el bucle y que ni siquiera lo vea:

for(int i=0;i<array.length&&!condition;i++)

Si quieres usar ese estilo independientemente, te recomiendo cambiar las partes for(int i=0;i< y ;i++) que le dicen al cerebro del lector que esto es un estándar para el bucle.

Otra razón para ir con if - break es que no siempre puedes usar tu enfoque con la condición for . Debe usar if - break cuando la condición de interrupción es demasiado compleja para ocultarse dentro de una declaración for , o depende de variables a las que solo se puede acceder dentro del bucle for .

for(int i=0;i<array.length&&!((someWidth.X==17 && y < someWidth.x/2) || (y == someWidth.x/2 && someWidth.x == 18);i++)

Entonces, si decides ir con if - break , estás cubierto. Pero si decides ir con las condiciones for , tienes que usar una combinación de las condiciones if - break y for . Para ser coherente, deberá mover las condiciones de un lado a otro entre las condiciones for y las condiciones if - break cada vez que cambien las condiciones.

    
respondido por el Peter 28.02.2018 - 02:09
4

Su transformación está asumiendo que todo lo que condition se evalúa como true al ingresar al ciclo. No podemos comentar sobre la corrección de eso en este caso porque no está definido.

Al tener éxito en "reducir las líneas de código" aquí, puede continuar mirando

for(int i=0;i<array.length;i++){
    // some other code
    if(condition){
        break;
    }
    // further code
}

y aplique la misma transformación, llegando a

for(int i=0;i<array.length && !condition;i++){
    // some other code
    // further code
}

Que es una transformación no válida, ya que ahora incondicionalmente haces further code donde anteriormente no lo hiciste.

Un esquema de transformación mucho más seguro es extraer some other code y evaluar condition a una función separada

bool evaluate_condition(int i) // This is an horrible name, but I have no context to improve it
{
     // some other code
     return condition;
}

for(int i=0;i<array.length && !evaluate_condition(i);i++){
    // further code
}
    
respondido por el Caleth 27.02.2018 - 12:50
4

TL; DR Haz lo que se lea mejor en tu circunstancia particular

Tomemos este ejemplo:

for ( int i = 1; i < array.length; i++ ) 
{
    if(something) break;
    // perform operations
}

En este caso, no queremos que se ejecute ninguno de los códigos en el bucle for si something es verdadero, por lo que mover la prueba de something al campo de condición es razonable.

for ( int i = 1; i < array.length && !something; i++ )

Nuevamente, dependiendo de si something se puede establecer en true antes del bucle, pero no dentro de él, esto podría ofrecer más claridad:

if(!something)
{
    for ( int i = 1; i < array.length; i++ )
    {...}
}

Ahora imagina esto:

for ( int i = 1; i < array.length; i++ ) 
{
    // perform operations
    if(something) break;
    // perform more operations
}

Estamos enviando un mensaje muy claro allí. Si something se convierte en verdadero al procesar la matriz, abandone toda la operación en este punto. Para mover el cheque al campo de condición que necesita hacer:

for ( int i = 1; i < array.length && !something; i++ ) 
{
    // perform operations
    if(!something)
    {
        // perform more operations
    }
}

Podría decirse que el "mensaje" se ha vuelto confuso, y estamos revisando la condición de falla en dos lugares. ¿Qué pasa si la condición de falla cambia y olvidamos uno de esos lugares?

Por supuesto, hay muchas otras formas diferentes que podrían ser un bucle for y una condición, todas con sus propios matices.

Escriba el código para que se lea bien (esto es obviamente algo subjetivo) y conciso. Fuera de un conjunto draconiano de pautas de codificación todas las "reglas" tienen algunas excepciones. Escriba lo que crea que exprese mejor la toma de decisiones y minimice las posibilidades de errores futuros.

    
respondido por el Grimm The Opiner 28.02.2018 - 15:21
2

Aunque puede ser atractivo porque ve todas las condiciones en el encabezado del bucle y break puede ser confuso dentro de bloques grandes, un bucle for es un patrón en el que la mayoría de los programadores esperan un bucle en algún rango.

Especialmente porque lo hace, agregar una condición de interrupción adicional puede causar confusión y es más difícil de ver si es funcionalmente equivalente.

A menudo, una buena alternativa es usar un bucle while o do {} while que verifique ambas condiciones, asumiendo que la matriz tiene al menos un elemento.

int i=0
do {
    // some other code
    i++; 
} while(i < array.length && condition);

Al usar un bucle que solo está verificando una condición y no está ejecutando el código del encabezado del bucle, queda muy claro cuándo se verifica la condición y cuál es la condición real y qué es el código con efectos secundarios.

    
respondido por el allo 27.02.2018 - 14:59
1

Esto es malo, ya que el código debe ser leído por humanos, los bucles no idiomáticos for a menudo son confusos para leer, lo que significa que es más probable que los errores se oculten aquí, pero el código original pudo haber sido posible para mejorar mientras se mantiene corto y legible.

Si lo que desea es encontrar un valor en una matriz (el ejemplo de código proporcionado es un tanto genérico, pero también puede ser este patrón), puede intentar explícitamente usar una función proporcionada por un lenguaje de programación específicamente para ese proposito Por ejemplo (pseudo-código, como no se especificó un lenguaje de programación, las soluciones varían según un idioma en particular).

array.find(item -> item.valid)

Esto debería acortar notablemente la solución al tiempo que la simplifica, ya que su código dice específicamente lo que necesita.

    
respondido por el Konrad Borowski 28.02.2018 - 10:45
1

En mi opinión, hay pocas razones que digan que no debería.

  1. Esto reduce la legibilidad.
  2. La salida puede no ser la misma. Sin embargo, se puede hacer con un bucle do...while (no while ) ya que la condición se verifica después de la ejecución de algún código.

Pero además de eso, considera esto,

for(int i=0;i<array.length;i++){

    // Some other code
    if(condition1){
        break;
    }

    // Some more code
    if(condition1){
        break;
    }

    // Some even more code
}

Ahora, ¿realmente puedes lograrlo agregando estas condiciones a esa condición for ?

    
respondido por el Harsh 28.02.2018 - 05:53
0

Creo que eliminar break puede ser una buena idea, pero no para guardar líneas de código.

La ventaja de escribirlo sin break es que la condición posterior del bucle es obvia y explícita. Cuando finaliza el bucle y ejecuta la siguiente línea del programa, su condición de bucle i < array.length && !condition debe haber sido falsa. Por lo tanto, o i fue mayor o igual que la longitud de tu matriz, o condition es verdadero.

En la versión con break , hay un gotcha oculto. La condición del bucle dice que el bucle termina cuando se ejecuta algún otro código para cada índice de matriz válido, pero de hecho hay una declaración break que podría terminar el haga un ciclo antes de eso, y no lo verá a menos que revise el código del bucle con mucho cuidado. Y los analizadores estáticos automatizados, incluidos los optimizadores de compilación, también podrían tener problemas para deducir cuáles son las condiciones posteriores del bucle.

Esta fue la razón original por la que Edgar Dijkstra escribió "Goto Considered Harmful". No fue una cuestión de estilo: salir de un bucle hace que sea difícil razonar formalmente sobre el estado del programa. He escrito muchas declaraciones de break; en mi vida, y una vez como adulto, incluso escribí un goto (para romper los múltiples niveles de un bucle interno de rendimiento crítico y luego continuar con otra rama del árbol de búsqueda). ). Sin embargo, es mucho más probable que escriba una sentencia condicional return desde un bucle (que nunca ejecuta el código después del bucle y, por lo tanto, no puede eliminar sus condiciones posteriores) que un break .

Postscript

De hecho, refactorizar el código para dar el mismo comportamiento podría necesitar más líneas de código. Su refactorización podría ser válida para algún otro código en particular, o si podemos probar que condition comenzará en falso. Pero su ejemplo es equivalente en el caso general a:

if ( array.length > 0 ) {
  // Some other code.
}
for ( int i = 1; i < array.length && !condition; i++ ) {
  // Some other code.
}

Si algún otro código no es de una sola línea, entonces puede moverlo a una función para que no se repita, y luego casi ha reformulado su bucle en una cola -función recursiva.

Reconozco que este no fue el punto principal de tu pregunta, y lo mantuviste simple.

    
respondido por el Davislor 28.02.2018 - 01:21
0

Sí, pero su sintaxis es poco convencional y, por lo tanto, mala (tm)

Esperemos que tu idioma admita algo como

while(condition) //condition being a condition which if true you want to continue looping
{
    do thing; //your conditionally executed code goes here
    i++; //you can use a counter if you want to reference array items in order of index
}
    
respondido por el Ewan 27.02.2018 - 10:52
0

Si le preocupa que una declaración for demasiado compleja sea difícil de leer, definitivamente es una preocupación válida. Perder espacio vertical también es un problema (aunque menos crítico).

(Personalmente no me gusta la regla de que las declaraciones if siempre deben tener llaves, lo que en gran parte es la causa del espacio vertical desperdiciado aquí. Tenga en cuenta que el problema es perder espacio vertical. Usar el espacio vertical con líneas significativas no debería ser un problema.)

Una opción es dividirla en varias líneas. Definitivamente, esto es lo que debe hacer si usa declaraciones for muy complejas, con múltiples variables y condiciones. (Esto es para mí un mejor uso del espacio vertical, dando tantas líneas como sea posible algo significativo).

for (
   int i = 0, j = 0;
   i < array.length && !condition;
   i++, j++
) {
   // stuff
}

Un mejor enfoque podría ser tratar de usar una forma más declarativa y menos imperativa. Esto variará según el idioma, o puede que necesite escribir sus propias funciones para habilitar este tipo de cosas. También depende de lo que condition sea en realidad. Es de suponer que debe poder cambiar de alguna manera, dependiendo de lo que esté en la matriz.

array
.takeWhile(condition)  // condition can be item => bool or (item, index) => bool
.forEach(doSomething); // doSomething can be item => void or (item, index) => void
    
respondido por el Dave Cousineau 02.03.2018 - 18:54
0

Una cosa que se olvidó: cuando salgo de un bucle (no hago todas las iteraciones posibles, no importa cómo esté implementado), generalmente es el caso que no solo quiero salir del bucle, también quiero saber por qué sucedió esto Por ejemplo, si busco un artículo X, no solo me separo del bucle, también quiero saber si se encontró X y dónde está.

En esa situación, tener una condición fuera del bucle es bastante natural. Así que empiezo con

bool found = false;
size_t foundIndex;

y en el bucle escribiré

if (condition) {
    found = true;
    foundIndex = i;
    break;
}

con el bucle for

for (i = 0; i < something && ! found; ++i)

Ahora, comprobar la condición en el bucle es bastante natural. Si hay una declaración de "ruptura" depende de si hay un procesamiento adicional en el bucle que desee evitar. Si se necesita algo de procesamiento y otro no, puede tener algo como

if (! found) { ... }

en algún lugar de tu bucle.

    
respondido por el gnasher729 03.03.2018 - 15:42

Lea otras preguntas en las etiquetas