¿Es una buena práctica usar una rotura etiquetada en Java?

81

Estoy mirando un código antiguo de 2001 y encontré esta declaración:

   else {
     do {
       int c = XMLDocumentFragmentScannerImpl.this.scanContent();
       if (c == 60) {
         XMLDocumentFragmentScannerImpl.this.fEntityScanner.scanChar();
         XMLDocumentFragmentScannerImpl.this.setScannerState(1);
         break label913;
       }

Nunca había visto esto antes y descubrí roturas etiquetadas aquí:

http://docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html

¿No funciona esto esencialmente goto? ¿Es incluso una buena práctica usarlo? Me hace sentir incómodo.

avgvstvs
fuente
2
donde esta label913definido?
Nikolay Kuznetsov
1
En este caso, unas 200 líneas después al final de una declaración de cambio bastante grande.
avgvstvs
continuar y el nombre de la etiqueta hará que el código procese esa parte del código donde se marcó el bloque con una etiqueta
user_CC
5
En este caso, entonces, ¡el problema es el tamaño de la instrucción de cambio!
Cyrille Ka
8
Esto parece un código generado (un analizador, en realidad). Hay muchos trucos que se usan con el código generado que no se usarían en código escrito a mano (principalmente porque facilitan la generación).
parsifal

Respuestas:

119

No, no es como un goto, ya que no puede "ir" a otra parte del flujo de control.

Desde la página que vinculó:

La sentencia break termina la sentencia etiquetada; no transfiere el flujo de control a la etiqueta. El flujo de control se transfiere a la declaración inmediatamente después de la declaración etiquetada (terminada).

Esto significa que solo puede romper los bucles que se estén ejecutando actualmente.

Considere este ejemplo:

first:
for( int i = 0; i < 10; i++) {
  second:
  for(int j = 0; j < 5; j ++ )
  {
    break xxx;
  }
}

third:
for( int a = 0; a < 10; a++) {

}

Puede reemplazar xxxcon firsto second(para romper el bucle externo o interno), ya que ambos bucles se están ejecutando, cuando presiona la breakinstrucción, pero reemplazar xxxcon thirdno se compilará.

Thomas
fuente
1
@user_CC En realidad, no, todavía no puedes salirte del flujo de control. Una continuación etiquetada solo iniciará la siguiente iteración del ciclo etiquetado y solo funcionará mientras ese ciclo ya se esté ejecutando.
Thomas
3
Entonces break first;, si lo usara , ¿el compilador volvería a subir a la línea inmediatamente después first:y ejecutaría esos bucles nuevamente?
Ungeheuer
15
@JohnnyCoder no, break first;sería romper (fin) el bucle de etiquetado first, es decir, que continuará con third. Por otro lado continue first;, finalizaría la iteración actual de firsty se rompería secondcon eso y continuaría con el siguiente valor de iin first.
Thomas
1
@TheTechExpertGuy bueno, en realidad no. Es solo similar a gotomás o menos en el mismo sentido que una if-elsedeclaración. Los Goto son mucho más "flexibles" como en "ir a cualquier parte", por ejemplo, a una posición antes del bucle (de hecho, así es como los lenguajes antiguos solían implementar los bucles: "si no se cumple la condición, ve al inicio del cuerpo del bucle (de nuevo)") . He tenido que lidiar con ellos recientemente y créanme: gotoes un verdadero dolor de cabeza, mientras que las roturas etiquetadas no lo son.
Thomas
1
@TheTechExpertGuy de nada :) - Ya que eres un principiante, me siento inclinado a darte el consejo una vez más: no lo uses gotoen absoluto, incluso si C ++ lo admite. Casi siempre hay una mejor manera de resolver las cosas que te ahorrarán muchos dolores de cabeza más adelante. La tentación puede llegar ... ¡resiste!
Thomas
16

No es tan terrible gotoporque solo envía el control al final de la declaración etiquetada (generalmente una construcción de bucle). Lo que lo hace gotodesagradable es que es una rama arbitraria a cualquier lugar, incluidas las etiquetas que se encuentran más arriba en la fuente del método, de modo que puede tener un comportamiento de bucle propio. La funcionalidad de rotura de etiquetas en Java no permite ese tipo de locura porque el control solo avanza.

Solo lo usé una vez hace unos 12 años, en una situación en la que necesitaba romper con los bucles anidados, la alternativa más estructurada habría hecho pruebas más complicadas en los bucles. No recomendaría usarlo mucho, pero no lo marcaría como un olor automático a código incorrecto.

Nathan Hughes
fuente
6
@Boann: no quiero sugerir que goto sea del todo malvado en todos los contextos. es solo una de esas cosas como la aritmética de punteros, la sobrecarga de operadores y la administración explícita de memoria que los creadores de Java decidieron que no valía la pena. Y después de leer programas COBOL que parecían tener más del 90% de errores, no me entristece que lo hayan dejado fuera de Java.
Nathan Hughes
1
Oh, tenías razón la primera vez @NathanHughes, goto es bastante malvado. No en todas las situaciones, por supuesto, pero sí, no me lo pierdo.
Percepción
2
goto puede ser más "útil" que las etiquetas, @Boann, pero eso es completamente superado por su costo de mantenimiento. las etiquetas no son la varita mágica que son los gotos, y eso es algo bueno.
DavidS
9

Siempre es posible reemplazarlo breakcon un nuevo método.

Considere la verificación de código para cualquier elemento común en dos listas:

List list1, list2;

boolean foundCommonElement = false;
for (Object el : list1) {
    if (list2.contains(el)) {
        foundCommonElement = true;
        break;
    }
}

Puedes reescribirlo así:

boolean haveCommonElement(List list1, List list2) {
    for (Object el : list1) {
        if (list2.contains(el)) {
            return true;
        }
    }
    return false;
}

Por supuesto, para buscar elementos comunes entre dos listas, es mejor usar el list1.retainAll(new HashSet<>(list2))método para hacerlo O(n)con O(n)memoria adicional, o ordenar ambas listas en O(n * log n)y luego encontrar elementos comunes en O(n).

Logicray
fuente
Este es casi siempre el enfoque correcto en mi opinión. gotoes (potencialmente) dañino porque podría enviarle a muchos lugares; no es en absoluto evidente lo que se está logrando; La creación de una función de ayuda le da límites claros al siguiente tipo en cuanto a lo que está tratando de hacer y dónde lo va a hacer, y le da la oportunidad de nombrar esa parte de la funcionalidad. Mucho más claro de esta manera.
ethanbustad
@ethanbustad Pero esto se trata de etiquetado break, no goto. Claro, la refactorización que se muestra aquí es una buena idea en ambos casos. Pero etiquetado breakno es tan poco moderado y propenso al código espaguetizado como gotoes.
underscore_d
Este ejemplo es terrible, la rotura no funciona si, la etiqueta aquí es simplemente inútil.
Diracnote
8

Antes de leer el resto de esta respuesta, lea Ir a la declaración considerada dañina . Si no quiere leerlo en su totalidad, aquí está lo que considero el punto clave:

El uso desenfrenado de la declaración go to tiene una consecuencia inmediata de que resulta terriblemente difícil encontrar un conjunto significativo de coordenadas en las que describir el progreso del proceso.

O para reformular, el problema con gotoes que el programa puede llegar en medio de un bloque de código, sin que el programador comprenda el estado del programa en ese punto. Las construcciones estándar orientadas a bloques están diseñadas para delinear claramente las transiciones de estado, un etiquetado breakestá destinado a llevar el programa a un estado conocido específico (fuera del bloque etiquetado que lo contiene).

En un programa imperativo del mundo real, el estado no está claramente delimitado por límites de bloque, por lo que es cuestionable si la etiqueta breakes una buena idea. Si el bloque cambia de estado que es visible desde fuera del bloque, y hay varios puntos para salir del bloque, entonces una etiqueta breakes equivalente a la primitiva goto. La única diferencia es que, en lugar de la posibilidad de aterrizar en medio de un bloque con un estado indeterminado, comienzas un nuevo bloque con un estado indeterminado.

Entonces, en general, consideraría una etiqueta breakcomo peligrosa. En mi opinión, es una señal de que el bloque debe convertirse en una función, con acceso limitado al alcance adjunto.

Sin embargo, este código de ejemplo fue claramente el producto de un generador de analizador (el OP comentó que era el código fuente de Xerces). Y los generadores de analizadores sintácticos (o generadores de código en general) a menudo se toman libertades con el código que generan, porque tienen un conocimiento perfecto del estado y los humanos no necesitan entenderlo.

parsifal
fuente
No estoy de acuerdo breakes bastante útil en varias ocasiones. Por ejemplo, cuando busca algún valor que puede provenir de varias fuentes ordenadas. Estás probando uno por uno, y cuando se encuentra el primero, tú break. Es un código más elegante que if / else if / else if / else if. (Al menos son menos líneas). Mover todo al contexto de un método puede no ser siempre práctico. Otro caso es, si está rompiendo condicionalmente desde unos pocos niveles anidados. En resumen, si no te gusta break, no te gusta returnningún otro lugar excepto el final de un método.
Ondra Žižka
Para el contexto; "Ir a la declaración considerada dañina" se escribió cuando cosas como doy whileno existían y todos usaban en if(.. ) gototodas partes. Tenía la intención de alentar a los diseñadores de idiomas a agregar soporte para cosas como doy while. Lamentablemente, una vez que el tren comenzó a rodar, se convirtió en un tren de bombo publicitario impulsado por personas ignorantes que pusieron cosas como breakbucles de "solo se ejecuta una vez" y lanzaron excepciones a quién sabe qué, para todos los casos (raros) que gotoes más limpio y menos dañino.
Brendan
1

Esto no es como una gotodeclaración en la que se hace retroceder el control de flujo. Una etiqueta simplemente le muestra a usted (el programador) dónde ha ocurrido la ruptura. Además, el control de flujo se transfiere a la siguiente declaración después de break.

Sobre su uso, personalmente creo que no es de gran utilidad, ya que una vez que comienzas a escribir un código que vale miles de líneas, se vuelve insignificante. Pero de nuevo, dependería del caso de uso.

nómada
fuente
for(int i = 0; i < 1; i++) { do_something(); if(I_want_to_go_backwards) { i = 0; continue; } }.
Brendan
1

Una forma más limpia de expresar la intención podría ser, al menos en mi opinión, poner el fragmento de código que contiene el bucle en un método separado y simplemente return partir de él.

Por ejemplo, cambie esto:

someLabel:
for (int j = 0; j < 5; j++)
{
    // do something
    if ...
        break someLabel;
}

dentro de esto:

private void Foo() {
    for (int j = 0; j < 5; j++)
    {
        // do something
        if ...
            return;
    }
}

Esto también es más idiomático para los desarrolladores que dominan otros lenguajes que podrían funcionar con su código en el futuro (o en el futuro ).

Marek
fuente