En Java, ¿para qué sirven las excepciones marcadas? [cerrado]

14

Las excepciones comprobadas de Java han tenido mala prensa a lo largo de los años. Una señal reveladora es que es, literalmente, el único idioma del mundo que los tiene (ni siquiera otros lenguajes JVM como Groovy y Scala). Prominentes bibliotecas de Java como Spring e Hibernate tampoco las usan.

Personalmente, he encontrado un uso para ellos ( en la lógica de negocios entre capas), pero de lo contrario, soy una excepción bastante verificada.

¿Hay otros usos que no conozco?

Brad Cupit
fuente
Todas las bibliotecas centrales de Java usan excepciones marcadas de manera bastante extensa.
Joonas Pulakka
3
@Joonas lo sé, ¡y es un dolor!
Brad Cupit el

Respuestas:

22

En primer lugar, como cualquier otro paradigma de programación, debe hacerlo correctamente para que funcione bien.

Para mí, la ventaja de las excepciones comprobadas es que los autores de la biblioteca de tiempo de ejecución de Java YA han decidido por mí qué problemas comunes podría razonablemente esperar que pueda manejar en el punto de llamada (en lugar de una captura de impresión de nivel superior) morir bloqueado) y considerar lo antes posible cómo manejar estos problemas.

Me gustan las excepciones marcadas porque hacen que mi código sea más robusto al obligarme a pensar en la recuperación de errores lo antes posible.

Para ser más precisos, para esto hace que mi código sea más robusto, ya que me obliga a considerar casos extraños de esquina muy temprano en el proceso en lugar de decir "Vaya, mi código no se maneja si el archivo aún no existe" basado en un error en la producción, que luego debe modificar su código para manejarlo. Agregar el manejo de errores al código existente puede ser una tarea no trivial, y por lo tanto costosa, cuando se realiza el mantenimiento en lugar de hacerlo desde el principio.

Puede ser que el archivo faltante sea algo fatal y deba causar que el programa se incendie, pero luego tomas esa decisión con

} catch (FileNotFoundException e) {
  throw new RuntimeException("Important file not present", e);
}

Esto también muestra un efecto secundario muy importante. Si envuelve una excepción, puede agregar una explicación que va en el seguimiento de la pila . Esto es extremadamente poderoso porque puede agregar información sobre, por ejemplo, el nombre del archivo que faltaba, o los parámetros pasados ​​a este método u otra información de diagnóstico, y esa información está presente directamente en el seguimiento de la pila, que con frecuencia es lo único que usted necesita. obtener cuando un programa se ha bloqueado.

La gente puede decir "podemos ejecutar esto en el depurador para reproducir", pero he descubierto que con frecuencia los errores de producción no pueden reproducirse más tarde, y no podemos ejecutar depuradores en producción, excepto en casos muy desagradables en los que esencialmente está en juego su trabajo.

Cuanta más información haya en su seguimiento de pila, mejor. Las excepciones marcadas me ayudan a poner esa información allí, y temprano.


EDITAR: Esto también se aplica a los diseñadores de bibliotecas. Una biblioteca que uso a diario contiene muchas, muchas excepciones comprobadas que podrían haber sido diseñadas mucho mejor haciendo que sea menos tedioso de usar.


fuente
+1. Los diseñadores de Spring hicieron un buen trabajo al traducir muchas de las excepciones de Hibernate en excepciones no controladas.
Fil
FYI: Frank Sommers mencionó en este hilo que el software tolerante a fallas a menudo se basa en el mismo mecanismo. Quizás pueda subdividir su respuesta en el caso recuperable y el caso fatal.
rwong
@rwong, ¿podría explicar con más detalle lo que tiene en mente?
11

Tienes dos buenas respuestas que explican en qué se han convertido las excepciones marcadas en la práctica. (+1 a ambos.) Pero también valdría la pena examinar para qué estaban destinados en teoría, porque la intención realmente vale la pena.

Las excepciones marcadas tienen la intención de hacer que el idioma sea más seguro. Considere un método simple como la multiplicación de enteros. Puede pensar que el tipo de resultado de este método sería un entero, pero, estrictamente hablando, el resultado es un entero o una excepción de desbordamiento. Considerando el resultado entero por sí mismo como el tipo de retorno del método no expresa el rango completo de la función.

Visto desde este punto de vista, no es estrictamente cierto decir que las excepciones marcadas no han llegado a otros idiomas. Simplemente no han tomado la forma que usaba Java. En las aplicaciones de Haskell, es común usar tipos de datos algebraicos para distinguir entre la finalización exitosa y la no exitosa de la función. Aunque esto no es una excepción, per se, la intención es muy similar a una excepción marcada; es una API diseñada para obligar al consumidor de API a manejar tanto a los exitosos como a los casos no exitosos, por ejemplo:

data Foo a =
     Success a
   | DidNotWorkBecauseOfA
   | DidNotWorkBecauseOfB

Esto le dice al programador que la función tiene dos resultados posibles adicionales además del éxito.

Craig Stuntz
fuente
+1 para las excepciones comprobadas sobre seguridad de tipo. Nunca había escuchado esa idea antes, pero tiene mucho sentido.
Ixrec
11

En mi opinión, las excepciones marcadas son una implementación defectuosa de lo que podría haber sido una idea bastante decente (en circunstancias completamente diferentes, pero en realidad ni siquiera es una buena idea en las circunstancias actuales).

La buena idea es que sería bueno que el compilador verifique que se detectarán todas las excepciones que un programa tiene el potencial de lanzar. La implementación defectuosa es que para hacer eso, Java te obliga a lidiar con la excepción directamente en cualquier clase que invoque cualquier cosa que se declare para lanzar una excepción marcada. Una de las ideas más básicas (y ventajas) del manejo de excepciones es que, en caso de error, permite que las capas intermedias de código que no tienen nada que ver con un error en particular ignoren por completo su existencia. Las excepciones marcadas (como se implementan en Java) no permiten eso, destruyendo así una ventaja importante (¿la principal?) Del manejo de excepciones para empezar.

En teoría, podrían haber hecho que las excepciones comprobadas fueran útiles al aplicarlas solo en todo el programa, es decir, al "construir" el programa, construir un árbol de todas las rutas de control en el programa. Se anotarían varios nodos en el árbol con 1) excepciones que pueden generar y 2) excepciones que pueden atrapar. Para asegurarse de que el programa fuera correcto, caminaría por el árbol y verificaría que cada excepción que se haya lanzado en cualquier nivel del árbol se haya capturado en algún nivel superior del árbol.

La razón por la que no hicieron eso (supongo, de todos modos) es que hacerlo sería insoportablemente difícil; de hecho, dudo que alguien haya escrito un sistema de construcción que pueda hacer eso para cualquier cosa que no sea extremadamente simple (bordeando el "juguete" ") lenguajes / sistemas. Para Java, cosas como la carga dinámica de clases lo hacen aún más difícil que en muchos otros casos. Peor aún, (a diferencia de algo como C) Java no está (al menos normalmente) compilado / vinculado estáticamente a un ejecutable. Eso significa que nada en el sistema realmente tiene la conciencia global para tratar de hacer este tipo de aplicación hasta que el usuario final realmente comience a cargar el programa para ejecutarlo.

Hacer cumplir en ese momento no es práctico por un par de razones. En primer lugar, incluso en el mejor de los casos, ampliaría los tiempos de inicio, que ya son una de las quejas más grandes y comunes sobre Java. En segundo lugar, para hacer mucho bien, realmente necesita detectar el problema lo suficientemente temprano como para que un programador lo solucione. Cuando el usuario está cargando el programa, es demasiado tarde para hacer mucho bien: sus únicas opciones en ese momento son ignorar el problema o negarse a cargar / ejecutar el programa, en caso de que pueda haber una excepción eso no fue atrapado.

Tal como están, las excepciones marcadas son peores que inútiles, pero los diseños alternativos para las excepciones marcadas son aún peores. Si bien la idea básica para las excepciones comprobadas parece ser buena, en realidad no es muy buena, y resulta ser particularmente inadecuada para Java. Para algo como C ++, que normalmente se compila / enlaza en un ejecutable completo sin nada parecido a la carga de clase de reflexión / dinámica, tendrías un poco más de posibilidades (aunque, francamente, incluso luego aplicarlas correctamente sin el mismo tipo de desorden) la causa actual en Java probablemente aún estaría más allá del estado actual de la técnica).

Jerry Coffin
fuente
Su primera declaración inicial sobre tener que manejar la excepción directamente ya es falsa; simplemente puede agregar una declaración throws, y esa declaración throws puede ser de tipo primario. Además, si realmente no cree que tiene que manejarlo, simplemente puede convertirlo en RuntimeException. Los IDE suelen ayudarlo con ambas tareas.
Maarten Bodewes
2
@owlstead: Gracias, probablemente debería haber sido más cuidadoso con la redacción allí. El punto no era tanto que realmente tuvieras que atrapar la excepción, sino que cada nivel intermedio de código debe ser consciente de cada excepción que pueda fluir a través de él. En cuanto al resto: tener una herramienta que agilice y facilite la desorganización de su código no cambia el hecho de que está desordenando el código.
Jerry Coffin
Las excepciones verificadas estaban destinadas a contingencias (resultados predecibles y recuperables distintos del éxito, a diferencia de los fracasos) , problemas impredecibles de bajo nivel o del sistema. FileNotFound o error al abrir una conexión JDBC se consideraron correctamente como contingencias. Desafortunadamente, los diseñadores de la biblioteca Java cometieron un error total y asignaron todas las demás excepciones de E / S y SQL a la clase 'marcada' también; a pesar de que estos son casi completamente irrecuperables. literatejava.com/exceptions/…
Thomas W
@owlstead: las excepciones marcadas solo deberían burbujear a través de capas intermedias de la pila de llamadas si se lanzan en los casos que el código de llamada intermedio espera . En mi humilde opinión, las excepciones comprobadas habrían sido buenas si las personas que llamaran tuvieran que atraparlas o indicar si se esperaban o no ; Las excepciones inesperadas deben envolverse automáticamente en un UnexpectedExceptiontipo derivado de RuntimeException. Tenga en cuenta que "tiros" y "no espera" no son contradictorios; si foolanza BozExceptiony llama bar, eso no significa que espera barlanzar BozException.
supercat
10

Son realmente buenos para los programadores molestos y fomentan la deglución de excepciones. La mitad del punto de excepciones es para que tenga una forma sensata predeterminada de manejar los errores y no tenga que pensar en propagar explícitamente las condiciones de error que no puede manejar. (El valor predeterminado correcto es que si nunca maneja el error en cualquier parte de su código, ha afirmado efectivamente que no puede suceder, y se queda con una salida sensata y un seguimiento de la pila si está equivocado). esta. Tienen muchas ganas de propagar errores explícitamente en C.

dsimcha
fuente
44
Fácil solución: throws FileNotFoundException. Solución difícil:catch (FileNotFoundException e) { throw RuntimeException(e); }
Thomas Eding,
5

Creo que es justo decir que las excepciones marcadas fueron un experimento un poco fallido. Donde se equivocaron es que rompieron las capas:

  • El Módulo A podría estar construido sobre el Módulo B, donde
  • El Módulo B podría estar construido sobre el Módulo C.
  • El Módulo C puede saber cómo identificar un error, y
  • El Módulo A podría saber cómo manejar el error.

La agradable separación de los interesados ​​está rota, porque ahora el conocimiento de los errores que podrían haberse limitado a A y C ahora debe dispersarse sobre B.

Hay una buena discusión sobre las excepciones marcadas en Wikipedia.

Y Bruce Eckel también tiene un buen artículo al respecto. Aquí hay una cita:

[E] cuantas más reglas aleatorias se apilen en el programador, reglas que no tienen nada que ver con resolver el problema en cuestión, más lento puede producir el programador. Y esto no parece ser un factor lineal, sino exponencial

Creo que para el caso de C ++, si todos los métodos tienen la throwscláusula de especificación, entonces puede tener algunas buenas optimizaciones. Sin embargo, no creo que Java pueda tener esas ventajas porque RuntimeExceptionsno están marcadas. Un JIT moderno debería poder optimizar el código de todos modos.

Una buena característica de AspectJ es el suavizado de excepciones: puede convertir las excepciones marcadas en excepciones no marcadas, específicamente para mejorar la estratificación.

Quizás sea irónico que Java haya pasado por todos estos problemas, cuando podría haberlo verificado null, lo que podría haber sido mucho más valioso .

Macneil
fuente
jaja, nunca pensé en comprobar que algo nulo era una decisión que no tomaron ... Pero finalmente entendí que NO es un problema innato después de trabajar en Objective-C, que permite que los nulos tengan métodos llamados.
Dan Rosenstark el
3
la verificación nula es un dolor mucho mayor, SIEMPRE tiene que verificar, además no explica la razón, lo que hace una excepción bien nombrada.
5

Me encantan las excepciones.

Sin embargo, estoy constantemente desconcertado por su mal uso.

  1. No los use para manejar el flujo. No desea un código que intente hacer algo y utilice una excepción como desencadenante para hacer otra cosa, porque las excepciones son objetos que deben crearse y usar memoria, etc., etc. simplemente porque no puede molestarse en escribir algunos tipo de control if () antes de hacer su llamada Eso es simplemente vago y le da un mal nombre a la gloriosa Excepción.

  2. No te los tragues. Nunca. Bajo cualquier circunstancia. Como mínimo absoluto, debe pegar algo en su archivo de registro para indicar que ocurrió una excepción. Intentar rastrear tu camino a través del código que traga excepciones es una pesadilla. De nuevo, ¡maldiga a los tragadores de excepciones por desacreditar a la poderosa Excepción!

  3. Trate las excepciones con su sombrero OO puesto. Crea tus propios objetos de excepción con jerarquías significativas. Capa su lógica de negocios y sus excepciones de la mano. Solía ​​encontrar que si comenzaba en una nueva área de un sistema, encontraría la necesidad de subclasificar Exception dentro de la media hora de la codificación, así que en estos días empiezo escribiendo las clases de Exception.

  4. Si está escribiendo fragmentos de código 'frameworky', asegúrese de que todas sus interfaces iniciales estén escritas para lanzar sus propias Excepciones nuevas cuando sea apropiado ... y asegúrese de que sus codificadores tengan algún código de muestra alrededor del lugar de qué hacer cuando su código lanza una IOException pero la interfaz en la que están escribiendo quiere lanzar una 'ReportingApplicationException'.

Una vez que haya aprendido cómo hacer que las Excepciones funcionen para usted, nunca mirará hacia atrás.

DanW
fuente
2
+1 para buenas instrucciones. Además, cuando corresponda, use initCauseo inicialice la excepción con la excepción que la causó. Ejemplo: tiene una utilidad de E / S que solo necesita una excepción si falla la escritura. Sin embargo, hay varias razones por las que una escritura podría fallar (no se pudo obtener acceso, error de escritura, etc.) Lanza tu excepción personalizada con la causa para que no se trague el problema original.
Michael K
3
No quiero ser grosero, pero ¿qué tiene esto que ver con las excepciones COMPROBADAS? :)
palto
Podría reescribirse para escribir su propia jerarquía de excepciones marcadas .
Maarten Bodewes
4

Las excepciones marcadas también están en ADA.

(Advertencia, esta publicación contiene creencias muy arraigadas que puede encontrar confrontado).

Los programadores no les gustan y se quejan, o escriben código de deglución de excepción.

Existen excepciones comprobadas porque las cosas no solo pueden dejar de funcionar, también puede hacer un análisis de modo / efectos de falla y determinar esto de antemano.

Las lecturas de archivos pueden fallar. Las llamadas RPC pueden fallar. La red IO puede fallar. Los datos pueden tener un formato incorrecto cuando se analizan.

El "camino feliz" para el código es fácil.

Conocí a un chico en la Universidad que podía escribir un gran código de "camino feliz". Ninguno de los casos límite funcionó jamás. En estos días hace Python para una compañía de código abierto. Dijo Nuff.

Si no desea manejar las excepciones marcadas, lo que realmente está diciendo es

While I'm writing this code, I don't want to consider obvious failure modes.

The User will just have to like the program crashing or doing weird things.

But that's okay with me because 

I'm so much more important than the people who will have to use the software

in the real, messy, error-prone world.

After all, I write the code once, you use it all day long.

Por lo tanto, los programadores no les agradarán a las excepciones marcadas, porque significa más trabajo.

Por supuesto, otras personas podrían haber querido hacer ese trabajo.

Es posible que hayan querido la respuesta correcta incluso si el servidor de archivos falló / la memoria USB se apaga.

Es una creencia extraña en la comunidad de programación que deberías estar usando un lenguaje de programación que te haga la vida más fácil, que disfrutes, cuando tu trabajo es escribir software. Tu trabajo es resolver el problema de alguien, no permitiéndote participar en la improvisación programática de Jazz.

Si eres un programador aficionado (no estás programando por dinero), no dudes en programar en C # o en algún otro lenguaje sin excepciones comprobadas. Diablos, corta al intermediario y programa en Logo. Puedes dibujar bonitos patrones en el suelo con la tortuga.

Tim Williscroft
fuente
66
Bonita diatriba que tienes allí.
Adam Lear
2
+1 "Ninguno de los casos límite funcionó. En estos días, hace Python para una compañía de código abierto. Nuff dijo". Clásico
NimChimpsky
1
@palto: Java te obliga a considerar la ruta no fácil. Esa es la gran diferencia. Para C #, podría decir: "La excepción está documentada" y lávese las manos. Pero los programadores son propensos a errores. Se olvidan de las cosas. Cuando escribo código, quiero que me recuerden al 100% cualquier grieta que un compilador pueda informarme.
Thomas Eding
2
@ThomasEding Java me obliga a manejar la excepción donde se llama al método. Es muy raro que realmente quiera hacer algo al respecto en el código de llamada y, por lo general, quiero hacer saltar la excepción a la capa de manejo de errores de mi aplicación. Luego tengo que remediarlo ya sea envolviendo la excepción en una excepción de tiempo de ejecución o tengo que crear mi propia excepción. Los programadores perezosos tienen una tercera opción: simplemente ignórenlo porque no me importa. De esa manera, nadie sabe que ocurrió el error y el software tiene errores realmente extraños. Ese tercero no ocurre con excepciones no marcadas.
palto
3
@ThomasEding Hacer eso cambiará la interfaz de todo lo anterior, lo que revela detalles de implementación innecesariamente para cada capa de la aplicación. Solo puede hacerlo si la excepción es algo de lo que desea recuperarse y tiene sentido en la interfaz. No quiero agregar una IOException a mi método getPeople porque puede faltar algún archivo de configuración. Simplemente no tiene sentido.
palto
4

Las excepciones marcadas deberían haber alentado a las personas a manejar errores cerca de su fuente. Cuanto más lejos de la fuente, más funciones han terminado en el medio de la ejecución, y es más probable que el sistema haya entrado en un estado inconsistente. Además, es más probable que atrape la excepción incorrecta por accidente.

Lamentablemente, la gente ha tendido a ignorar las excepciones o agregar especificaciones de lanzamiento cada vez mayores. Ambos pierden el sentido de lo que se supone que alentarán las excepciones marcadas. Son como transformar el código de procedimiento para usar muchas funciones de Getter y Setter que supuestamente lo hacen OO.

Esencialmente, las excepciones marcadas intentan probar un cierto grado de corrección en su código. Es más o menos la misma idea que la escritura estática, ya que intenta probar que ciertas cosas son correctas incluso antes de que se ejecute el código. El problema es que toda esa prueba adicional requiere esfuerzo y ¿vale la pena el esfuerzo?

Winston Ewert
fuente
2
"Ignorar excepciones" a menudo es correcto: muchas veces la mejor respuesta a una excepción es dejar que la aplicación se bloquee. Para obtener una base teórica para este punto de vista, lea la Construcción de software orientado a objetos de Bertrand Meyer . Él muestra que si las excepciones se usan correctamente (para infracciones de contrato), existen esencialmente dos respuestas correctas: (1) dejar que la aplicación se bloquee o (2) solucionar el problema que causó la excepción y reiniciar el proceso. Lea a Meyer para más detalles; Es difícil resumir en un comentario.
Craig Stuntz el
1
@ Craig Stuntz, al ignorar las excepciones, me refería a tragarlas.
Winston Ewert
Ah, eso es diferente. Estoy de acuerdo en que no debes tragarlos.
Craig Stuntz el
"Ignorar las excepciones es a menudo correcto, muchas veces la mejor respuesta a una excepción es dejar que la aplicación se bloquee". Realmente depende de la aplicación. Si tiene una excepción no detectada en una aplicación web, lo peor que puede suceder es que su cliente informe que ve un mensaje de error en una página web y que puede implementar una solución de error en unos minutos. Si tiene una excepción mientras aterriza un avión, será mejor que lo maneje e intente recuperarse.
Giorgio
@ Jorge, muy cierto. Aunque me pregunto si en el caso de un avión o similar, si la mejor manera de recuperarse es reiniciar el sistema.
Winston Ewert