¿Por qué Java tiene un error de compilador de "declaración inalcanzable"?

83

A menudo encuentro que cuando depuro un programa es conveniente (aunque podría decirse que es una mala práctica) insertar una declaración de retorno dentro de un bloque de código. Podría intentar algo como esto en Java ...

class Test {
        public static void main(String args[]) {
                System.out.println("hello world");
                return;
                System.out.println("i think this line might cause a problem");
        }
}

por supuesto, esto produciría el error del compilador.

Test.java:7: declaración inalcanzable

Podría entender por qué una advertencia podría justificarse porque tener código sin usar es una mala práctica. Pero no entiendo por qué esto necesita generar un error.

¿Es esto solo Java tratando de ser una niñera, o hay una buena razón para convertirlo en un error del compilador?

Miguel
fuente
11
Java tampoco es del todo coherente con esto. Para algunos flujos de control que causan código muerto, pero Java no se queja. Para otros lo hace. Determinar qué código está muerto es un problema incuestionable. No sé por qué Java decidió comenzar algo que no pudo terminar.
Paul Draper

Respuestas:

63

Porque el código inalcanzable no tiene sentido para el compilador. Si bien hacer que el código sea significativo para las personas es fundamental y más difícil que hacerlo significativo para un compilador, el compilador es el consumidor esencial de código. Los diseñadores de Java adoptan el punto de vista de que el código que no es significativo para el compilador es un error. Su postura es que si tiene algún código inalcanzable, ha cometido un error que debe corregirse.

Aquí hay una pregunta similar: Código inalcanzable: ¿error o advertencia?, en el que el autor dice "Personalmente creo que debería ser un error: si el programador escribe un fragmento de código, siempre debería ser con la intención de ejecutarlo en algún escenario". Obviamente, los diseñadores de lenguajes de Java están de acuerdo.

Si el código inalcanzable debería impedir la compilación es una cuestión sobre la que nunca habrá consenso. Pero es por eso que los diseñadores de Java lo hicieron.


Varias personas en los comentarios señalan que hay muchas clases de código inalcanzable que Java no impide la compilación. Si entiendo correctamente las consecuencias de Gödel, ningún compilador puede capturar todas las clases de código inalcanzable.

Las pruebas unitarias no pueden detectar todos los errores. No usamos esto como un argumento en contra de su valor. Del mismo modo, un compilador no puede detectar todo el código problemático, pero aún así es valioso para evitar la compilación de código incorrecto cuando puede.

Los diseñadores del lenguaje Java consideran que el código inalcanzable es un error. Por lo tanto, evitar que se compile cuando sea posible es razonable.


(Antes de votar en contra: la pregunta no es si Java debería tener o no un error de compilador de declaración inalcanzable. La pregunta es por qué Java tiene un error de compilador de declaración inalcanzable. No me rechace solo porque crea que Java tomó la decisión de diseño incorrecta).

SamStephens
fuente
22
Obviamente, los diseñadores de lenguajes de Java están de acuerdo en que los "diseñadores de lenguajes de Java" son humanos y también propensos a cometer errores. Personalmente, creo que el otro lado de la discusión al que se refiere tiene argumentos mucho más sólidos.
Nikita Rybak
6
@Gabe: Porque no es inofensivo, es casi seguro que es un error. O ha puesto su código en el lugar equivocado o ha entendido mal que ha escrito su declaración de tal manera que no se puede acceder a parte de ella. Hacer que esto sea un error evita que se escriba un código incorrecto, y si desea algo en un lugar inaccesible en su código para que otros desarrolladores lo lean (la única audiencia para el código inalcanzable), use un comentario en su lugar.
SamStephens
10
SamStephens: Dado que los métodos no llamados y las variables no utilizadas son esencialmente todas formas de código inalcanzable. ¿Por qué permitir algunas formas y no otras? En particular, ¿por qué rechazar un mecanismo de depuración tan útil?
Gabe
5
¿No son también inalcanzables los comentarios? En mi opinión, el código inalcanzable es en realidad una forma de comentarios (esto es lo que estaba tratando de hacer antes, etc.). Pero considerando que los comentarios "reales" tampoco son "accesibles" ... tal vez también deberían plantear este error;)
Brady Moritz
10
El problema es que ellos (los diseñadores del lenguaje Java) son inconsistentes con esto. Si hay un análisis de flujo real, es trivial darse cuenta de que se garantiza que ambos return; System.out.println();y if(true){ return; } System.out.println();tienen el mismo comportamiento, pero uno es un error de compilación y el otro no. Entonces, cuando estoy depurando, y uso este método para "comentar" el código temporalmente, así es como lo hago. El problema de comentar el código es que tienes que encontrar el lugar apropiado para detener tu comentario, lo que puede llevar más tiempo que lanzarlo return;rápidamente.
LadyCailin
47

No hay una razón definitiva por la que no se deban permitir declaraciones inalcanzables; otros idiomas los permiten sin problemas. Para su necesidad específica, este es el truco habitual:

if (true) return;

Parece una tontería, cualquiera que lea el código adivinará que debe haber sido hecho deliberadamente, no un error descuidado de dejar inalcanzable el resto de declaraciones.

Java tiene un poco de soporte para "compilación condicional"

http://java.sun.com/docs/books/jls/third_edition/html/statements.html#14.21

if (false) { x=3; }

no da como resultado un error en tiempo de compilación. Un compilador optimizador puede darse cuenta de que la declaración x = 3; nunca se ejecutará y puede optar por omitir el código para esa declaración del archivo de clase generado, pero la declaración x = 3; no se considera "inalcanzable" en el sentido técnico especificado aquí.

El fundamento de este tratamiento diferente es permitir que los programadores definan "variables de marca" como:

static final boolean DEBUG = false;

y luego escriba un código como:

if (DEBUG) { x=3; }

La idea es que debería ser posible cambiar el valor de DEBUG de falso a verdadero o de verdadero a falso y luego compilar el código correctamente sin otros cambios en el texto del programa.

irrefutable
fuente
2
Aún no entiendo por qué te molestarías con el truco que mostraste. El código inalcanzable no tiene sentido para el compilador. Entonces, la única audiencia para ella son los desarrolladores. Utilice un comentario. Aunque supongo que si está agregando una devolución temporalmente, usar su solución alternativa podría ser un poco más rápido que simplemente ingresar una devolución y comentar el siguiente código.
SamStephens
8
@SamStephens Pero cada vez que quisieras cambiar tendrías que recordar todos los lugares donde tienes que comentar el código y donde tienes que hacer lo contrario. Tendría que leer todo el archivo, cambiando el código fuente, probablemente cometiendo algunos errores sutiles de vez en cuando en lugar de simplemente reemplazar "verdadero" por "falso" en un lugar. Creo que es mejor codificar la lógica de conmutación una vez y no tocarla a menos que sea necesario.
Robert
"Cualquiera que lea el código adivinará que debe haber sido hecho deliberadamente". Este es exactamente el problema en mi opinión, la gente no debería adivinar, debería entender. Recordar el código es comunicación con personas, no solo instrucciones para computadoras. Si es algo que no sea extremadamente temporal, debería estar usando la configuración en mi opinión. Mirando lo que está mostrando arriba, if (true) return;no indica POR QUÉ se está saltando la lógica, mientras que if (BEHAVIOURDISABLED) return;comunica la intención.
SamStephens
5
Este truco es realmente útil para depurar. A menudo agrego un retorno if (verdadero) para eliminar "el resto" de un método tratando de averiguar dónde está fallando. Hay otras formas, pero esto es limpio y rápido, pero no es algo que deba registrarse.
Bill K
18

Es Nanny. Siento que .Net lo hizo bien: genera una advertencia para el código inalcanzable, pero no un error. Es bueno estar advertido al respecto, pero no veo ninguna razón para evitar la compilación (especialmente durante las sesiones de depuración, donde es bueno lanzar un retorno para omitir algún código).

Brady Moritz
fuente
1
Java se diseñó mucho antes, cada byte cuenta en ese momento, los disquetes todavía eran de alta tecnología.
irrefutable
1
Para la devolución anticipada de depuración, se necesitan otros cinco segundos para comentar las líneas que siguen a su devolución anticipada. Un pequeño precio para jugar por los errores que se evitan al convertir el código inalcanzable en un error.
SamStephens
6
también es fácil para el compilador excluir el código inalcanzable. no hay razón para obligar al desarrollador a hacerlo.
Brady Moritz
2
@SamStephens no si ya tiene comentarios allí, ya que los comentarios no se acumulan, es un dolor innecesario solucionar esto con comentarios.
enrey
@SamStephens El código comentado no se refactoriza bien.
carenado
14

Acabo de notar esta pregunta y quería agregar mi $ .02 a esto.

En el caso de Java, esta no es realmente una opción. El error de "código inalcanzable" no proviene del hecho de que los desarrolladores de JVM pensaron en proteger a los desarrolladores de cualquier cosa, o estar más alerta, sino de los requisitos de la especificación de JVM.

Tanto el compilador de Java como la JVM utilizan lo que se denomina "mapas de pila", una información definida sobre todos los elementos de la pila, según lo asignado para el método actual. Se debe conocer el tipo de todas y cada una de las ranuras de la pila, de modo que una instrucción JVM no maltrate un elemento de un tipo por otro. Esto es muy importante para evitar que se utilice un valor numérico como puntero. Es posible, usando el ensamblaje de Java, intentar presionar / almacenar un número, pero luego abrir / cargar una referencia de objeto. Sin embargo, JVM rechazará este código durante la validación de la clase, es decir, cuando se crean mapas de pila y se prueba la coherencia.

Para verificar los mapas de la pila, la máquina virtual tiene que recorrer todas las rutas de código que existen en un método y asegurarse de que, independientemente de la ruta de código que se ejecute, los datos de la pila para cada instrucción concuerden con lo que ha empujado cualquier código anterior. / almacenado en la pila. Entonces, en el caso simple de:

Object a;
if (something) { a = new Object(); } else { a = new String(); }
System.out.println(a);

en la línea 3, JVM verificará que ambas ramas de 'if' solo se hayan almacenado en un (que es solo var # 0 local) algo que sea compatible con Object (ya que así es como el código de la línea 3 en adelante tratará la var # 0 local ).

Cuando el compilador llega a un código inalcanzable, no sabe muy bien qué estado podría estar la pila en ese punto, por lo que no puede verificar su estado. Ya no puede compilar el código en ese punto, ya que tampoco puede realizar un seguimiento de las variables locales, por lo que en lugar de dejar esta ambigüedad en el archivo de clase, produce un error fatal.

Por supuesto, una condición simple como if (1<2) lo engañará, pero no es realmente engañoso: le está dando una rama potencial que puede conducir al código, y al menos tanto el compilador como la VM pueden determinar cómo se pueden usar los elementos de la pila desde allí. en.

PD: No sé qué hace .NET en este caso, pero creo que también fallará la compilación. Normalmente, esto no será un problema para ningún compilador de código de máquina (C, C ++, Obj-C, etc.)

Pawel Veselov
fuente
¿Qué tan agresivo es el sistema de advertencia de código muerto de Java? ¿Podría expresarse como parte de la gramática (por ejemplo, { [flowingstatement;]* }=> flowingstatement, { [flowingstatement;]* stoppingstatement; }=> stoppingstatement, return=> stoppingstatement, if (condition) stoppingstatement; else stoppingstatement=> stoppingstatement;etc.?
supercat
No soy bueno con las gramáticas, pero creo que sí. Sin embargo, la "parada" puede ser local, por ejemplo, una declaración de "interrupción" dentro de un bucle. Además, el final de un método es una declaración de detención implícita para el nivel del método, si el método es nulo. 'lanzar' también será una declaración de detención explícita.
Pawel Veselov
¿Qué diría el estándar de Java sobre algo como void blah() { try {return;} catch (RuntimeError ex) { } DoSomethingElse(); } Java chirriaría que no había forma de que el trybloque pudiera salir realmente a través de una excepción, o pensaría que podría ocurrir cualquier tipo de excepción sin marcar dentro de cualquier trybloque?
supercat
La mejor manera de averiguarlo es intentar compilarlo. Pero el compilador permite incluso declaraciones try / catch vacías. Sin embargo, cualquier instrucción JVM teóricamente puede lanzar una excepción, por lo que el bloque try puede salir así. Pero no estoy seguro de qué importancia tiene esto para esta pregunta.
Pawel Veselov
Básicamente, la cuestión es si las únicas declaraciones "inalcanzables" prohibidas son aquellas que podrían identificarse como inalcanzables sobre la base del análisis sintáctico, sin tener que evaluar realmente ninguna expresión, o si el estándar podría prohibir declaraciones inalcanzables como if (constantThatEqualsFive < 3) {doSomething;};.
supercat
5

Uno de los objetivos de los compiladores es descartar clases de errores. Algún código inalcanzable está allí por accidente, es bueno que javac descarte esa clase de error en el momento de la compilación.

Por cada regla que detecta código erróneo, alguien querrá que el compilador lo acepte porque sabe lo que está haciendo. Esa es la penalización de la verificación del compilador, y lograr el equilibrio correcto es uno de los puntos más engañosos del diseño del lenguaje. Incluso con la verificación más estricta, todavía hay una cantidad infinita de programas que se pueden escribir, por lo que las cosas no pueden ir tan mal.

Ricky Clarkson
fuente
5

Si bien creo que este error del compilador es bueno, hay una manera de solucionarlo. Utilice una condición que sepa que será cierta:

public void myMethod(){

    someCodeHere();

    if(1 < 2) return; // compiler isn't smart enough to complain about this

    moreCodeHere();

}

El compilador no es lo suficientemente inteligente como para quejarse de eso.

Sean Patrick Floyd
fuente
6
La pregunta aquí es ¿por qué? El código inalcanzable no tiene sentido para el compilador. Entonces, la única audiencia para ella son los desarrolladores. Utilice un comentario.
SamStephens
3
¿Entonces recibo votos negativos porque estoy de acuerdo con la forma en que los chicos de Java diseñaron su compilación? Jeez ...
Sean Patrick Floyd
1
Obtienes votos negativos por no responder la pregunta real. Vea el comentario de SamStephens.
BoltClock
2
javac definitivamente puede inferir que 1<2es cierto, y el resto del método no tiene que incluirse en el código de bytes. las reglas de las "declaraciones inalcanzables" deben ser claras, fijas e independientes de la inteligencia de los compiladores.
irrefutable
1
@Sam con esa actitud, vas a tener que votar en contra del 50% de las mejores respuestas de este sitio (y no estoy diciendo que mi respuesta esté entre esas). Una gran parte de responder preguntas sobre SO, como en cualquier situación de consultoría de TI, es identificar la pregunta real (o preguntas secundarias relevantes) de una pregunta determinada.
Sean Patrick Floyd
0

Ciertamente, es bueno quejarse cuanto más estricto sea el compilador, mejor, en la medida en que le permita hacer lo que necesita. Por lo general, el pequeño precio a pagar es comentar el código, la ganancia es que cuando compila su código funciona. Un ejemplo general es Haskell sobre el cual la gente grita hasta que se dan cuenta de que su prueba / depuración es solo una prueba principal y una corta. Yo personalmente en Java casi no depuro mientras (de hecho, a propósito) no estoy atento.

Jérôme JEAN-CHARLES
fuente
0

Si la razón para permitir if (aBooleanVariable) return; someMoreCode;es permitir banderas, entonces el hecho de que if (true) return; someMoreCode;no genere un error de tiempo de compilación parece una inconsistencia en la política de generar la excepción CodeNotReachable, ya que el compilador 'sabe' quetrue no es una bandera (ni una variable).

Otras dos formas que pueden ser interesantes, pero que no se aplican a desactivar parte del código de un método, así como if (true) return :

Ahora, en lugar de decir, if (true) return;es posible que desee decir assert falsey agregar-ea OR -ea package OR -ea className argumentos a jvm. El buen punto es que esto permite cierta granularidad y requiere agregar un parámetro adicional a la invocación de jvm, por lo que no es necesario configurar un indicador DEBUG en el código, sino mediante un argumento agregado en tiempo de ejecución, lo cual es útil cuando el objetivo no es el máquina de desarrollo y recompilar y transferir código de bytes lleva tiempo.

También existe la System.exit(0)forma, pero esto podría ser una exageración, si lo pones en Java en una JSP, terminará el servidor.

Aparte de que Java es por diseño un lenguaje 'niñera', prefiero usar algo nativo como C / C ++ para tener más control.

MichaelS
fuente
Cambiar algo de una static finalvariable que se establece en un bloque de inicialización estático a una static finalvariable que se establece en su declaración no debería ser un cambio rotundo. Es completamente plausible que cuando una clase se escribe por primera vez, a veces no ha podido hacer algo (y no se sabe hasta el momento de la ejecución si podría hacerlo o no), pero las versiones posteriores de la clase podrían haberlo hecho. haz esa acción siempre. Cambiar myClass.canFooa una constante debería hacer if (myClass.canFoo) myClass.Foo(); else doSomethingElse();más eficiente, no romperla.
supercat