patrones:excepciones
Diferencias
Muestra las diferencias entre dos versiones de la página.
— | patrones:excepciones [2023/04/07 21:26] (actual) – creado - editor externo 127.0.0.1 | ||
---|---|---|---|
Línea 1: | Línea 1: | ||
+ | ====== Tratamiento de Excepciones ====== | ||
+ | El tratamiento de excepciones en Java demasiadas veces se realiza de forma errónea. Veamos ahora la forma de hacer un correcto tratamiento de excepciones. | ||
+ | ===== Excepciones en Java ===== | ||
+ | Empecemos recordando los dos tipos de excepciones que hay en Java(( Hay realmente mas como <javadoc jdk7 > | ||
+ | * [[# | ||
+ | * [[# | ||
+ | |||
+ | <uml> | ||
+ | note " | ||
+ | note " | ||
+ | class Exception | ||
+ | class RuntimeException | ||
+ | |||
+ | Exception . N1 | ||
+ | |||
+ | Exception <|-- RuntimeException | ||
+ | |||
+ | RuntimeException . N2 | ||
+ | </ | ||
+ | ==== Excepciones Checked ==== | ||
+ | Las excepciones Checked son aquellas que deben declararse en el método mediante la palabra '' | ||
+ | |||
+ | Son excepciones Checked cualquier objeto de la clase <javadoc jdk7> | ||
+ | |||
+ | <code java 1> | ||
+ | public class Matematicas { | ||
+ | public double dividir(double a, double b) throws Exception { | ||
+ | if (b == 0) { | ||
+ | throw new Exception(" | ||
+ | } | ||
+ | |||
+ | return a / b; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | El método '' | ||
+ | |||
+ | Ahora cualquiera que llame al método '' | ||
+ | * Mediante un '' | ||
+ | public class Main { | ||
+ | public static void main(String[] args) { | ||
+ | Matematicas matematicas=new Matematicas(); | ||
+ | try { | ||
+ | double c=matematicas.dividir(-1.6, | ||
+ | } catch (Exception ex) { | ||
+ | //Tratar la excepción | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | * Declarándo a su vez en el método que puede lanzar dicha excepción:< | ||
+ | public class Main { | ||
+ | public static void main(String[] args) throws Exception { | ||
+ | Matematicas matematicas=new Matematicas(); | ||
+ | | ||
+ | double c=matematicas.dividir(-1.6, | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Excepciones Unchecked ==== | ||
+ | Las excepciones Unchecked son aquellas que **no** se declaran en el método | ||
+ | |||
+ | Son excepciones Unchecked | ||
+ | |||
+ | <code java 1> | ||
+ | public class Matematicas { | ||
+ | public double dividir(double a, double b) { | ||
+ | if (b == 0) { | ||
+ | throw new RuntimeException(" | ||
+ | } | ||
+ | |||
+ | return a / b; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | El método '' | ||
+ | |||
+ | <code java 1> | ||
+ | public class Main { | ||
+ | public static void main(String[] args) { | ||
+ | Matematicas matematicas=new Matematicas(); | ||
+ | |||
+ | double c=matematicas.dividir(-1.6, | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | Como la excepción que lanza '' | ||
+ | |||
+ | ===== Cuando usar excepciones Checked o Unchecked y que hacer con ellas ===== | ||
+ | Una vez visto como Java trata las excepciones de ambos tipos pasemos a explicar cuando debemos usar las de un tipo u otro. | ||
+ | |||
+ | ==== Checked Exceptions ==== | ||
+ | Las excepciones Checked son condiciones excepcionales del flujo del programa pero que no son debido a un error del propio programa. Es decir si se lanza una excepción checked | ||
+ | |||
+ | Por ejemplo si tenemos una función que borra ficheros y al ir a borrarlo , dicho fichero ya no existe, deberemos lanzar una < | ||
+ | |||
+ | De ahí que al ser usa situación legitima del programa pero poco probable los creadores de Java han decidido que es necesario declararla en el método y que sea obligatorio tratarla. Las Checked Exceptions no tienen //nada de malo// y son muy útiles para realizar correctamente los programas. | ||
+ | |||
+ | <note important> | ||
+ | La Checked Exceptions forman parte del flujo lógico de nuestro programa y al programar debemos tenerlas en cuenta y tratarlas adecuadamente no viéndolas como un problema del que debemos deshacernos. | ||
+ | </ | ||
+ | |||
+ | <note tip> | ||
+ | Aunque hemos puesto el mismo ejemplo del método '' | ||
+ | </ | ||
+ | ==== Unchecked Exceptions ==== | ||
+ | Este tipo de error es mucho mas sencillo de entender. Una excepción unchecked es aquella que se lanza cuando hay un error en el programa. | ||
+ | |||
+ | Por ejemplo en el caso anterior de la división por cero, es un error de programación que alguien nos pase como dividendo un cero, en ese caso lanzaremos un < | ||
+ | |||
+ | Al ser las unchecked Exceptions errores en el programa , poco podemos hacer por arreglarlas. Por ello no es obligatorio tratarlas con un '' | ||
+ | |||
+ | <note important> | ||
+ | Las unchecked exceptions no debemos tratarlas ya que no forma parte de la lógica de la aplicación y no debemos hacer nada con ella, simplemente debemos preocuparnos en el mensaje que acabará llegando al usuario. En una aplicación Web es tan sencillo como tener páginas de error personalizadas. | ||
+ | </ | ||
+ | |||
+ | ===== El problema con las excepciones ===== | ||
+ | Hasta ahora hemos visto los dos tipos de excepciones que hay , como las trata el compilador de Java y cuando usar una u otra. Sin embargo hay un problema con las excepciones que hace que no se traten de forma correcta. | ||
+ | |||
+ | El problema de las excepciones es que en muchas APIs de Java se lanzan checked cuando deberían ser unchecked. | ||
+ | |||
+ | Por ejemplo, en el API de JDBC al ejecutar una consulta mediante < | ||
+ | |||
+ | <note important> | ||
+ | El verdadero problema de Java es que muchas API lanzan Checked exceptions cuando deberían haber sido uncheked exceptions. | ||
+ | </ | ||
+ | |||
+ | Como acabamos de decir, eso nos lleva a los programadores a capturar dichas excepciones y tener que tratarlas. Pero ¿como tratamos dichas excepciones Checked si realmente son errores de programa y por lo tanto excepciones unchecked? Pues solo hay una forma de hacerlo, es transformando las excepciones Checked en unchecked. | ||
+ | |||
+ | <code java 1> | ||
+ | try { | ||
+ | double c=matematicas.dividir(-1.6, | ||
+ | } catch (Exception ex) { | ||
+ | throw new RuntimeException(" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Vemos en la línea 4 como lanzamos una nueva excepción del tipo unchecked pero //dentro// contiene la excepción original que se ha producido ya que se le pasa como argumento. Con ésto hemos solucionado el problema. Pero vuelvo a repetir ésta construcción solo debe hacerse con aquellas excepciones checked que consideramos que deberían haber sido unchecked. | ||
+ | |||
+ | <note warning> | ||
+ | Esta construcción tan sencilla que consiste en que las excepciones checked **,pero que deberían haber sido uncheked,** trasformarlas en excepciones uncheked nos soluciona prácticamente todos nuestros problemas. | ||
+ | |||
+ | <code java 1> | ||
+ | try { | ||
+ | double c=matematicas.dividir(-1.6, | ||
+ | } catch (Exception ex) { | ||
+ | throw new RuntimeException(" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | </ | ||
+ | |||
+ | ==== Mas problemas ==== | ||
+ | Veamos ahora otro problema de las excepciones.Hay excepciones unchecked , que según su significado, | ||
+ | |||
+ | La excepción <javadoc jee6> | ||
+ | |||
+ | El problema que se genera con ésto es que la excepción <javadoc jee6> | ||
+ | |||
+ | <note important> | ||
+ | Otro problema de Java es que hay APIs que lanzan UnChecked Exceptions cuando deberían haber sido Cheked Exceptions. | ||
+ | </ | ||
+ | |||
+ | La solución a este problema consiste simplemente en leerse la documentación para ver cuando se lanza una excepción unchecked , que debería haber sido Checked, para capturarla y hacer el correcto tratamiento con ella. | ||
+ | |||
+ | Mas información sobre la excepción <javadoc jee6> | ||
+ | ===== Formas erróneas de tratar las excepciones ===== | ||
+ | Veamos ahora una serie de errores que se cometen al tratar las excepciones. | ||
+ | |||
+ | ==== Perder la excepción original ==== | ||
+ | Quizás otro origen del desconocimiento de las excepciones es que hasta Java 1.4 no se podía encapsular una excepción dentro de otra. Lo podemos ver en la clase '' | ||
+ | * [[http:// | ||
+ | * [[http:// | ||
+ | |||
+ | Esto último nos ha llevado a soluciones erróneas como la siguiente: | ||
+ | <code java 1> | ||
+ | try { | ||
+ | double c=matematicas.dividir(-1.6, | ||
+ | } catch (Exception ex) { | ||
+ | throw new RuntimeException(" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | En la línea 4 vemos como se crea una '' | ||
+ | |||
+ | Por ejemplo la excepción <javadoc jdk7> | ||
+ | |||
+ | ==== Crear una excepción inutil ==== | ||
+ | Otro segundo error es crear una nueva excepción **generica** (( No me refiero a excepciones concretas que tienen significado específico como '' | ||
+ | |||
+ | <code java 1> | ||
+ | public class MiExcepcion extends RuntimeException { | ||
+ | public MiExcepcion (String msg) { | ||
+ | super(msg); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Creando una excepción personalizada que no aporta nada. | ||
+ | |||
+ | <code java 1> | ||
+ | try { | ||
+ | double c=matematicas.dividir(-1.6, | ||
+ | } catch (Exception ex) { | ||
+ | throw new MiExcepcion (" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | En la línea 4 se lanza la nueva excepción '' | ||
+ | |||
+ | ==== Pasar el problema hacia arriba ==== | ||
+ | La siguiente forma errónea de tratar la excepciones es simplemente hacer que el método superior a su vez declare que lanza esas excepciones checked pero que deberían haber sido uncheked. Con ésto lo único que hacemos es expandir el problema hacia arriba sin solucionarlo. | ||
+ | |||
+ | <code java 1> | ||
+ | public class Main { | ||
+ | public static void main(String[] args) throws Exception { | ||
+ | Matematicas matematicas=new Matematicas(); | ||
+ | | ||
+ | double c=matematicas.dividir(-1.6, | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | En la línea 2 vemos ahora como el método '' | ||
+ | |||
+ | ==== Ignorarla ==== | ||
+ | Este es el peor error que podemos cometer al tratar una excepción.Consiste simplemente en ignorar la excepción y que continúe la ejecución del programa. Esto es tan peligroso ya que si se ha producido algún fallo en el programa lo más seguro es hacer que se detenga cuanto antes y no seguir haciendo operaciones con datos que quizás sean erróneos. | ||
+ | |||
+ | <code java 1> | ||
+ | try { | ||
+ | double c=matematicas.dividir(-1.6, | ||
+ | } catch (Exception ex) { | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | |||
+ | |||
+ | Vemos en la línea 4 como no se realiza ninguna acción al capturar la excepción, por lo tanto la ejecución seguirá como si no hubiera pasado nada. | ||
+ | |||
+ | ==== Log ==== | ||
+ | Acabamos de ver en el apartado anterior que nunca (( Siempre hay // | ||
+ | |||
+ | <code java 1> | ||
+ | try { | ||
+ | double c=matematicas.dividir(-1.6, | ||
+ | } catch (RuntimeException ex) { | ||
+ | Logger.getLogger(Main.class.getName()).log(Level.SEVERE, | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | En la línea 4 guardamos en el log la excepción pero el programa sigue ejecutándose. Como ya hemos comentado no debería seguir la ejecución del programa puesto que los datos pueden haber quedado en un estado erróneo. | ||
+ | |||
+ | La idea de guardar en el log la excepción es buena por si misma. Sin embargo no suele ser necesario guardarla explícitamente ya que en muchas ocasiones ya se guarda automáticamente. Por ejemplo en aplicaciones Web , el propio contenedor al detectar ((Siempre que no la detengamos antes )) una excepción guardará en el log la excepción que se ha producido. | ||
+ | |||
+ | ==== Consola | ||
+ | Otra forma erronea de guardar las excepciones consiste en mostrar la traza del error por la consola. | ||
+ | |||
+ | <code java 1> | ||
+ | try { | ||
+ | double c=matematicas.dividir(-1.6, | ||
+ | } catch (RuntimeException ex) { | ||
+ | ex.printStackTrace(); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | En la línea 4 mostramos por la consola la traza de la excepción pero el programa sigue ejecutándose. Como ya se ha dicho no debería seguir la ejecución del programa puesto que los datos pueden haber quedado en un estado erróneo. | ||
+ | |||
+ | En aplicaciones Web o aplicaciones de Ventanas , esta opción tiene menos sentido aun que la opción del log, ya que guardando la traza en un fichero de log está mucho más accesible para poder averiguar el origen del problema que mostrándolo por consola ya que la consola la puede cerrar el usuario y perder toda la traza o simplemente que no tengamos acceso a la consola. | ||
+ | |||
+ | ==== Imprimirla ==== | ||
+ | Por último pero no por ello menos usada está la // | ||
+ | |||
+ | <code java 1> | ||
+ | try { | ||
+ | double c=matematicas.dividir(-1.6, | ||
+ | } catch (RuntimeException ex) { | ||
+ | System.out.println(" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Esta forma tiene los mismos problemas del anterior de imprimirla por consola pero ésta es aun peor, hemos perdido toda la información de la excepción. | ||
+ | |||
+ | ===== Mejoras en el tratamiento de excepciones ===== | ||
+ | Ya hemos visto como es la forma correcta de tratar las excepciones Checked que realmente deberían ser unchecked: | ||
+ | |||
+ | <code java 1> | ||
+ | try { | ||
+ | double c=matematicas.dividir(-1.6, | ||
+ | } catch (Exception ex) { | ||
+ | throw new RuntimeException(" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Este código tiene unas pegas al respecto de las trazas que se generara por pantalla (( o en el sistema de log )) cuando se produce una excepción. | ||
+ | |||
+ | Si ejecutamos el código se produce la siguiente traza: | ||
+ | <code 1> | ||
+ | Exception in thread " | ||
+ | at ejemplo.Main.main(Main.java: | ||
+ | Caused by: java.lang.Exception: | ||
+ | at ejemplo.Matematicas.dividir(Matematicas.java: | ||
+ | at ejemplo.Main.main(Main.java: | ||
+ | </ | ||
+ | |||
+ | Como podemos ver en la traza, se muestran dos excepciones, | ||
+ | |||
+ | El problema viene si el método '' | ||
+ | |||
+ | Veamos el código modificado de '' | ||
+ | |||
+ | <code java 1> | ||
+ | public class Matematicas { | ||
+ | public double dividir(double a, double b) throws Exception { | ||
+ | if (a<0) { | ||
+ | throw new RuntimeException(" | ||
+ | } | ||
+ | if (b<0) { | ||
+ | throw new RuntimeException(" | ||
+ | } | ||
+ | | ||
+ | if (b == 0) { | ||
+ | throw new Exception(" | ||
+ | } | ||
+ | |||
+ | return a / b; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Vemos en las líneas de la 3 a la 8 como ahora también se lanza un '' | ||
+ | |||
+ | Si volvemos ahora a ejecutar el programa se genera la siguiente traza de error: | ||
+ | |||
+ | <code 1> | ||
+ | Exception in thread " | ||
+ | at ejemplo.Main.main(Main.java: | ||
+ | Caused by: java.lang.RuntimeException: | ||
+ | at ejemplo.Matematicas.dividir(Matematicas.java: | ||
+ | at ejemplo.Main.main(Main.java: | ||
+ | </ | ||
+ | |||
+ | Ahora la traza si que ha quedado un poco mal.Hemos encapsulado una '' | ||
+ | |||
+ | La solución a ésto, es bastante sencilla.Si nos llega una '' | ||
+ | |||
+ | <code java 1> | ||
+ | public class Main { | ||
+ | public static void main(String[] args) { | ||
+ | Matematicas matematicas=new Matematicas(); | ||
+ | try { | ||
+ | double c=matematicas.dividir(-1.6, | ||
+ | } catch (RuntimeException ex) { | ||
+ | throw ex; | ||
+ | } catch (Exception ex) { | ||
+ | throw new RuntimeException(" | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | En la línea 6 ahora capturamos la '' | ||
+ | |||
+ | Si ahora volvemos a ejecutar el código, la traza resultante es: | ||
+ | |||
+ | <code 1> | ||
+ | Exception in thread " | ||
+ | at ejemplo.Matematicas.dividir(Matematicas.java: | ||
+ | at ejemplo.Main.main(Main.java: | ||
+ | </ | ||
+ | |||
+ | Es decir que ahora la traza para una '' | ||
+ | |||
+ | <note tip> | ||
+ | Por los ejemplos puede parecer un poco exagerado querer que no se encapsulen las excepciones pero pongo aquí un ejemplo de excepciones en Hibernate: | ||
+ | |||
+ | Exception in thread " | ||
+ | at org.hibernate.persister.internal.PersisterFactoryImpl.create(PersisterFactoryImpl.java: | ||
+ | at org.hibernate.persister.internal.PersisterFactoryImpl.createEntityPersister(PersisterFactoryImpl.java: | ||
+ | at org.hibernate.internal.SessionFactoryImpl.< | ||
+ | at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java: | ||
+ | at curshibernatet03e01.CursHibernateT03E01.main(CursHibernateT03E01.java: | ||
+ | Caused by: org.hibernate.HibernateException: | ||
+ | at org.hibernate.tuple.entity.EntityTuplizerFactory.constructTuplizer(EntityTuplizerFactory.java: | ||
+ | at org.hibernate.tuple.entity.EntityTuplizerFactory.constructDefaultTuplizer(EntityTuplizerFactory.java: | ||
+ | at org.hibernate.tuple.entity.EntityMetamodel.< | ||
+ | at org.hibernate.persister.entity.AbstractEntityPersister.< | ||
+ | at org.hibernate.persister.entity.SingleTableEntityPersister.< | ||
+ | at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) | ||
+ | at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java: | ||
+ | at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java: | ||
+ | at java.lang.reflect.Constructor.newInstance(Constructor.java: | ||
+ | at org.hibernate.persister.internal.PersisterFactoryImpl.create(PersisterFactoryImpl.java: | ||
+ | ... 4 more | ||
+ | Caused by: java.lang.reflect.InvocationTargetException | ||
+ | at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) | ||
+ | at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java: | ||
+ | at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java: | ||
+ | at java.lang.reflect.Constructor.newInstance(Constructor.java: | ||
+ | at org.hibernate.tuple.entity.EntityTuplizerFactory.constructTuplizer(EntityTuplizerFactory.java: | ||
+ | ... 13 more | ||
+ | Caused by: org.hibernate.PropertyNotFoundException: | ||
+ | at org.hibernate.property.BasicPropertyAccessor.createGetter(BasicPropertyAccessor.java: | ||
+ | at org.hibernate.property.BasicPropertyAccessor.getGetter(BasicPropertyAccessor.java: | ||
+ | at org.hibernate.mapping.Property.getGetter(Property.java: | ||
+ | at org.hibernate.tuple.entity.PojoEntityTuplizer.buildPropertyGetter(PojoEntityTuplizer.java: | ||
+ | at org.hibernate.tuple.entity.AbstractEntityTuplizer.< | ||
+ | at org.hibernate.tuple.entity.PojoEntityTuplizer.< | ||
+ | ... 18 more | ||
+ | | ||
+ | Realmente la información mas útil la encontramos justamente en la primera excepción que se produce. Sin embargo está justamente al final de toda la traza: | ||
+ | |||
+ | PropertyNotFoundException: | ||
+ | | ||
+ | Así que si no se hubiera encapsulado la excepción se vería en la primera línea en vez de ir a buscarla al final de la traza. | ||
+ | |||
+ | </ | ||
+ | ==== La solución definitiva ==== | ||
+ | La solución definitiva es que las excepciones checked tenga también una traza //limpia// como las unchecked. Antes hemos comentado que es imposible pero hay un truco en Java que permite hacerlo, no voy a explicar como funciona el truco simplemente voy a explicar como hacerlo. | ||
+ | |||
+ | Se debe crear la siguiente clase: | ||
+ | <code java 1> | ||
+ | public | ||
+ | |||
+ | public static RuntimeException lanzar( Exception ex){ | ||
+ | RelanzadorExcepciones.< | ||
+ | |||
+ | throw new AssertionError(" | ||
+ | } | ||
+ | |||
+ | private static <T extends Exception> | ||
+ | throw (T) toThrow; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Ahora nuestro código '' | ||
+ | <code java 1> | ||
+ | public class Main { | ||
+ | public static void main(String[] args) { | ||
+ | Matematicas matematicas=new Matematicas(); | ||
+ | try { | ||
+ | double c=matematicas.dividir(3, | ||
+ | } catch (Exception ex) { | ||
+ | RelanzadorExcepciones.lanzar(ex); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Vemos como ahora para relanzar la excepción en la línea 22 usamos el nuevo método que hemos creado '' | ||
+ | |||
+ | Si ejecutamos ahora la aplicación y vemos la traza, queda de la siguiente forma: | ||
+ | < | ||
+ | Exception in thread " | ||
+ | at ejemplo.Matematicas.dividir(Matematicas.java: | ||
+ | at ejemplo.Main.main(Main.java: | ||
+ | </ | ||
+ | |||
+ | ¡¡Es decir que únicamente se ve la excepción checked sin que quede encapsulada por nadie.!! | ||
+ | |||
+ | Por fin con este ultimo truco hemos conseguido simplificar la traza con todas las excepciones. | ||
+ | |||
+ | ===== Referencias ===== | ||
+ | * [[http:// | ||
+ | * [[http:// | ||
+ | * [[http:// | ||
+ | * [[http:// | ||
+ | * [[http:// | ||
+ | * [[http:// | ||
+ | * [[http:// |