¿Son `break` y` continue` malas prácticas de programación?

191

Mi jefe sigue mencionando con indiferencia que los malos programadores usan breaky continueen bucles.

Los uso todo el tiempo porque tienen sentido; déjame mostrarte la inspiración:

function verify(object) {
    if (object->value < 0) return false;
    if (object->value > object->max_value) return false;
    if (object->name == "") return false;
    ...
}

El punto aquí es que primero la función verifica que las condiciones sean correctas, luego ejecuta la funcionalidad real. OMI mismo se aplica con bucles:

while (primary_condition) {
    if (loop_count > 1000) break;
    if (time_exect > 3600) break;
    if (this->data == "undefined") continue;
    if (this->skip == true) continue;
    ...
}

Creo que esto facilita la lectura y depuración; pero tampoco veo un inconveniente.

Mikhail
fuente
2
No hace falta mucho para olvidar cuál hace qué.
57
No. Tampoco goto. Saber cuándo usarlos es la clave. Son herramientas en la caja de herramientas. Los usa cuando proporcionan un código claro y conciso.
orj
67
No puedo expresar mi apoyo a este estilo de codificación con suficiente fuerza. Múltiples niveles de condicionales anidados son mucho peores que este enfoque. Por lo general, no soy militante sobre el estilo de codificación, pero esto es casi un factor decisivo para mí.
Emil H
99
Obviamente su jefe no escribe (suficiente) código. Si lo hiciera, sabría que todas las palabras clave (sí, incluso goto) son útiles en algunos casos.
sakisk
57
Los malos programadores usan break y continue no significa que los buenos programadores no lo hagan. Los malos programadores también usan if y while.
Mouviciel

Respuestas:

240

Cuando se usan al comienzo de un bloque, cuando se realizan las primeras verificaciones, actúan como condiciones previas, por lo que es bueno.

Cuando se usan en el medio del bloque, con algo de código, actúan como trampas ocultas, por lo que es malo.

Klaim
fuente
66
@Klaim: Se puede argumentar que cualquier rutina que tenga varios puntos de salida es una rutina mal factorizada. Una rutina correctamente factorizada debe hacer una cosa y solo una cosa.
bit-twiddler
79
@ bit-twiddler: esta es una mentalidad muy C-ish. Una variable temporal se puede modificar más adelante, por lo que un solo error tipográfico 20 líneas más abajo podría borrar ese resultado cuidadosamente elaborado. Sin embargo, un retorno inmediato (o interrupción o continuación) es extremadamente claro: puedo dejar de leer ahora porque sé que no es posible modificarlo más abajo . Es bueno para mi pequeño cerebro, realmente hace que caminar por el código sea más fácil.
Matthieu M.
15
@ Matthieu estoy de acuerdo. Salga de un bloque cuando reciba un resultado que satisfaga el propósito del bloque.
Evan Plaice
8
@ bit-twiddler: el principio del punto de salida único es independiente del principio de responsabilidad única. No estoy de acuerdo en que la comprobación siempre esté separada de la responsabilidad de WRT. De hecho, "responsabilidad única" siempre me parece un término subjetivo. Por ejemplo, en un solucionador cuadrático, ¿calcular el discriminante debería ser una responsabilidad separada? ¿O puede toda la fórmula cuadrática ser una sola responsabilidad? Yo diría que depende de si tiene un uso separado para el discriminante; de ​​lo contrario, tratarlo como una responsabilidad separada es probablemente excesivo.
Steve314
10
Esa respuesta es una regla general, no una regla difícil. Funciona en la mayoría de los casos, no dude en romperlo si tiene sentido en su contexto.
Klaim
87

Podrías leer el documento de 1974 de Donald Knuth, Estructuración de la programación, con Ir a declaraciones , en el que analiza varios usos de los go toque son estructuralmente deseables. Incluyen el equivalente de breaky continuedeclaraciones (muchos de los usos de go toallí se han desarrollado en construcciones más limitadas). ¿Es tu jefe el tipo de llamar a Knuth un mal programador?

(Los ejemplos dados me interesan. Por lo general, breaky continueno les gustan las personas que les gusta una entrada y una salida de cualquier pieza de código, y ese tipo de persona también frunce el ceño en varios returnestados.)

David Thornley
fuente
8
La mayoría de las personas a las que les gusta que las funciones y los procedimientos tengan puntos de entrada y salida únicos crecieron en Pascal. Pascal no fue el primer idioma que aprendí, pero tuvo un profundo impacto en cómo estructuro el código hasta el día de hoy. La gente siempre comenta lo fácil que es leer mi código Java. Esto se debe a que evito múltiples puntos de salida, así como también mezclar declaraciones con código. Hago mi mejor esfuerzo para declarar cada variable local utilizada en un método en la parte superior del método. Esta práctica evita divagaciones de código al obligarme a mantener los métodos concisos.
bit-twiddler
55
Pascal también tenía funciones anidadas. Solo
digo
66
Por lo que recuerdo, en el pasado, la razón principal por la que a la gente no le gustaban las declaraciones de devolución múltiples en las funciones era porque los depuradores no las manejaban correctamente. Fue un verdadero dolor establecer un punto de interrupción al final de una función, pero nunca lo golpeó debido a una declaración de devolución anterior. Para cada compilador que uso hoy en día, eso ya no es un problema.
Dunk
28
@ bit-twiddler: No estoy tan seguro de eso. Todavía estoy usando Pascal hoy, y generalmente considero "entrada única, salida única", o al menos la parte de salida única, como programación de culto de carga. Solo lo considero Break, Continuey Exitcomo herramientas en mi caja de herramientas; Los uso donde hace que el código sea más fácil de seguir, y no los uso donde sea más difícil de leer.
Mason Wheeler
2
@ bit-twiddler: amén a eso. También agregaré que una vez que tenga bloques que se ajustan fácilmente en la pantalla, los puntos de salida múltiples se vuelven mucho menos problemáticos.
Shog9
44

No creo que sean malos. La idea de que son malas proviene de los días de la programación estructurada. Está relacionado con la noción de que una función debe tener un único punto de entrada y un único punto de salida, es decir, solo uno returnpor función.

Esto tiene sentido si su función es larga y si tiene múltiples bucles anidados. Sin embargo, sus funciones deben ser cortas y debe envolver los bucles y sus cuerpos en funciones cortas propias. En general, obligar a una función a tener un único punto de salida puede resultar en una lógica muy complicada.

Si su función es muy corta, si tiene un solo bucle, o en el peor de los casos, dos bucles anidados, y si el cuerpo del bucle es muy corto, entonces está muy claro lo que hace a breako a continue. También está claro lo returnque hacen las declaraciones múltiples .

Estos problemas se abordan en "Clean Code" de Robert C. Martin y en "Refactoring" de Martin Fowler.

Dima
fuente
12
"Haz que tus funciones sean pequeñas. Luego hazlas más pequeñas" -Robert C. Martin. Descubrí que esto funciona sorprendentemente bien. Cada vez que vea un bloque de código en una función que necesite un comentario que explique lo que hace, envuélvalo en una función separada con un nombre descriptivo. Incluso si solo son unas pocas líneas, e incluso si solo se usa una vez. Esta práctica elimina la mayoría de los problemas con pausa / continuar o devoluciones múltiples.
Dima
2
@Mikhail: La complejidad ciclomática generalmente está bastante relacionada con SLOC, lo que significa que el consejo se puede simplificar para "no escribir funciones largas".
John R. Strohm
3
La idea de un único punto de salida es ampliamente malinterpretada. Érase una vez, las funciones no tenían que devolver su llamador. Podrían regresar a otro lugar. Esto se hacía comúnmente en lenguaje ensamblador. Fortran tenía una construcción especial para esto; podría pasar un número de declaración precedido de un signo CALL P(X, Y, &10)y, en caso de error, la función podría pasar el control a esa declaración, en lugar de regresar al punto de la llamada.
Kevin Cline
@kevincline como se ve con Lua, por ejemplo.
Qix
1
@cjsimon lo tienes. No solo las funciones deben ser pequeñas. Las clases también deberían ser pequeñas.
Dima
39

Los malos programadores hablan en absoluto (al igual que Sith). Los buenos programadores usan la solución más clara posible ( todas las demás cosas son iguales ).

Usar break y continue frecuentemente hace que el código sea difícil de seguir. Pero si reemplazarlos hace que el código sea aún más difícil de seguir, entonces ese es un mal cambio.

El ejemplo que diste es definitivamente una situación en la que los descansos y continuos deberían ser reemplazados por algo más elegante.

Matthew Read
fuente
Sí, exageré la cantidad de condiciones para proporcionar ejemplos de casos para salir.
Mikhail
99
¿Cuál es un ejemplo del código de reemplazo que ha sugerido? Pensé que era un ejemplo bastante razonable de declaraciones de guardia.
simgineer
Me parece que "la solución más clara posible" es siempre ... ¿posible? ¿Cómo podría no haber una solución "más clara"? Pero entonces, no soy de los absolutos, así que quizás tengas razón.
@nocomprende No estoy seguro de a qué te refieres. Lo "posible" aquí no indica que la mejor solución no exista, solo que la claridad perfecta y definitiva no es una cosa. Es subjetivo, después de todo.
Matthew leyó el
22

La mayoría de la gente piensa que es una mala idea porque el comportamiento no es fácilmente predecible. Si está leyendo el código y ve while(x < 1000){}que asume que se ejecutará hasta x> = 1000 ... Pero si hay interrupciones en el medio, entonces eso no es cierto, por lo que realmente no puede confiar en su bucle ...

Es la misma razón por la que a la gente no le gusta GOTO: claro, se puede usar bien, pero también puede conducir a un codicioso código de espagueti, donde el código salta al azar de una sección a otra.

Para mí, si iba a hacer un bucle que se rompió en más de una condición, while(x){}entonces cambiaría a X a falso cuando necesitaba salir. El resultado final sería el mismo, y cualquiera que lea el código sabría mirar más de cerca las cosas que cambiaron el valor de X.

Satanicpuppy
fuente
2
+1 muy bien dicho, y +1 (si pudiera hacer otro) para el while(notDone){ }enfoque.
FrustratedWithFormsDesigner
55
Mikhail: El problema con el descanso es que la condición final para el ciclo nunca se establece simplemente en un lugar. Eso hace que sea difícil predecir la condición posterior después del ciclo. En este caso trivial (> = 1000) no es difícil. Agregue muchas declaraciones if y diferentes niveles de anidamiento, puede resultar muy, muy difícil determinar la condición posterior del bucle.
S.Lott
S.Lott golpeó el clavo directamente en la cabeza. La expresión que controla la iteración debe incluir todas las condiciones que deben cumplirse para continuar la iteración.
bit-twiddler
66
Reemplazar break;con x=false;no hace que su código sea más claro. Todavía tiene que buscar en el cuerpo esa declaración. Y en el caso de x=false;que tenga que comprobar que no golpee x=true;más abajo.
Sjoerd
14
Cuando la gente dice "Veo xy supongo que y, pero si haces z esa suposición no es válida" tiendo a pensar "así que no hagas esa estúpida suposición". Mucha gente lo simplificaría a "cuando vea while (x < 1000), supongo que se ejecutará 1000 veces". Bueno, hay muchas razones por las que eso es falso, incluso si xinicialmente es cero. Por ejemplo, ¿quién dice que xse incrementa con precisión una vez durante el ciclo y nunca se modifica de otra manera? Incluso para su propia suposición, el hecho de que algo se establezca x >= 1000no significa que el ciclo finalizará; puede volver a establecerse dentro del rango antes de que se verifique la condición.
Steve314
14

Sí, puede [re] escribir programas sin declaraciones de interrupción (o retornos desde la mitad de los bucles, que hacen lo mismo). Pero es posible que deba introducir variables adicionales y / o duplicación de código, lo que generalmente hace que el programa sea más difícil de entender. Pascal (el lenguaje de programación) fue muy malo, especialmente para los programadores principiantes por esa razón. Su jefe básicamente quiere que programe en las estructuras de control de Pascal. Si Linus Torvalds estuviera en tu lugar, ¡probablemente le mostraría a tu jefe el dedo medio!

Hay un resultado informático llamado jerarquía de estructuras de control de Kosaraju, que se remonta a 1973 y que se menciona en el famoso (más) documento de Knuth sobre gotos de 1974. (David Thornley ya recomendó este documento de Knuth anteriormente) .) Lo que S. Rao Kosaraju demostró en 1973 es que no es posible reescribir todos los programas que tienen saltos de profundidad de niveles múltiples n en programas con profundidad de descanso menor que n sin introducir variables adicionales. Pero digamos que es solo un resultado puramente teórico. (¡¿Solo agregue algunas variables adicionales?! Seguramente puede hacer eso para complacer a su jefe ...)

Lo que es mucho más importante desde una perspectiva de ingeniería de software es un artículo más reciente de 1995 de Eric S. Roberts titulado Salidas de bucle y programación estructurada: reapertura del debate ( http://cs.stanford.edu/people/eroberts/papers/SIGCSE- 1995 / LoopExits.pdf ). Roberts resume varios estudios empíricos realizados por otros antes que él. Por ejemplo, cuando a un grupo de estudiantes de tipo CS101 se les pidió que escribieran código para una función que implementa una búsqueda secuencial en una matriz, el autor del estudio dijo lo siguiente sobre aquellos estudiantes que usaron un descanso / retorno / goto para salir de el ciclo de búsqueda secuencial cuando se encontró el elemento:

Todavía no he encontrado una sola persona que haya intentado un programa usando [este estilo] que haya producido una solución incorrecta.

Roberts también dice que:

A los estudiantes que intentaron resolver el problema sin utilizar un retorno explícito del ciclo for les fue mucho peor: solo siete de los 42 estudiantes que intentaron esta estrategia lograron generar soluciones correctas. Esa cifra representa una tasa de éxito de menos del 20%.

Sí, es posible que tenga más experiencia que los estudiantes de CS101, pero sin usar la declaración de interrupción (o equivalentemente regresar / ir desde el medio de los bucles), eventualmente escribirá código que, si bien nominalmente está bien estructurado, es bastante complicado en términos de lógica adicional variables y duplicación de código que alguien, probablemente usted mismo, pondrá errores lógicos al tratar de seguir el estilo de codificación de su jefe.

También voy a decir aquí que el trabajo de Roberts es mucho más accesible para el programador promedio, por lo que es mejor leerlo primero que el de Knuth. También es más corto y cubre un tema más específico. Probablemente incluso podría recomendarlo a su jefe, incluso si él es el administrador en lugar del tipo CS.

usuario3588161
fuente
2
Aunque la programación estructurada fue mi enfoque durante muchos años, en los últimos pocos pasé a utilizar completamente salidas explícitas en la primera oportunidad posible. Esto hace que la ejecución sea más rápida y casi elimina los errores lógicos que solían dar como resultado bucles sin fin (bloqueos).
DocSalvager
9

No considero usar ninguna de estas malas prácticas, pero usarlas demasiado dentro del mismo ciclo debería justificar repensar la lógica que se usa en el ciclo. Úsalos con moderación.

Bernardo
fuente
:) sí, el ejemplo que proporcioné fue conceptual
Mikhail
7

El ejemplo que diste no necesita descansos ni continúa:

while (primary-condition AND
       loop-count <= 1000 AND
       time-exec <= 3600) {
   when (data != "undefined" AND
           NOT skip)
      do-something-useful;
   }

Mi "problema" con las 4 líneas en su ejemplo es que todas están en el mismo nivel pero hacen cosas diferentes: algunas se rompen, algunas continúan ... Debe leer cada línea.

En mi enfoque anidado, cuanto más profundizas, más 'útil' se vuelve el código.

Pero, si en el fondo encuentra una razón para detener el ciclo (que no sea una condición primaria), una interrupción o retorno tendría su uso. Prefiero eso sobre el uso de una bandera adicional que se probará en la condición de nivel superior. El descanso / retorno es más directo; establece mejor la intención que establecer otra variable más.

Peter Frings
fuente
+1 Pero en realidad sus <comparaciones deben ser <=para que coincida con la solución de los OP
El Ronnoco
3
En la mayoría de los casos, si uno es tan profundo que necesita usar interrupción / retorno para administrar el control de flujo, la función / método de uno es demasiado complejo.
bit-twiddler
6

La "maldad" depende de cómo los uses. Normalmente uso interrupciones en construcciones de bucle SOLO cuando me ahorrará ciclos que no se pueden guardar mediante la refactorización de un algoritmo. Por ejemplo, recorrer una colección en busca de un elemento con un valor en una propiedad específica establecida en verdadero. Si todo lo que necesita saber es que uno de los elementos tenía esta propiedad establecida en verdadero, una vez que logre ese resultado, un descanso es bueno para terminar el ciclo adecuadamente.

Si usar un descanso no hará que el código sea específicamente más fácil de leer, más corto de ejecutar o guardar ciclos de procesamiento de manera significativa, entonces es mejor no usarlos. Cuando es posible, tiendo a codificar con el "mínimo común denominador" para asegurarme de que cualquiera que me siga pueda ver fácilmente mi código y descubrir qué está pasando (no siempre tengo éxito en esto). Los descansos reducen eso porque introducen puntos de entrada / salida extraños. Mal uso, pueden comportarse de manera muy parecida a una declaración "goto" fuera de control.

Joel Etherton
fuente
¡Estoy de acuerdo con ambos puntos! En cuanto a su segundo punto, creo que es más fácil seguir mi publicación original porque se lee como en inglés. Si tiene una combinación de condiciones en la instrucción 1 if, es casi un desciframiento descubrir qué tiene que suceder para que if se ejecute verdadero.
Mikhail
@Mikhail: Las muestras que proporcionó son un poco altruistas para un realismo específico. A mi entender, esas muestras son claras, concisas, más fáciles de leer. La mayoría de los bucles no son así. La mayoría de los bucles tienen algún otro tipo de lógica que están ejecutando y condicionales potencialmente mucho más complicados. Es en estos casos donde break / continue puede no ser el mejor uso porque enturbia la lógica cuando se lee.
Joel Etherton
4

Absolutamente no ... Sí, el uso de gotoes malo porque deteriora la estructura de su programa y también es muy difícil entender el flujo de control.

Pero el uso de declaraciones como breaky continueson absolutamente necesarias en estos días y no se consideran como una mala práctica de programación.

Y tampoco es tan difícil de entender el flujo de control en uso de breaky continue. En construcciones como switchla breakdeclaración es absolutamente necesaria.

Radheshyam Nayak
fuente
2
No he usado "continuar" desde que aprendí C por primera vez en 1981. Es una característica de lenguaje innecesaria, ya que el código que se omite en la declaración de continuación se puede incluir en una declaración de control condicional.
bit-twiddler
12
Prefiero usar continuar en esos casos, ya que se asegura de que mi código no se convierta en código de flecha. Odio el código de flecha más que las declaraciones tipo goto. También lo leí como, "si esta afirmación es verdadera, omita el resto de este ciclo y continúe con la próxima iteración". Muy útil cuando está al comienzo de un bucle for (menos útil en bucles while).
jsternberg
@jsternberg ¡Por la victoria! :-)
Notinlist
3

La noción esencial proviene de poder analizar semánticamente su programa. Si tiene una sola entrada y una única salida, la matemática necesaria para denotar posibles estados es considerablemente más fácil que si tiene que gestionar rutas de bifurcación.

En parte, esta dificultad se refleja en ser capaz de razonar conceptualmente sobre su código.

Francamente, tu segundo código no es obvio. ¿Qué está haciendo? ¿Continúa 'continúa' o 'sigue' el ciclo? No tengo idea. Al menos tu primer ejemplo es claro.

Paul Nathan
fuente
Cuando trabajo en un proyecto que obliga a usar gestor de ellos, tuve que usar un diagrama de flujo para recordar su uso, y las varias rutas de salida, hice código º más confuso ...
umlcat
su comentario "francamente" es sintáctico - "siguiente" es una cosa perl; los idiomas normales usan "continuar" para significar "omitir este ciclo"
Mikhail
@Mik, tal vez "omitir esta iteración" sería una mejor descripción. Atentamente, Dr. IM Pedantic
Pete Wilson
@ Mikhail: Claro. Pero cuando uno trabaja en muchos idiomas, puede generar errores cuando la sintaxis no es común entre los idiomas.
Paul Nathan
Pero las matemáticas no son programación. El código es más expresivo que las matemáticas. Yo entiendo que una sola entrada / salida solo puede hacer diagramas de flujo se ven más bonitas, pero a qué costo (es decir., Donde ruptura / retorno puede hacer que el código funcione mejor)?
Evan Plaice
3

Reemplazaría su segundo fragmento de código con

while (primary_condition && (loop_count <= 1000 && time_exect <= 3600)) {
    if (this->data != "undefined" && this->skip != true) {
        ..
    }
}

no por ninguna razón de terquedad: en realidad creo que esto es más fácil de leer y que alguien entienda lo que está sucediendo. En términos generales, las condiciones para sus bucles deben estar contenidas únicamente dentro de esas condiciones de bucle que no están esparcidas por todo el cuerpo. Sin embargo, hay algunas situaciones donde breaky continuepuede ayudar a la legibilidad. breakmás de lo continueque podría agregar: D

El Ronnoco
fuente
MIENTRAS no estoy de acuerdo con, "En realidad creo que esto es más fácil de leer", esto cumple exactamente el mismo propósito que el código anterior. Nunca he pensado realmente en 'break' como un operador de cortocircuito hasta ahora, pero tiene mucho sentido. En cuanto a "En términos generales, las condiciones para sus bucles deben estar contenidas puramente dentro de esas condiciones de bucle", ¿qué hace cuando procesa un bucle foreach ya que realmente no hay forma de insertar lógica condicional en la definición del bucle?
Evan Plaice
@Evan La condición no es aplicable a un foreachbucle ya que esto solo repetirá cada elemento de una colección. Un forbucle es similar en el sentido de que no debe tener un punto final condicional. Si necesita un punto final condicional, debe usar un whilebucle.
El Ronnoco
1
@Evan, sin embargo, entiendo tu punto de vista, es decir, '¿qué pasa si necesitas salir de un bucle foreach?' - Bueno, solo debería haber un breakmáximo en mi opinión desde el bucle.
El Ronnoco
2

No estoy de acuerdo con tu jefe. Hay lugares adecuados para breaky continuepara ser utilizados. De hecho, la razón por la que se introdujeron las excepciones y el manejo de excepciones en los lenguajes de programación modernos es que no puede resolver todos los problemas utilizando solo structured techniques.

En una nota al margen , no quiero comenzar una discusión religiosa aquí, pero podría reestructurar su código para que sea aún más legible de esta manera:

while (primary_condition) {
    if (loop_count > 1000) || (time_exect > 3600) {
        break;
    } else if ( ( this->data != "undefined") && ( !this->skip ) ) {
       ... // where the real work of the loop happens
    }
}

En otra nota al margen

Personalmente, no me gusta el uso de ( flag == true )condicionales porque si la variable ya es booleana, está introduciendo una comparación adicional que debe suceder cuando el valor del booleano tiene la respuesta que desea, a menos que, por supuesto, esté seguro de que su compilador optimice esa comparación extra de distancia.

Zeke Hansell
fuente
He cuestionado esta pregunta durante años. Su forma ('if (flag) {...}') es mucho más conciso y quizás se vea más 'profesional' o 'experto'. Pero, ya sabes, a menudo significa que un lector / mantenedor tiene que interrumpirse brevemente para recordar lo que significa la construcción; y para estar seguro del sentido de la prueba. Actualmente, estoy usando 'if (flag == true) {...}' solo porque parece ser una mejor documentación. ¿Próximo mes? Quien sabe?
Pete Wilson
2
@Pete: el término no es conciso sino elegante . Puede hacer gofres todo lo que quiera, pero si le preocupa que el lector / mantenedor no entienda qué booleanes o qué significa la terminología concisa / elegante, entonces quizás sea mejor que contrate a algunos mantenedores más inteligentes ;-)
Zeke Hansell
@Pete, también mantengo mi declaración sobre el código generado. Está haciendo una comparación más al comparar una bandera con un valor constante antes de evaluar el valor booleano de la expresión. ¿Por qué hacerlo más difícil de lo que tiene que ser, la variable flag ya tiene el valor que deseas!
Zeke Hansell
+1 buen trabajo. Definitivamente mucho más elegante que el ejemplo anterior.
Evan Plaice
2

Estoy de acuerdo con tu jefe. Son malos porque producen métodos con alta complejidad ciclomática. Tales métodos son difíciles de leer y difíciles de probar. Afortunadamente hay una solución fácil. Extraiga el cuerpo del bucle en un método separado, donde "continuar" se convierte en "retorno". "Regresar" es mejor porque después de "regresar" se acabó, no hay preocupaciones sobre el estado local.

Para "break", extraiga el ciclo en un método separado, reemplazando "break" por "return".

Si los métodos extraídos requieren una gran cantidad de argumentos, es una indicación para extraer una clase, ya sea recopilarlos en un objeto de contexto.

Kevin Cline
fuente
0

Creo que solo es un problema cuando está anidado profundamente dentro de múltiples bucles. Es difícil saber a qué circuito te estás rompiendo. Puede ser difícil seguir una continuación también, pero creo que el verdadero dolor proviene de los descansos: la lógica puede ser difícil de seguir.

davidhaskins
fuente
2
En realidad, es fácil ver si sangra correctamente, tiene la semántica de esas declaraciones internalizadas y no tiene demasiado sueño.
@delnan - eso es un montón de suposiciones;)
davidhaskins
2
Sí, especialmente el último.
Michael K
Bueno, el # 1 es requerido para una programación seria de todos modos, el # 2 es de esperar de todos los llamados programadores, y el # 3 es bastante útil en general;)
Algunos lenguajes (solo scripts que conozco) son compatibles break 2; para otros, supongo que se usan banderas temporales de bool
Mikhail
0

Siempre que no se usen como goto disfrazados como en el siguiente ejemplo:

do
{
      if (foo)
      {
             /*** code ***/
             break;
      }

      if (bar)
      {
             /*** code ***/
             break;
      }
} while (0);

Estoy bien con ellos. (Ejemplo visto en el código de producción, meh)

SuperBloup
fuente
¿Recomendaría para casos como estos crear funciones?
Mikhail
Sí y si no es posible, un simple goto. Cuando veo un hacer, pienso en la repetición, no en el contexto para simular el salto.
SuperBloup
oh Estoy de acuerdo, sólo estaba preguntando cómo se haría de él :) ¡Los informáticos en varias ocasiones
Mikhail
0

No me gusta ninguno de estos estilos. Esto es lo que preferiría:

function verify(object)
{
    if not (object->value < 0) 
       and not(object->value > object->max_value)
       and not(object->name == "") 
       {
         do somethign important
       }
    else return false; //probably not necessary since this function doesn't even seem to be defined to return anything...?
}

Realmente no me gusta usar returnpara abortar una función. Se siente como un abuso de return.

Usar breaktambién no siempre es claro para leer.

Mejor aún podría ser:

notdone := primarycondition    
while (notDone)
{
    if (loop_count > 1000) or (time_exect > 3600)
    {
       notDone := false; 
    }
    else
    { 
        skipCurrentIteration := (this->data == "undefined") or (this->skip == true) 

        if not skipCurrentIteration
        {
           do something
        } 
        ...
    }
}

menos anidamiento y las condiciones complejas se refactorizan en variables (en un programa real tendrías que tener mejores nombres, obviamente ...)

(Todo el código anterior es pseudocódigo)

FrustratedWithFormsDesigner
fuente
11
¿Realmente preferirías 3 niveles de anidación sobre lo que escribí anteriormente?
Mikhail
@Mikhail: Sí, o asignaría el resultado de la condición a una variable. Me resulta mucho más fácil de entender que la lógica de breaky continue. Terminar anormalmente un ciclo simplemente se siente raro y no me gusta.
FrustratedWithFormsDesigner
1
Irónicamente, has leído mal mis condiciones. continuesignifica omitir la funcionalidad ir al siguiente ciclo; no "continuar con la ejecución"
Mikhail
@ Mikhail: Ah. No lo uso a menudo y cuando lo leo, me confunde el significado. Otra razón por la que no me gusta. : P Dame un minuto para actualizar ...
FrustratedWithFormsDesigner
77
Demasiada anidación destruye la legibilidad. Y, a veces, se evita el colapso / continuar introduce la necesidad de invertir la lógica en sus pruebas condicionales, lo que puede llevar a una mala interpretación de lo que su código está haciendo - Soy apenas Sayin'
Zeke Hansell
0

No. Es una forma de resolver un problema, y ​​hay otras formas de resolverlo.

Muchos lenguajes convencionales actuales (Java, .NET (C # + VB), PHP, escriba el suyo) usan "break" y "continue" para omitir los bucles. Ambas oraciones "estructuradas".

Sin ellos:

String myKey = "mars";

int i = 0; bool found = false;
while ((i < MyList.Count) && (not found)) {
  found = (MyList[i].key == myKey);
  i++;   
}
if (found)
  ShowMessage("Key is " + i.toString());
else
  ShowMessage("Not found.");

Con ellos:

String myKey = "mars";

for (i = 0; i < MyList.Count; i++) {
  if (MyList[i].key == myKey)
    break;
}
ShowMessage("Key is " + i.toString());

Tenga en cuenta que el código "break" y "continue" es más corto y generalmente convierte "while" en "for" o "foreach".

Ambos casos son una cuestión de estilo de codificación. Prefiero no usarlos , porque el estilo detallado me permite tener más control del código.

De hecho, trabajo en algunos proyectos, donde era obligatorio usar esas oraciones.

Algunos desarrolladores pueden pensar que no son necesariamente, sino hipotéticos, si tuviéramos que eliminarlos, también tenemos que eliminar "while" y "do while" ("repetir hasta", muchachos pascales) ;-)

Conclusión, incluso si prefiero no usarlos, creo que es una opción, no una mala práctica de programación.

umlcat
fuente
Lamento ser exigente, pero en su segundo ejemplo falta la salida para cuando no se encuentra la clave (por lo que, por supuesto, parece mucho más corta).
FrustratedWithFormsDesigner
2
sin mencionar que el primer ejemplo solo funciona si la clave es la última en la lista.
Mikhail
@FrustratedWithFormsDesigner EXACTAMENTE. Me puse a propósito para denotar por qué es preferible ese método ;-)
umlcat
sin embargo, tiene dos rutinas con semántica diferente; por lo tanto, no son lógicamente equivalentes.
bit-twiddler
2
Su segundo ejemplo tiene dos errores, uno sintáctico y otro lógico. 1. No se compilará porque el iterador no se declara fuera del alcance del bucle for (por lo tanto, no está disponible en la salida de la cadena). 2. Incluso si el iterador se declaró fuera del ciclo, si la clave no se encuentra en la colección, la salida de la cadena imprimirá la clave del último elemento de la lista.
Evan Plaice
0

No estoy en contra continuey, breaken principio, pero creo que son construcciones de muy bajo nivel que muy a menudo pueden ser reemplazadas por algo aún mejor.

Estoy usando C # como ejemplo aquí, considere el caso de querer iterar sobre una colección, pero solo queremos los elementos que cumplan con algún predicado, y no queremos hacer más de un máximo de 100 iteraciones.

for (var i = 0; i < collection.Count; i++)
{
    if (!Predicate(item)) continue;
    if (i >= 100) break; // at first I used a > here which is a bug. another good thing about the more declarative style!

    DoStuff(item);
}

Esto se ve RAZONABLEMENTE limpio. No es muy difícil de entender. Sin embargo, creo que ganaría mucho siendo más declarativo. Compárelo con lo siguiente:

foreach (var item in collection.Where(Predicate).Take(100))
    DoStuff(item);

Quizás las llamadas Where y Take ni siquiera deberían estar en este método. Tal vez este filtrado debería hacerse ANTES de que la colección se pase a este método. De todos modos, al alejarnos de las cosas de bajo nivel y centrarnos más en la lógica comercial real, se vuelve más claro en lo que REALMENTE estamos interesados. Se hace más fácil separar nuestro código en módulos cohesivos que se adhieren más a las buenas prácticas de diseño y así en.

Las cosas de bajo nivel seguirán existiendo en algunas partes del código, pero queremos ocultar esto tanto como sea posible, porque se necesita energía mental que podríamos utilizar para razonar sobre los problemas comerciales.

kai
fuente
-1

Code Complete tiene una buena sección sobre el uso gotoy múltiples retornos de la rutina o el ciclo.

En general no es una mala práctica. breako continuedecir exactamente lo que pasa después. Y estoy de acuerdo con esto.

Steve McConnell (autor de Code Complete) usa casi los mismos ejemplos que usted para mostrar las ventajas de usar varias gotodeclaraciones.

Sin embargo, el uso excesivo breako continuepodría conducir a un software complejo e imposible de mantener.

Grzegorz Gierlik
fuente