¿Es una mala práctica usar break para salir de un bucle en Java?

Me preguntaba si es una “mala práctica” usar una instrucción break para salir de un bucle en lugar de cumplir la condición de bucle.

No tengo suficiente información sobre Java y la JVM para saber cómo se maneja un bucle, así que me preguntaba si estaba pasando por alto algo crítico al hacerlo.

El enfoque de esta pregunta: ¿hay una sobrecarga de rendimiento específica?

Buen señor no. A veces existe la posibilidad de que algo pueda ocurrir en el ciclo que satisfaga el requisito general, sin satisfacer la condición de ciclo lógico. En ese caso, se usa el break para evitar que vayas dando vueltas en un bucle sin sentido.

Ejemplo

 String item; for(int x = 0; x < 10; x++) { // Linear search. if(array[x].equals("Item I am looking for")) { //you've found the item. Let's stop. item = array[x]; break; } } 

Lo que tiene más sentido en este ejemplo. Continúa repitiendo hasta 10 cada vez, incluso después de haberlo encontrado, o haz un bucle hasta que encuentres el objeto y te detengas. O para ponerlo en términos del mundo real; cuando encuentras tus llaves, ¿sigues buscando?

Editar en respuesta al comentario

¿Por qué no configurar x a 11 para romper el ciclo? Carece de sentido. ¡Tenemos break ! A menos que su código suponga que x es definitivamente más grande que 10 más tarde (y probablemente no debería ser así), entonces está bien simplemente usando break .

Edite en aras de la integridad

Definitivamente hay otras formas de simular el break . Por ejemplo, agregar lógica adicional a su condición de terminación en su ciclo. Decir que es un bucle inútil o usar un break no es justo. Como se señaló, un ciclo while a menudo puede lograr una funcionalidad similar. Por ejemplo, siguiendo el ejemplo anterior ...

 while(x < 10 && item == null) { if(array[x].equals("Item I am looking for")) { item = array[x]; } x++; } 

Usar el break simplemente significa que puede lograr esta funcionalidad con un bucle for . También significa que no tiene que seguir agregando condiciones en su lógica de terminación, siempre que desee que el ciclo se comporte de manera diferente. Por ejemplo.

 for(int x = 0; x < 10; x++) { if(array[x].equals("Something that will make me want to cancel")) { break; } else if(array[x].equals("Something else that will make me want to cancel")) { break; } else if(array[x].equals("This is what I want")) { item = array[x]; } } 

En lugar de un while loop con una condición de terminación que se ve así:

 while(x < 10 && !array[x].equals("Something that will make me want to cancel") && !array[x].equals("Something else that will make me want to cancel")) 

Usar el break , como prácticamente cualquier otra característica del idioma, puede ser una mala práctica, dentro de un contexto específico, en el que claramente estás haciendo un mal uso. Pero algunas expresiones idiomáticas muy importantes no pueden codificarse sin ella, o al menos dar como resultado un código mucho menos legible. En esos casos, break es el camino a seguir.

En otras palabras, no escuche ningún consejo general, no calificado, sobre un break o cualquier otra cosa. No es una vez que he visto código totalmente demacrado solo para hacer cumplir literalmente una “buena práctica”.

En cuanto a su preocupación por los gastos generales de rendimiento, no hay absolutamente nada. En el nivel de código de bytes no hay construcciones explícitas de bucle de todos modos: todo el control de flujo se implementa en términos de saltos condicionales.

El JLS especifica que una ruptura es una terminación anormal de un bucle. Sin embargo, el hecho de que se considere anormal no significa que no se use en muchos ejemplos de código, proyectos, productos, transbordadores espaciales, etc. La especificación de JVM no indica ni la existencia ni la ausencia de una pérdida de rendimiento, aunque es la ejecución clara del código continuará después del ciclo.

Sin embargo, la legibilidad del código puede sufrir interrupciones impares. Si está haciendo una pausa en una statement if compleja rodeada de efectos secundarios y códigos de limpieza extraños, posiblemente con un salto de varios niveles con una etiqueta (o peor, con un extraño conjunto de condiciones de salida uno tras otro), no va a funcionar. ser fácil de leer para todos.

Si quiere romper su ciclo forzando a la variable de iteración a estar fuera del rango de iteración, o al introducir una forma de salida no necesariamente directa, es menos legible que el break .

Sin embargo, el bucle de tiempos extra de una manera vacía es casi siempre una mala práctica, ya que requiere iteraciones adicionales y puede no estar claro.

En mi opinión, se debe usar un bucle For cuando se realice una cantidad fija de iteraciones y no se detendrán antes de que se complete cada iteración. En el otro caso en el que desea salir antes, prefiero usar un ciclo While . Incluso si lees esas dos pequeñas palabras, parece más lógico. Algunos ejemplos:

 for (int i=0;i<10;i++) { System.out.println(i); } 

Cuando leo este código rápidamente sabré con certeza que imprimirá 10 líneas y luego continuará.

 for (int i=0;i<10;i++) { if (someCondition) break; System.out.println(i); } 

Este ya es menos claro para mí. ¿Por qué primero declaras que tomarás 10 iteraciones, pero luego dentro del ciclo agregas algunas condiciones adicionales para parar más rápido?

Prefiero el ejemplo anterior escrito de esta manera (incluso cuando es un poco más detallado, pero eso es solo con 1 línea más):

 int i=0; while (i<10 && !someCondition) { System.out.println(i); i++; } 

Todos los que lean este código verán inmediatamente que existe una condición adicional que podría terminar el ciclo antes.

Por supuesto, en bucles muy pequeños siempre se puede discutir que cada progtwigdor notará la statement de interrupción. Pero puedo decir por mi propia experiencia que en los bucles más grandes esos descansos pueden ser supervisados. (Y eso nos lleva a otro tema para comenzar a dividir el código en trozos más pequeños)

Usar bucles de interrupción puede ser perfectamente legítimo e incluso puede ser la única forma de resolver algunos problemas.

Sin embargo, su mala reputación proviene del hecho de que los progtwigdores nuevos suelen abusar de ella, lo que genera un código confuso, especialmente al usar break para detener el bucle en condiciones que podrían haberse escrito en la statement de condición de ciclo en primer lugar.

No, no es una mala práctica salir de un bucle cuando se llega a cierta condición deseada (como encontrar una coincidencia). Muchas veces, es posible que desee detener las iteraciones porque ya ha logrado lo que desea, y no tiene sentido iterar más. Pero ten cuidado de asegurarte de que no te falte algo por accidente ni de que se rompa cuando no se requiera.

Esto también puede boost la mejora del rendimiento si rompe el ciclo, en lugar de iterar sobre miles de registros, incluso si el propósito del ciclo está completo (es decir, puede coincidir con el registro requerido que ya está hecho).

Ejemplo:

 for (int j = 0; j < type.size(); j++) { if (condition) { // do stuff after which you want break; // stop further iteration } } 

No es una mala práctica, pero puede hacer que el código sea menos legible. Una refactorización útil para evitar esto es mover el ciclo a un método separado, y luego usar una statement de retorno en lugar de un salto, por ejemplo, esto (ejemplo extraído de la respuesta de @ Chris):

 String item; for(int x = 0; x < 10; x++) { // Linear search. if(array[x].equals("Item I am looking for")) { //you've found the item. Let's stop. item = array[x]; break; } } 

se puede refactorizar (utilizando el método de extracción ) para esto:

 public String searchForItem(String itemIamLookingFor) { for(int x = 0; x < 10; x++) { if(array[x].equals(itemIamLookingFor)) { return array[x]; } } } 

Que cuando se llama desde el código circundante puede resultar más legible.

Si empiezas a hacer algo como esto, entonces diría que empieza a ser un poco extraño y que es mejor que lo muevas a un método separado que arroje un resultado sobre la condición coincidente.

 boolean matched = false; for(int i = 0; i < 10; i++) { for(int j = 0; j < 10; j++) { if(matchedCondition) { matched = true; break; } } if(matched) { break; } } 

Para más detalles sobre cómo limpiar el código anterior, puede refactorizar, moviendo el código a una función que returns lugar de usar breaks . En general, se trata mejor de las breaks complejas / desordenadas.

 public boolean matches() for(int i = 0; i < 10; i++) { for(int j = 0; j < 10; j++) { if(matchedCondition) { return true; } } } return false; } 

Sin embargo, para algo simple como mi ejemplo de abajo. ¡Por supuesto use break !

 for(int i = 0; i < 10; i++) { if(wereDoneHere()) { // we're done, break. break; } } 

Y cambiando las condiciones, en el caso anterior i , y el valor de j , simplemente harías el código realmente difícil de leer. También podría haber un caso en el que los límites superiores (10 en el ejemplo) son variables, por lo que sería aún más difícil adivinar qué valor establecer para salir del ciclo. Por supuesto, puede configurar i y j en Integer.MAX_VALUE, pero creo que puede ver que esto comienza a complicarse muy rápidamente. 🙂

Hay una serie de situaciones comunes para las cuales la break es la forma más natural de express el algoritmo. Se llaman constructos de “bucle y media”; el ejemplo del paradigma es

 while (true) { item = stream.next(); if (item == EOF) break; process(item); } 

Si no puedes usar el break para esto, debes repetirlo en su lugar:

 item = stream.next(); while (item != EOF) { process(item); item = stream.next(); } 

En general, se está de acuerdo en que esto es peor.

Del mismo modo, para continue , hay un patrón común que se ve así:

 for (item in list) { if (ignore_p(item)) continue; if (trivial_p(item)) { process_trivial(item); continue; } process_complicated(item); } 

Esto a menudo es más legible que la alternativa con el encadenado else if , especialmente cuando process_complicated es más que una llamada a la función.

Lectura adicional: Salidas de bucle y progtwigción estructurada: Reapertura del debate

No, no es una mala práctica. Es la forma más fácil y eficiente.

Si bien no es una mala práctica utilizar el descanso y existen muchos usos excelentes para él, no debería ser lo único en lo que confías. Casi cualquier uso de una ruptura se puede escribir en la condición de bucle. El código es mucho más legible cuando se usan condiciones reales, pero en el caso de un ciclo largo o infinito, los descansos tienen mucho sentido. También tienen sentido cuando se buscan datos, como se muestra arriba.

Si sabe de antemano dónde tendrá que detenerse el bucle, probablemente mejorará la legibilidad del código para indicar la condición en el bucle for , while o `do-while while.

De lo contrario, ese es el caso de uso exacto para el break .

break y continue rompe la legibilidad para el lector, aunque a menudo es útil. No tanto como el concepto “goto”, pero casi.

Además, si tomas algunos lenguajes nuevos como Scala (inspirado en Java y lenguajes de progtwigción funcionales como Ocaml), notarás que el break y continue simplemente desaparecieron.

Especialmente en la progtwigción funcional, se evita este estilo de código:

¿Por qué Scala no admite romper y continuar?

En resumen: break y continue son ampliamente utilizados en Java para un estilo imperativo, pero para cualquier progtwigdor que solía practicar progtwigción funcional, podría ser … extraño.