English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
La razón por la que las excepciones son una herramienta de depuración poderosa es que responden a los siguientes tres problemas:
1¿Qué salió mal?63;
2¿Dónde salió mal?63;
3¿Por qué salió mal?63;
En el uso efectivo de excepciones, el tipo de excepción responde a '¿qué se lanzó?', la traza de pila de excepciones responde a '¿dónde se lanzó?', y la información de la excepción responde a '¿por qué se lanzó'. Si tus excepciones no responden a todas estas preguntas, es posible que no las estés utilizando adecuadamente.
Tres principios pueden ayudarte a utilizar al máximo las excepciones en el proceso de depuración, y estos principios son:
1、Específica y clara
2、Lanzamiento anticipado
3、Captura diferida
Para ilustrar estos tres principios de manejo efectivo de excepciones, este artículo discute el gestor de finanzas personales ficticio JCheckbook, que se utiliza para registrar y rastrear actividades bancarias como depósitos y retiros, y la emisión de recibos.
Específica y clara
Java define una estructura jerárquica de clases de excepciones, que comienza con Throwable, se expande a Error y Exception, y Exception se expande a RuntimeException. Como se muestra en la figura1Mostrado.
Estas cuatro clases son genéricas y no proporcionan mucha información de error, aunque la instanciación de estas clases es gramaticalmente válida (como: new Throwable()), es mejor tratarlas como clases base virtuales y usar sus subclases más especializadas. Java ya ha proporcionado una gran cantidad de subclases de excepciones, y si es necesario ser más específico, también puedes definir tus propias clases de excepciones.
Por ejemplo, el paquete java.io define la subclase Exception IOException, que es más especializada, como FileNotFoundException, EOFException y ObjectStreamException, que son subclases de IOException. Cada una describe un tipo específico de I/Error O: Se trata de la pérdida de archivos, el final incorrecto de los archivos de excepción y la secuencia de objetos serializados incorrecta. Cuanto más específica sea la excepción, mejor podrá nuestro programa responder a la pregunta de '¿qué salió mal?'
Al capturar una excepción, es importante ser lo más claro posible. Por ejemplo: JCheckbook puede manejar FileNotFoundException preguntando nuevamente al usuario por el nombre del archivo, y para EOFException, puede continuar ejecutándose según la información leída antes de que se lance la excepción. Si se lanza ObjectStreamException, el programa debe informar al usuario de que el archivo está dañado y debe usar un archivo de respaldo u otro archivo.
Java facilita la captura explícita de excepciones, ya que podemos definir múltiples bloques catch para el mismo bloque try, lo que permite manejar cada tipo de excepción de manera adecuada.
File prefsFile = new File(prefsFilename); try{ readPreferences(prefsFile); } catch (FileNotFoundException e){ // alert the user that the specified file // does not exist } catch (EOFException e){ // alert the user that the end of the file // was reached } catch (ObjectStreamException e){ // alert the user that the file is corrupted } catch (IOException e){ // alert the user that some other I/O // error occurred }
JCheckbook utiliza múltiples bloques catch para proporcionar información clara sobre las excepciones capturadas al usuario. Por ejemplo: si se captura FileNotFoundException, puede sugerirle al usuario que especifique otro archivo. En algunos casos, el trabajo adicional de codificación que implica múltiples bloques catch puede ser una carga innecesaria, pero en este ejemplo, el código adicional realmente ayuda al programa a proporcionar una respuesta más amigable para el usuario.
Además de las excepciones manejadas por los tres primeros bloques catch, el último bloque catch proporciona información de error más generalizada cuando se lanza IOException. De esta manera, el programa puede proporcionar información específica en la medida de lo posible, pero también tiene la capacidad de manejar otras excepciones inesperadas.
A veces, los desarrolladores capturan excepciones generalizadas y muestran el nombre de la clase de excepción o imprimen información de pila para lograr "específico". ¡No hagan eso! Ver la excepción java.io.EOFException o la información de pila solo causará dolores de cabeza al usuario en lugar de proporcionar ayuda. Debería capturar la excepción específica y proporcionar información precisa al usuario en un lenguaje "humano". Sin embargo, la información de pila de excepciones se puede imprimir en el archivo de registro. Recuerda, las excepciones e información de pila se utilizan para ayudar a los desarrolladores y no a los usuarios.
Finalmente, debe notarse que JCheckbook no captura excepciones en readPreferences(), sino que deja que la captura y el manejo de excepciones se realicen en la capa de interfaz de usuario, de modo que se pueda notificar al usuario mediante cuadro de diálogo u otra forma. Esto se llama "captura diferida", y se abordará en el texto siguiente.
lanzar tempranamente
La información de la pila de excepciones proporciona el orden exacto de las llamadas a métodos que causaron la excepción, incluyendo el nombre de la clase, el nombre del método, el nombre del archivo de código e incluso el número de línea, para localizar con precisión el lugar en el que se produjo la excepción.
java.lang.NullPointerException en java.io.FileInputStream.open(Método nativo) en java.io.FileInputStream.<init>(FileInputStream.java:103) en jcheckbook.JCheckbook.readPreferences(JCheckbook.java:225) en jcheckbook.JCheckbook.startup(JCheckbook.java:116) at jcheckbook.JCheckbook.<init>(JCheckbook.java:27) at jcheckbook.JCheckbook.main(JCheckbook.java:318)
Lo anterior muestra el caso en el que el método open() de la clase FileInputStream lanza NullPointerException. Sin embargo, tenga en cuenta que FileInputStream.close() es parte de la biblioteca de clases estándar de Java y es probable que el problema cause esta excepción esté en nuestro código en lugar de en la API de Java. Por lo tanto, el problema probablemente esté en uno de los métodos anteriores, afortunadamente, también se imprime en la información de la pila.
Desafortunadamente, el NullPointerException es una de las excepciones más informativas en Java (aunque también es una de las más comunes y frustrantes). No menciona lo que más nos importa: ¿dónde está null. Así que tenemos que retroceder unos pasos para encontrar dónde salió el error.
Al rastrear la información de la pila de manera gradual y verificar el código, podemos determinar que la causa del error es que se pasó un parámetro de nombre de archivo nulo a readPreferences(). Ya que readPreferences() sabe que no puede manejar nombres de archivo nulos, verifica esta condición de inmediato:
public void readPreferences(String filename) lanza IllegalArgumentException{ if (filename == null){ throw new IllegalArgumentException("filename is null"); } //if //...realizar otras operaciones... InputStream in = new FileInputStream(filename); //...leer el archivo de preferencias... }
Al lanzar excepciones tempranamente (también conocidas como "fallo rápido"), las excepciones se vuelven claras y precisas. La información de la pila inmediatamente refleja qué salió mal (se proporcionó un valor de parámetro ilegal), por qué salió mal (el nombre del archivo no puede ser nulo) y dónde salió mal (la parte inicial de readPreferences()). De esta manera, nuestra información de la pila puede proporcionar la verdad:
java.lang.IllegalArgumentException: el nombre del archivo es nulo en jcheckbook.JCheckbook.readPreferences(JCheckbook.java:207) en jcheckbook.JCheckbook.startup(JCheckbook.java:116) at jcheckbook.JCheckbook.<init>(JCheckbook.java:27) at jcheckbook.JCheckbook.main(JCheckbook.java:318)
Además, la información de la excepción incluida ("El nombre del archivo está vacío") hace que la información proporcionada por la excepción sea más rica, ya que esta respuesta es algo que el NullPointerException que lanzamos en el código anterior no podía proporcionar.
Al lanzar una excepción inmediatamente cuando se detecta un error, se puede evitar la construcción innecesaria de objetos o el uso de recursos, como archivos o conexiones de red. Además, también se pueden evitar las operaciones de limpieza necesarias para abrir estos recursos.
Retrasar la captura
Un error que pueden cometer tanto novatos como expertos es capturar una excepción antes de que el programa tenga la capacidad de manejarla. El compilador de Java fomenta indirectamente este comportamiento al requerir que todas las excepciones detectadas sean capturadas o lanzadas. La práctica natural es envolver el código inmediatamente en un bloque try y usar catch para capturar la excepción, para evitar que el compilador reporte un error.
El problema radica en qué hacer después de capturar la excepción. Lo peor que se puede hacer es no hacer nada. Un bloque catch vacío es como arrojar toda la excepción a un agujero negro, perdiendo para siempre toda la información que podría explicar cuándo, dónde y por qué se produjo el error. Grabar la excepción en el registro es un poco mejor, al menos hay un registro que consultar. Pero no podemos esperar que el usuario lea o entienda los archivos de registro y la información de la excepción. No es apropiado que readPreferences() muestre un cuadro de diálogo de error, porque aunque JCheckbook es actualmente una aplicación de escritorio, planeamos convertirla en una aplicación web basada en HTML. En ese caso, mostrar un cuadro de diálogo de error no es una opción. Al mismo tiempo, ya sea HTML o C/En la versión S, la información de configuración se lee en el servidor, mientras que los mensajes de error necesitan mostrarse en el navegador web o en el programa cliente. readPreferences() debe considerar estas necesidades futuras desde el diseño. Separar adecuadamente el código de la interfaz de usuario y la lógica del programa mejora la reutilización de nuestro código.
Capturar una excepción demasiado pronto antes de manejarla condicionalmente, generalmente lleva a errores más graves y otras excepciones. Por ejemplo, si el método readPreferences() anterior captura y graba inmediatamente al llamar al constructor FileInputStream la posible excepción FileNotFoundException, el código se convertirá en lo siguiente:
public void readPreferences(String filename){ //... InputStream in = null; // ¡NO HAGA ESTO NI SIQUIERA! try{ in = new FileInputStream(filename); } catch (FileNotFoundException e){ logger.log(e); } in.read(...); //... }
El código anterior captura un FileNotFoundException sin tener la capacidad de recuperarse de él. Si el archivo no se puede encontrar, el método siguiente obviamente no puede leerlo. ¿Qué sucede si se requiere que readPreferences() lea un archivo inexistente? Claro, se registraría un FileNotFoundException, si en ese momento miráramos el archivo de registro. Sin embargo, ¿qué sucede cuando el programa intenta leer datos del archivo? Dado que el archivo no existe, la variable in está vacía y se lanzará una NullPointerException.
Durante la depuración del programa, nuestra intuición nos dice que debemos mirar la información al final del registro. Eso será NullPointerException, y es muy molesto que esta excepción no sea específica. La información de error no solo nos engaña sobre lo que salió mal (el verdadero error es FileNotFoundException en lugar de NullPointerException), sino que también nos engaña sobre la ubicación del error. El verdadero problema está a varias líneas de distancia del lugar donde se lanza la NullPointerException, entre las cuales podrían haber varios llamados a métodos y la destrucción de clases. Nuestra atención se desvía de la verdadera causa del problema por esta pequeña cosa, hasta que miramos el registro hacia atrás para descubrir la fuente del problema.
Dado que lo que realmente debería hacer readPreferences() no es capturar estas excepciones, ¿qué debería ser? Parece contradictorio, pero lo más adecuado es realmente no hacer nada, no capturar la excepción de inmediato. Dejar la responsabilidad con el llamador de readPreferences(), que debe investigar la manera correcta de manejar la falta de archivo de configuración, que podría sugerir al usuario que especifique otro archivo, o usar un valor predeterminado, o en caso de que no funcione, advertir al usuario y salir del programa.
La manera de transmitir la responsabilidad de manejar la excepción al usuario superior de la cadena de llamadas es declarar la excepción en la cláusula throws del método. Al declarar las excepciones que pueden ocurrir, es mejor ser lo más específico posible. Esto se utiliza para identificar los tipos de excepciones que el programa que llama a su método necesita saber y estar preparado para manejar. Por ejemplo, la versión "captura diferida" de readPreferences() podría ser así:
public void readPreferences(String filename) lanza una excepción IllegalArgumentException, FileNotFoundException, IOException{ if (filename == null){ throw new IllegalArgumentException("filename is null"); } //if //... InputStream in = new FileInputStream(filename); //... }
Técnicamente, la única excepción que necesitamos declarar es IOException, pero hemos declarado explícitamente que el método puede lanzar FileNotFoundException. IllegalArgumentException no es obligatorio declarar, ya que es una excepción no verificada (es decir, subclase de RuntimeException). Sin embargo, declaramos esto para documentar nuestro código (estas excepciones también deben anotarse en los JavaDocs del método).
Por supuesto, su programa necesita capturar excepciones, de lo contrario se detendrá inesperadamente. Pero la técnica aquí es capturar excepciones en el nivel adecuado, para que su programa pueda recuperarse significativamente de las excepciones y continuar sin causar errores más profundos; o proporcionar información clara al usuario, incluyendo guiarlos a recoverarse de los errores. Si su método no es competente, no trate la excepción, déjela para capturar y manejar en el nivel adecuado.
Resumen
Los desarrolladores experimentados saben que el mayor desafío de depurar programas no radica en la corrección de defectos, sino en encontrar el escondite de los defectos en el gran volumen de código. Si solo sigue los tres principios de este artículo, sus excepciones pueden ayudarlo a rastrear y erradicar defectos, haciendo que su programa sea más robusto y amigable para los usuarios. Esto es todo el contenido de este artículo, espero que pueda proporcionar cierta ayuda a su aprendizaje o trabajo.
Declaración: El contenido de este artículo se ha obtenido de la red, es propiedad del autor original, el contenido se ha contribuido y subido por los usuarios de Internet de manera autónoma. Este sitio no posee los derechos de propiedad, no ha sido editado por humanos y no asume ninguna responsabilidad legal relacionada. Si encuentra contenido sospechoso de infracción de derechos de autor, por favor envíe un correo electrónico a: notice#oldtoolbag.com (al enviar un correo electrónico, por favor reemplace # con @) para denunciar, y proporcione evidencia relevante. Una vez confirmado, este sitio eliminará inmediatamente el contenido sospechoso de infracción.