¿Esto justifica las declaraciones de goto?

15

Me encontré con esta pregunta hace un segundo, y estoy sacando parte del material de allí: ¿Hay un nombre para la construcción 'break n'?

Esto parece ser una forma innecesariamente compleja para que las personas tengan que instruir al programa para que salga de un bucle doble anidado:

for (i = 0; i < 10; i++) {
    bool broken = false;
    for (j = 10; j > 0; j--) {
        if (j == i) {
            broken = true;
            break;
        }
    }
    if (broken)
        break;
}

Sé que a los libros de texto les gusta decir que las declaraciones de goto son el demonio, y no me gustan en absoluto, pero ¿justifica esto una excepción a la regla?

Estoy buscando respuestas que traten con n-nested para bucles.

NOTA: Ya sea que responda sí, no o en algún punto intermedio, las respuestas completamente cerradas no son bienvenidas. Especialmente si la respuesta es no, proporcione una razón buena y legítima (que de todos modos no está demasiado lejos de las regulaciones de Stack Exchange).

Panzercrisis
fuente
2
Si puede escribir la condición del bucle interno como for (j = 10; j > 0 && !broken; j--)entonces, no necesitaría la break;declaración. Si mueve la declaración de brokena antes de que comience el ciclo externo, entonces si eso podría escribirse como for (i = 0; i < 10 && !broken; i++)entonces, creo que no break; es necesario.
FrustratedWithFormsDesigner
8
Dado el ejemplo es de C # - la referencia de C # en goto: goto (Referencia de C #) - "La instrucción goto también es útil para salir de bucles profundamente anidados".
¡Dios mío, nunca supe que C # tenía una declaración Goto! Las cosas que aprendes en StackExchange. (No recuerdo la última vez que deseé una capacidad de declaración goto, parece que nunca aparece).
John Wu
66
En mi humilde opinión, realmente depende de su "religión de programación" y la fase lunar actual. Para mí (y para la mayoría de las personas que me rodean) está bien usar un goto para salir de los nidos profundos si el bucle es bastante pequeño, y la alternativa sería una verificación superflua de una variable bool introducida solo para que se rompa la condición del bucle. Esto es más aún cuando quieres salir a la mitad del cuerpo, o cuando después del bucle más interno hay un código que también debería verificar el bool.
PlasmaHH
1
@JohnWu gotose requiere realmente en un interruptor C # para pasar a otro caso después de ejecutar cualquier código dentro del primer caso. Sin embargo, ese es el único caso que he usado goto.
Dan Lyons

Respuestas:

33

La aparente necesidad de una declaración ir surge de que usted elija expresiones condicionales pobres para los bucles .

Usted declara que desea que el ciclo externo continúe tanto tiempo i < 10y que el más interno continúe tanto tiempo j > 0.

Pero en realidad eso no es lo que quería , simplemente no le dijo a los bucles la condición real que quería que evaluaran, luego desea resolverlo usando break o goto.

Si le dice a los bucles sus verdaderas intenciones para comenzar , entonces no necesita descansos ni goto.

bool alsoThis = true;
for (i = 0; i < 10 && alsoThis; i++)
{    
    for (j = 10; j > 0 && alsoThis; j--)
    {
        if (j == i)
        {
            alsoThis = false;
        }
    }
}
Tulains Córdova
fuente
Vea mis comentarios sobre las otras respuestas. El código original se imprime ial final del bucle externo, por lo que hacer un cortocircuito en el incremento es importante para que el código se vea 100% equivalente. No digo que nada en su respuesta sea incorrecto, solo quería señalar que romper el ciclo de inmediato era un aspecto importante del código.
pswg
1
@pswg No veo ninguna impresión de código ial final del bucle externo en la pregunta de OP.
Tulains Córdova
OP hizo referencia al código de mi pregunta aquí , pero omitió esa parte. No te rechacé, por cierto. La respuesta es completamente correcta con respecto a elegir correctamente las condiciones del bucle.
pswg
3
@pswg Supongo que si realmente realmente realmente deseas justificar un goto, puedes crear un problema que necesite uno, por otro lado, he estado programando profesionalmente durante dos décadas y no tengo un solo problema del mundo real que necesite uno.
Tulains Córdova
1
El propósito del código no era proporcionar un caso de uso válido para goto(estoy seguro de que hay mejores), incluso si generó una pregunta que parece usarlo como tal, sino para ilustrar la acción básica de los break(n)idiomas que no No lo soportes.
pswg
34

En este caso, podría refactorizar el código en una rutina separada y luego solo returndesde el bucle interno. Eso negaría la necesidad de salir del bucle externo:

bool isBroken() {
    for (i = 0; i < 10; i++)
        for (j = 10; j > 0; j--)
            if (j == i) return true;

    return false;
}
Roger
fuente
2
Es decir, sí, lo que muestra es una forma innecesariamente compleja de romper con bucles anidados, pero no, hay una forma de refactorizar que no hace que un goto sea más atractivo.
Roger
2
Solo una nota al margen: en el código original se imprime idespués del final del bucle externo. Entonces, para reflejar realmente ese ies el valor importante, aquí el return true;y return false;ambos deberían ser return i;.
pswg
+1, estaba a punto de publicar esta respuesta, y creo que esta debería ser la respuesta aceptada. No puedo creer que la respuesta aceptada haya sido aceptada porque ese método solo funciona para un subconjunto específico de algoritmos de bucle anidados, y no necesariamente hace que el código sea mucho más limpio. El método en esta respuesta funciona en general.
Ben Lee
6

No, no creo que gotosea ​​necesario. Si lo escribes así:

bool broken = false;
saved_i = 0;
for (i = 0; i < 10 && !broken; i++)
{
    broken = false;
    for (j = 10; j > 0 && !broken; j--)
    {
        if (j == i)
        {
            broken = true;
            saved_i = i;
        }
    }
}

Tener &&!brokenambos bucles te permite salir de ambos cuando i==j.

Para mí, este enfoque es preferible. Las condiciones del bucle son extremadamente claro: i<10 && !broken.

Una versión de trabajo se puede ver aquí: http://rextester.com/KFMH48414

Código:

public static void main(String args[])
{
    int i=0;
    int j=0;
    boolean broken = false;
    for (i = 0; i < 10 && !broken; i++)
    {
        broken = false;
        for (j = 10; j > 0 && !broken; j--)
        {
            if (j == i)
            {
                broken = true;
                System.out.println("loop breaks when i==j=="+i);
            }
        }
    }
    System.out.println(i);
    System.out.println(j);
}

Salida:

el bucle se rompe cuando i == j == 1
2
0 0
FrustratedWithFormsDesigner
fuente
¿Por qué te utiliza brokenen su primer ejemplo en absoluto ? Solo usa break en el bucle interno. Además, si absolutamente debe usar broken, ¿por qué declararlo fuera de los bucles? Solo declaralo dentro del ibucle. En cuanto al whilebucle, no lo uses. Sinceramente, creo que logra ser aún menos legible que la versión goto.
luiscubal
@luiscubal: brokense usa una variable llamada porque no me gusta usar la breakdeclaración (excepto en caso de cambio, pero eso es todo). Se declara fuera del bucle externo en caso de que desee romper TODOS los bucles cuando i==j. Tienes razón, en general, brokensolo necesita ser declarado antes del ciclo más externo que se romperá.
FrustratedWithFormsDesigner
En su actualización, i==2al final del ciclo. La condición de éxito también debe provocar un cortocircuito en el incremento si debe ser 100% equivalente a a break 2.
pswg
2
Personalmente prefiero broken = (j == i)el if y, si el lenguaje lo permite, poner la declaración rota dentro de la inicialización del bucle externo. Aunque es difícil decir estas cosas para este ejemplo específico, ya que todo el ciclo es un no-op (no devuelve nada y no tiene efectos secundarios) y si hace algo, posiblemente lo haga dentro del if (aunque todavía me gusta broken = (j ==i); if broken { something(); }más personalmente)
Martijn
@Martijn En mi código original, se imprime idespués del final del bucle externo. En otras palabras, el único pensamiento realmente importante es cuándo sale exactamente del bucle externo.
pswg
3

La respuesta corta es "depende". Recomiendo elegir con respecto a la legibilidad y la comprensibilidad de su código. La forma de ir a Goto podría ser más legible que modificar la condición, o viceversa. También puede tener en cuenta el principio de menor sorpresa, la guía utilizada en la tienda / proyecto y la coherencia de la base del código. Por ejemplo, goto para dejar el interruptor o para el manejo de errores es una práctica común en la base del código del kernel de Linux. También tenga en cuenta que algunos programadores pueden tener problemas para leer su código (ya sea planteado con el mantra "goto is evil", o simplemente no lo aprendieron porque su maestro lo cree) y algunos de ellos podrían incluso "juzgarlo".

En términos generales, un goto que va más allá en la misma función a menudo es aceptable, especialmente si el salto no es demasiado largo. Su caso de uso de dejar un bucle anidado es uno de los ejemplos conocidos de goto "no tan malvado".

FabienAndre
fuente
2

En su caso, goto es una mejora suficiente que parece tentador, pero no es tanto una mejora que vale la pena romper la regla contra el uso de ellos en su base de código.

Piense en su futuro revisor: tendrá que pensar: "¿Es esta persona un mal programador o de hecho es un caso en el que valió la pena el goto? Permítanme tomar 5 minutos para decidir esto por mí mismo o investigarlo en el Internet." ¿Por qué demonios le harías eso a tu futuro codificador cuando no puedes usar goto por completo?

En general, esta mentalidad se aplica a todo el código "malo" e incluso lo define: si funciona ahora, y es bueno en este caso, pero crea un esfuerzo para verificar que sea correcto, lo que en esta era un goto siempre hará, entonces no hazlo.

Dicho esto, sí, usted produjo uno de los casos un poco más espinosos. Puedes:

  • use estructuras de control más complejas, como una bandera booleana, para romper ambos bucles
  • ponga su bucle interno dentro de su propia rutina (probablemente ideal)
  • ponga ambos bucles en una rutina y regrese en lugar de romper

Es muy difícil para mí crear un caso en el que un doble bucle no sea tan cohesivo que de todos modos no debería ser su propia rutina.

Por cierto, la mejor discusión sobre esto que he visto es el Código completo de Steve McConnell . Si te gusta pensar críticamente en estos problemas, te recomiendo leer, incluso de principio a fin a pesar de su inmensidad.

djechlin
fuente
1
Nuestros programadores de trabajo deben leer y comprender el código. No solo código simplista a menos que esa sea la naturaleza de su base de código. Hay, y siempre habrá excepciones a la generalidad (no regla) contra el uso de gotos debido al costo comprendido. Cada vez que el beneficio supera el costo es cuando se debe usar goto; como es el caso para todas las decisiones de codificación en ingeniería de software.
dietbuddha
1
@dietbuddha hay un costo en violar las convenciones de codificación de software aceptadas que deben tenerse en cuenta adecuadamente. Hay un costo en aumentar la diversidad de las estructuras de control utilizadas en un programa, incluso si esta estructura de control puede ser adecuada para una situación. Hay un costo en romper el análisis estático de código. Si todo esto todavía vale la pena, ¿quién soy yo para discutir?
djechlin
1

TLD; DR: No en específico, sí en general

Para el ejemplo específico que usó, diría que no por las mismas razones que muchos otros han señalado. Puede refactorizar fácilmente el código en una forma equivalentemente buena que elimine la necesidad de goto.

En general, la proscripción contra gotoes una generalidad basada en la naturaleza entendida gotoy el costo de usarla. Parte de la ingeniería de software es tomar decisiones de implementación. La justificación de esas decisiones se produce comparando el costo con los beneficios y cada vez que los beneficios superan el costo, la implementación está justificada. La controversia surge debido a diferentes valoraciones, tanto de costo como de beneficio.

El punto es que siempre habrá excepciones donde a gotoestá justificado, pero solo para algunos debido a diferentes valoraciones. La pena es que muchos ingenieros simplemente excluyen las técnicas en ignorancia intencional porque lo leen en Internet en lugar de tomarse el tiempo para entender por qué.

dietbuddha
fuente
¿Me puede recomendar algún artículo sobre la declaración goto que explique más el costo?
Thys
-1

No creo que este caso justifique una excepción, ya que esto se puede escribir como:

def outerLoop(){
    for (i = 0; i < 10; i++) {
        if( broken?(i) )
          return; // broken
    }
}

def broken?(i){
   for (j = 10; j > 0; j--) {
        if (j == i) {
            return true; // broken
        }
   }
   return false; // not broken
}
ptyx
fuente