¿Es una mala práctica usar break en un bucle for? [cerrado]


123

¿Es una mala práctica usar una breakdeclaración dentro de un forbucle ?

Digamos, estoy buscando un valor en una matriz. Compare dentro de un bucle for y cuando se encuentre el valor, break;salga del bucle for.

¿Es esta una mala práctica? He visto la alternativa utilizada: definir una variable vFoundy establecerla en verdadero cuando se encuentra el valor y verificar vFoundla forcondición de la declaración. ¿Pero es necesario crear una nueva variable solo para este propósito?

Estoy preguntando en el contexto de un C o C ++ normal para el bucle.

PD: Las pautas de codificación MISRA aconsejan no usar break.


28
No coloque breaken la misma liga que goto:)
BoltClock

2
Er, no los he colocado en la misma liga que ir a ... Las reglas de MISRA especifican descanso, si no recuerdo mal.
kiki el

1
La única razón por la que se me ocurre no usar break dentro de un bucle es cuando todavía tienes que procesar más elementos que podrían cambiar el resultado de lo que estás haciendo ...
Younes

44
Las reglas de MISRA se han relajado: misra.org.uk/forum/viewtopic.php?f=75&t=298
Johan Kotlinski el

Respuestas:


122

Muchas respuestas aquí, pero aún no he visto esto mencionado:

La mayoría de los "peligros" asociados con el uso breako continueen un bucle for se niegan si escribe bucles ordenados y fáciles de leer. Si el cuerpo de su ciclo abarca varias longitudes de pantalla y tiene múltiples subbloques anidados, sí, podría olvidar fácilmente que parte del código no se ejecutará después del corte. Sin embargo, si el ciclo es corto y va al grano, el propósito de la declaración de ruptura debe ser obvio.

Si un bucle se está volviendo demasiado grande, use una o más llamadas de función bien nombradas dentro del bucle. La única razón real para evitar hacerlo es para procesar cuellos de botella.


11
Muy cierto. Por supuesto, si el bucle es tan grande y complejo que es difícil ver lo que sucede dentro de él, es un problema si tiene un descanso o no.
Jay

1
Otro problema es que romper y continuar causa problemas con la refactorización. Esto podría ser una señal de que esta es una mala práctica. La intención del código también es más clara cuando se usan declaraciones if.
Ed Greaves

Esas "llamadas de función bien nombradas" pueden ser funciones lambda definidas localmente en lugar de funciones privadas definidas externamente que no se utilizarán en otros lugares.
DavidRR

135

No, el descanso es la solución correcta.

Agregar una variable booleana hace que el código sea más difícil de leer y agrega una fuente potencial de errores.


2
convenido. Especialmente si está buscando salir del ciclo bajo más de una sola condición. El booleano para dejar un bucle puede ser realmente confuso entonces.
Rontólogo

2
Es por eso gotoque solía salir de los bucles anidados de Nivel 2+, porque no haybreak(level)
Петър Петров

3
Prefiero poner el código de bucle en una función y simplemente regresar. Esto evita el goto y divide el código en fragmentos más pequeños.
Dave Branton

53

Puede encontrar todo tipo de código profesional con declaraciones 'break' en ellos. Tiene sentido usar esto cuando sea necesario. En su caso, esta opción es mejor que crear una variable separada solo con el propósito de salir del ciclo.


47

Usar breaktan bien como continueen forbucle está perfectamente bien.

Simplifica el código y mejora su legibilidad.


Sí. Por un tiempo no me gustó la idea y la codifiqué, pero no tardé mucho en darme cuenta de que la "solución" a menudo era una pesadilla de mantenimiento. Bueno, no es una pesadilla, pero es más propenso a errores.
MetalMikester

24

Lejos de las malas prácticas, Python (¿y otros lenguajes?) Extendió la estructura del forbucle para que parte de ella solo se ejecute si el bucle no lo hace break .

for n in range(5):
    for m in range(3):
        if m >= n:
            print('stop!')
            break
        print(m, end=' ')
    else:
        print('finished.')

Salida:

stop!
0 stop!
0 1 stop!
0 1 2 finished.
0 1 2 finished.

Código equivalente sin breaky que útil else:

for n in range(5):
    aborted = False
    for m in range(3):
        if not aborted:
            if m >= n:
                print('stop!')
                aborted = True
            else:            
                print(m, end=' ')
    if not aborted:
        print('finished.')

2
Oooh, me gusta eso y a veces lo he deseado en C. Sintaxis agradable también, que podría adaptarse a un futuro lenguaje tipo C sin ambigüedad.
supercat

"Lenguaje tipo C": P
nirvanaswap

15

No hay nada intrínsecamente incorrecto con el uso de una instrucción break, pero los bucles anidados pueden ser confusos. Para mejorar la legibilidad, muchos lenguajes ( al menos Java ) admiten romper las etiquetas, lo que mejorará en gran medida la legibilidad.

int[] iArray = new int[]{0,1,2,3,4,5,6,7,8,9};
int[] jArray = new int[]{0,1,2,3,4,5,6,7,8,9};

// label for i loop
iLoop: for (int i = 0; i < iArray.length; i++) {

    // label for j loop
    jLoop: for (int j = 0; j < jArray.length; j++) {

        if(iArray[i] < jArray[j]){
            // break i and j loops
            break iLoop;
        } else if (iArray[i] > jArray[j]){  
            // breaks only j loop
            break jLoop;
        } else {
            // unclear which loop is ending
            // (breaks only the j loop)
            break;
        }
    }
}

Diré que las declaraciones de ruptura (y retorno) a menudo aumentan la complejidad ciclomática, lo que dificulta probar que el código está haciendo lo correcto en todos los casos.

Si está considerando usar un descanso mientras itera sobre una secuencia para algún elemento en particular, es posible que desee reconsiderar la estructura de datos utilizada para mantener sus datos. Usar algo como un Conjunto o un Mapa puede proporcionar mejores resultados.


44
+1 para complejidad ciclomática.
sp00m

Los programadores .NET pueden querer explorar el uso de LINQ como una alternativa potencial a los forbucles complejos .
DavidRR

14

break es una declaración completamente aceptable para usar (por lo tanto , continue , por cierto). Se trata de la legibilidad del código, siempre que no tenga bucles demasiado complicados y tal, está bien.

No es que fueran la misma liga que goto . :)


Lo he visto argumentar que una declaración de ruptura es efectivamente un goto .
glenatron

3
El problema con goto es que puedes apuntarlo a cualquier parte. Ambos rompen y continúan preservando la modularidad del código. Ese argumento está en el mismo nivel que tener un único punto de salida para cada función.
Zachary Yates

1
He oído argumentar que tener múltiples puntos de salida para una función es efectivamente un goto . ; D
glenatron

2
@glenatron El problema con goto no es la declaración de goto, es la introducción de la etiqueta a la que salta el goto, ese es el problema. Cuando lee una declaración goto, no hay incertidumbre sobre el flujo de control. Cuando lee una etiqueta, hay mucha incertidumbre sobre el flujo de control (¿dónde están todos los gotos que transfieren el control a esta etiqueta?). Una declaración de ruptura no introduce una etiqueta, por lo que no introduce ninguna complicación nueva.
Stephen C. Steel el

1
El "descanso" es un goto especializado que solo puede dejar bloques. Los problemas históricos con "goto" fueron (1) podría ser, y a menudo fue, utilizado para construir bloques de bucles superpuestos de una manera que no es posible con estructuras de control adecuadas; (2) una forma común de hacer una construcción "si-entonces" que generalmente era "falsa" era hacer que el caso verdadero se bifurcara a un lugar no relacionado en el código, y luego bifurcar de nuevo. Esto costaría dos ramas en el caso raro y cero en el caso común, pero hizo que el código se viera horrible.
supercat

14

Regla general: si seguir una regla requiere que hagas algo más incómodo y difícil de leer que romper la regla, entonces rompe la regla.

En el caso de bucle hasta que encuentre algo, se encuentra con el problema de distinguir entre encontrado y no encontrado cuando sale. Es decir:

for (int x=0;x<fooCount;++x)
{
  Foo foo=getFooSomehow(x);
  if (foo.bar==42)
    break;
}
// So when we get here, did we find one, or did we fall out the bottom?

Entonces, está bien, puede establecer un indicador o inicializar un valor "encontrado" como nulo. Pero

Es por eso que en general prefiero insertar mis búsquedas en funciones:

Foo findFoo(int wantBar)
{
  for (int x=0;x<fooCount;++x)
  {
    Foo foo=getFooSomehow(x);
    if (foo.bar==wantBar)
      return foo;
  }
  // Not found
  return null;
}

Esto también ayuda a ordenar el código. En la línea principal, "buscar" se convierte en una sola declaración, y cuando las condiciones son complejas, solo se escriben una vez.


1
Exactamente lo que quería decir. A menudo, cuando me encuentro usando break, trato de encontrar alguna forma de refactorizar el código en una función, para poder usarlo returnen su lugar.
Rene Saarsoo

Estoy 100% de acuerdo con usted, no hay nada intrínsecamente malo en usar break. Sin embargo, generalmente indica que el código en el que se encuentra puede necesitar extraerse en una función donde el descanso se reemplazaría con el retorno. Debe considerarse un "olor a código" en lugar de un error absoluto.
vascowhite

Su respuesta podría incitar a algunos a explorar la conveniencia de usar múltiples declaraciones de devolución .
DavidRR

13

Depende del idioma. Si bien puede verificar una variable booleana aquí:

for (int i = 0; i < 100 && stayInLoop; i++) { ... }

no es posible hacerlo cuando se desplaza sobre una matriz:

for element in bigList: ...

De todos modos, breakharía que ambos códigos fueran más legibles.


7

En su ejemplo, no sabe el número de iteraciones para el bucle for . ¿Por qué no usar mientras bucle lugar, lo que permite que el número de iteraciones para ser indeterminada al principio?

Es por lo tanto no es necesario utilizar statemement descanso en general, como el bucle puede ser mejor establece como un tiempo de bucle.


1
Un bucle "for" a menudo es bueno si hay un número máximo conocido de iteraciones. Dicho esto, un bucle while (1) a veces es mejor en un escenario en el que uno está buscando un elemento y planea crearlo si no existe. En ese caso, si el código encuentra el elemento, realiza una ruptura directa, y si llega al final de la matriz, crea un nuevo elemento y luego realiza una ruptura.
supercat

2
En el ejemplo dado: al buscar una clave en una matriz, el bucle for es la construcción idiomática adecuada. No utiliza un ciclo while para caminar a través de una matriz. Usas un bucle for. (o un ciclo para cada uno si está disponible). Lo haces como cortesía a otros programadores, ya que reconocen la construcción "for (int i = 0; i <ra.length; i ++) {}" inmediatamente como "Recorrer la matriz" Agregar un salto, a esta construcción es simplemente una declaración de "Salga temprano si puede".
Chris Cudmore

7

Estoy de acuerdo con otros que recomiendan usar break. La pregunta consecuente obvia es ¿por qué alguien recomendaría lo contrario? Bueno ... cuando usa break, omite el resto del código en el bloque y las iteraciones restantes. A veces esto causa errores, por ejemplo:

  • un recurso adquirido en la parte superior del bloque puede liberarse en la parte inferior (esto es cierto incluso para los bloques dentro de los forbucles), pero ese paso de liberación puede omitirse accidentalmente cuando una breakdeclaración provoca una salida "prematura" (en "moderno" C ++, "RAII" se utiliza para manejar esto de una manera confiable y segura: básicamente, los destructores de objetos liberan recursos de manera confiable sin importar cómo se salga de un alcance)

  • alguien puede cambiar la prueba condicional en la fordeclaración sin darse cuenta de que hay otras condiciones de salida deslocalizadas

  • La respuesta de ndim observa que algunas personas pueden evitar que breaks mantenga un tiempo de ejecución de bucle relativamente constante, pero estaba comparando breakcon el uso de una variable de control booleana de salida temprana donde eso no se cumple

De vez en cuando, las personas que observan tales errores se dan cuenta de que pueden ser prevenidos / mitigados por esta regla de "sin interrupciones" ... de hecho, hay una estrategia relacionada con la programación "más segura" llamada "programación estructurada", donde se supone que cada función tiene un único punto de entrada y salida también (es decir, sin ir a, sin retorno anticipado). Puede eliminar algunos errores, pero sin duda introduce otros. ¿Por qué lo hacen?

  • tienen un marco de desarrollo que fomenta un estilo particular de programación / código, y tienen evidencia estadística de que esto produce un beneficio neto en ese marco limitado, o
  • han sido influenciados por pautas de programación o experiencia dentro de dicho marco, o
  • son solo idiotas dictatoriales, o
  • cualquiera de las anteriores + inercia histórica (relevante porque las justificaciones son más aplicables a C que a C ++ moderno).

Bueno, sí, pero de nuevo he visto errores en el código de personas que trataron de evitarlo religiosamente break. Lo evitaron, pero no pudieron pensar en las condiciones que reemplazaron el uso del descanso.
Tomáš Zato - Restablece a Monica el

5

Es perfectamente válido para usar break, como han señalado otros, no está en la misma liga que goto.

Aunque es posible que desee utilizar la vFoundvariable cuando desee verificar fuera del ciclo si el valor se encontró en la matriz. También desde un punto de vista de mantenibilidad, tener una bandera común que indique los criterios de salida podría ser útil.


5

Hice un análisis sobre la base de código en la que estoy trabajando actualmente (40,000 líneas de JavaScript).

Encontré solo 22 breakdeclaraciones, de esas:

  • 19 se usaron dentro de las switchdeclaraciones (¡solo tenemos 3 declaraciones de cambio en total!).
  • 2 se usaron dentro de forbucles, un código que inmediatamente clasifiqué como refactorizado en funciones separadas y reemplazado por una returndeclaración.
  • En cuanto al último bucle breakinterno while... ¡Corrí git blamea ver quién escribió esta basura!

Entonces, según mis estadísticas: si breakse usa fuera de switch, es un olor a código.

También busqué continuedeclaraciones. No encontré ninguno.


4

No veo ninguna razón por la cual sería una mala práctica, siempre que desee completar el procesamiento de STOP en ese momento.


4

En el mundo incrustado, existe una gran cantidad de código que utiliza la siguiente construcción:

    while(1)
    { 
         if (RCIF)
           gx();
         if (command_received == command_we_are_waiting_on)
           break;
         else if ((num_attempts > MAX_ATTEMPTS) || (TickGet() - BaseTick > MAX_TIMEOUT))
           return ERROR;
         num_attempts++;
    }
    if (call_some_bool_returning_function())
      return TRUE;
    else
      return FALSE;

Este es un ejemplo muy genérico, muchas cosas están sucediendo detrás de la cortina, interrupciones en particular. No use esto como código repetitivo, solo estoy tratando de ilustrar un ejemplo.

Mi opinión personal es que no hay nada de malo en escribir un bucle de esta manera, siempre y cuando se tomen las precauciones adecuadas para evitar permanecer indefinidamente en el bucle.


1
¿Podrías dar más detalles sobre eso? Me parece que el ciclo no hace absolutamente nada ... a menos que haya continuedeclaraciones dentro de la lógica de la aplicación. ¿Hay?
Rene Saarsoo el

1
Las declaraciones de continuación son innecesarias ya que ya está obligado por un bucle infinito. Básicamente, realiza la lógica necesaria requerida para lograr un objetivo y si ese objetivo no se cumple en n número de intentos o vence una condición de tiempo de espera, sale del bucle a través de una construcción de interrupción y toma medidas correctivas o informa al código de llamada de un error. Veo este tipo de código a menudo en uC que se comunican a través de un bus común (RS-485, etc.). Básicamente intenta aprovechar la línea y comunicarse sin colisiones, si ocurren colisiones, espera una duración determinada por un número aleatorio e intenta de nuevo.
Nate

1
Por el contrario, si las cosas van bien, también usa la instrucción break para salir una vez que se haya cumplido la condición. Dado este caso, el código que sigue al ciclo se ejecuta y todos están contentos.
Nate

"if (call_some_bool_returning_function ()) devuelve TRUE; de lo contrario, devuelve FALSE;" podría ser "return call_some_bool_returning_function ()"
frankster

3

Depende de su caso de uso. Hay aplicaciones en las que el tiempo de ejecución de un bucle for debe ser constante (por ejemplo, para satisfacer algunas restricciones de tiempo o para ocultar sus datos internos de ataques basados ​​en tiempo).

En esos casos, incluso tendrá sentido establecer un indicador y solo verificar el valor del indicador DESPUÉS de que se forhayan ejecutado todas las iteraciones de bucle. Por supuesto, todas las iteraciones de bucle for necesitan ejecutar código que todavía demora aproximadamente el mismo tiempo.

Si no le importa el tiempo de ejecución ... use break;y continue;para que el código sea más fácil de leer.


1
Si el tiempo es importante, es mejor que duerma un poco y ahorre recursos del sistema que dejar que el programa realice operaciones inútiles.
Bgs

2

En las reglas de MISRA 98, que se usa en mi compañía en C dev, la declaración de interrupción no se usará ...

Editar: Se permite el descanso en MISRA '04


2
¡Exactamente por qué hice esta pregunta! No encuentro una buena razón por la cual tal regla está allí como una guía de codificación. Además, como todos los demás aquí dijeron, descanso; Se ve mejor.
kiki

1
No sé exactamente por qué está prohibido (una persona más inteligente que yo lo he pensado ...) pero el descanso (y el retorno múltiple) son mejores para la legibilidad (en mi opinión, pero mis opiniones no son reglas corporativas ... )
Benoît

1
Uh, las reglas de MISRA permiten el uso de declaraciones de interrupción: misra.org.uk/forum/viewtopic.php?f=75&t=298
luis.espinal

Utilizamos las reglas de MISRA 98 ... Es exacto que MISRA 2004 cambia las reglas
Benoît

0

Por supuesto, break;es la solución para detener el bucle for o foreach. Lo usé en php en foreach y para loop y encontré que funcionaba.


0

¡Estoy en desacuerdo!

¿Por qué ignorarías la funcionalidad incorporada de un bucle for para crear el tuyo propio? No es necesario reinventar la rueda.

Creo que tiene más sentido tener sus cheques en la parte superior de su bucle for así

for(int i = 0; i < myCollection.Length && myCollection[i].SomeValue != "Break Condition"; i++)
{
//loop body
}

o si necesita procesar la fila primero

for(int i = 0; i < myCollection.Length && (i == 0 ? true : myCollection[i-1].SomeValue != "Break Condition"); i++)
{
//loop body
}

De esta manera, puede escribir una función para realizar todo y crear un código mucho más limpio.

for(int i = 0; i < myCollection.Length && (i == 0 ? true : myCollection[i-1].SomeValue != "Break Condition"); i++)
{
    DoAllThatCrazyStuff(myCollection[i]);
}

O si su condición es complicada, ¡también puede mover ese código!

for(int i = 0; i < myCollection.Length && BreakFunctionCheck(i, myCollection); i++)
{
    DoAllThatCrazyStuff(myCollection[i]);
}

El "Código profesional" que está plagado de interrupciones en realidad no me parece un código profesional. Suena como una codificación perezosa;)


44
código profesional ES código perezoso :)
Jason

Solo si no es un problema mantenerlo :)
Biff MaGriff

2
Tiendo a practicar "GTFO ASAP". Es decir, si puedo salir de una estructura limpiamente, debería hacerlo lo antes posible y tan cerca de la condición que especifica la salida.
Chris Cudmore

Bueno, eso también funciona. Creo que el argumento que estoy tratando de transmitir es que si está utilizando un bucle for, no está de más usar su funcionalidad integrada para que su código sea legible y modular. Si realmente necesita tener declaraciones de ruptura dentro de su ciclo, me sentaría y pensaría por qué se necesita ese nivel de complejidad.
Biff MaGriff

1
Jaja. Considere el hecho de que la mayoría de las personas que desalientan breakargumentan que es por la legibilidad del código. Ahora mira de nuevo la loca condición de 5 millas de largo en los bucles de arriba ...
Tomáš Zato - Restablece a Monica el
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.