La razón por la cual las excepciones son un poderoso método de depuración es que responden las siguientes tres preguntas:
1. ¿Qué salió mal?
2. ¿Dónde cometiste un error?
3. ¿Por qué salió algo mal?
En el caso del uso efectivo de excepciones, el tipo de excepción responde "lo que" se arroja, la excepción de la pila de rastreo responde "donde" se lanza, la información de excepción responde "por qué se lanza", y si su excepción no responde todas las preguntas anteriores, entonces es posible que no las use bien.
Hay tres principios que pueden ayudarlo a maximizar el uso de buenas excepciones durante la depuración. Estos tres principios son:
1. Específico y claro
2. Tíralo temprano
3. Captura de retraso
Para explicar estos tres principios de manejo efectivo de excepciones, este artículo lo discute fabricando un gerente financiero personal JCheckbook, que se utiliza para registrar y rastrear actividades de cuentas bancarias, como depósitos y retiros y emisión de facturas.
Concreto y claro
Java define una jerarquía de una clase de excepción, que comienza con lanzamiento, extiende el error y la excepción, y la excepción extiende RuntimeException. Como se muestra en la Figura 1.
Estas cuatro clases son generalizadas y no proporcionan mucha información de error. Aunque la instancia de estas clases es sintácticamente legal (como: New Showable ()), es mejor tratarlas como clases de base virtuales y usar sus subclases más especializadas. Java ha proporcionado una gran cantidad de subclases de excepción. Si desea ser más específico, también puede definir su propia clase de excepción.
Por ejemplo: el paquete Java.io define la subclase de la clase de excepción IOException, que es más especializada y subclase de IOException, como FileNotFoundException, EofException y ObjectStreamException. Cada uno describe una clase específica de errores de E/S: pérdida de archivos, excepción de la finalización del archivo y el error de error de los flujos de objetos serializados. Cuanto más específica sea la excepción, nuestro programa podrá responder la pregunta "qué salió mal".
También es importante tratar de ser lo más claro posible al atrapar las excepciones. Por ejemplo: JCHECKBook puede manejar FileNotFoundException al volver a producir el nombre del archivo del usuario. Para EOFException, puede continuar en función de la información leída antes de que se lance la excepción. Si se lanza ObjectStreamException, el programa debe solicitar al usuario que el archivo está corrupto y que se debe usar un archivo de copia de seguridad u otro archivo.
Java facilita la captura de excepciones explícitamente, porque podemos definir múltiples bloques de captura para el mismo bloque de try, para que cada excepción se pueda manejar apropiadamente por separado.
Archivo prefsfile = nuevo archivo (prefsfileName); intente {readPreferences (prefsfile);} catch (fileNotFoundException e) {// alerta al usuario que el archivo especificado // no existe} catch (eOfException e) {// Otro error de I/O // ocurrió}JCheckbook proporciona a los usuarios información explícita sobre la captura de excepciones mediante el uso de múltiples bloques de captura. Por ejemplo: si se atrapa FileNotFoundException, puede solicitar al usuario que especifique otro archivo. En algunos casos, el esfuerzo de codificación adicional traído por múltiples bloques de captura puede ser una carga no esencial, pero en este ejemplo, el código adicional ayuda al programa a proporcionar una respuesta más fácil de usar.
Además de las excepciones manejadas por los primeros tres bloques de captura, el último bloque de captura proporciona al usuario información de error más generalizada cuando se lanza la IOException. De esta manera, el programa puede proporcionar información específica tanto como sea posible, pero también tiene la capacidad de manejar otras excepciones inesperadas.
A veces, los desarrolladores atrapan excepciones normalizadas y muestran el nombre de la clase de excepción o la información de la pila de impresión para "especificidad". ¡No hagas esto! Los usuarios solo tendrán dolor de cabeza al ver java.io.EOFException o apilar información, en lugar de obtener ayuda. Se deben capturar excepciones específicas y se debe solicitar al usuario con "palabras humanas". Sin embargo, la pila de excepciones se puede imprimir en su archivo de registro. Recuerde, las excepciones y la información de la pila se utilizan para ayudar a los desarrolladores en lugar de a los usuarios.
Finalmente, debe tenerse en cuenta que JCheckbook no capta excepciones en readPreferences() , pero deja las excepciones de captura y manejo a la capa de interfaz de usuario, para que los usuarios puedan ser notificados utilizando cuadros de diálogo u otros métodos. Esto se llama "Captura de retraso", que se discutirá a continuación.
Tirar temprano
La información de la pila de excepciones proporciona el orden exacto de la cadena de llamadas del método que hace que ocurra la excepción, incluido el nombre de clase, el nombre del método, el nombre del archivo de código e incluso el número de línea de cada llamada de método, para localizar con precisión la escena donde ocurre la excepción.
java.lang.nullpointerexceptionat 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) en jcheckbook.jcheckbook. <Init> (jcheckbook.java:27) en jcheckbook.jcheckbook.main (jcheckbook.java:318)
El anterior muestra el caso donde el método Open () de la clase FileInputStream lanza una NullPointerException. Sin embargo, tenga en cuenta que FileInputStream.close() es parte de la biblioteca de clase Java estándar, y es probable que el problema de esta excepción sea que nuestro código en sí no sea la API Java. Por lo tanto, es probable que el problema aparezca en uno de los métodos anteriores, y afortunadamente también se imprime en la información de la pila.
Desafortunadamente, NullPointerException es la excepción menos informativa (pero también la más común y fallida) en Java. No menciona lo que más nos importa: dónde está nulo. Así que tuvimos que retroceder algunos pasos y averiguar dónde salía algo mal.
Por el paso de la información de la pila de seguimiento y verificar el código, podemos determinar que la causa del error es que un parámetro de nombre de archivo vacío se pasó a readPreferences() . Dado que readPreferences() sabe que no puede manejar los nombres de los archivos vacíos, verifique la condición de inmediato:
public void readPreferences (String FileName) arroja ilegalargumentException {if (filename == null) {throw newleArGumentException ("El nombre de archivo es nulo"); } // if //...perform otras operaciones ... InputStream in = new FileInputStream (nombre de archivo); //... leer el archivo de preferencias ...} Al lanzar una excepción temprano (también conocida como "falla rápidamente"), la excepción es clara y precisa. La información de la pila refleja inmediatamente lo que salió mal (se proporcionaron valores de parámetros ilegales), por qué salió mal (el nombre del archivo no puede ser nulo) y dónde salió mal (la primera parte de readPreferences() ). De esta manera, nuestra información de pila se puede proporcionar sinceramente:
java.lang.iLLEGALARGUNMEXCEPTION: el nombre de archivo es nullat jcheckbook.jcheckbook.readpreferences (jcheckbook.java:207) en jcheckbook.jcheckbook.startup (jcheckbook.java:116) en jcheckbook.jcheckbook. <init> (jcheckbook.java:27) At At jcheckbook.jcheckbook.main (jcheckbook.java:318)
Además, la información de excepción contenida en él ("El nombre del archivo está vacío") hace que la excepción sea más rica respondiendo explícitamente la pregunta de lo que está vacío, que no está disponible para nosotros en el código anterior.
La falla se logra lanzando una excepción inmediatamente cuando se detecta un error, la construcción innecesaria de objetos o el uso de recursos, como el archivo o la conexión de red, se pueden evitar de manera efectiva. Del mismo modo, las operaciones de limpieza provocadas al abrir estos recursos se pueden guardar.
Captura retrasada
Un error que tanto los novatos como los maestros pueden cometer es atrapar el programa antes de que tenga la capacidad de manejar la excepción. El compilador Java facilita indirectamente este comportamiento al requerir que la excepción verificada sea atrapada o arrojada. La forma natural es envolver inmediatamente el código en un bloque de try y usar captura para atrapar excepciones para evitar el error del compilador.
La pregunta es, ¿qué debo hacer si obtengo la excepción después de ser atrapado? Lo peor que debe hacer es no hacer nada. Un bloque de captura vacío es equivalente a arrojar toda la excepción al agujero negro, y toda la información que puede explicar cuándo, dónde, por qué los errores están equivocados se perderán para siempre. Es un poco mejor escribir excepciones en el registro, al menos hay registros para verificar. Pero no podemos esperar que los usuarios lean o comprendan archivos de registro e información de excepción. Tampoco es apropiado que readPreferences() muestre un diálogo de mensaje de error, porque si bien JCheckbook es actualmente una aplicación de escritorio, también planeamos convertirlo en una aplicación web basada en HTML. En ese caso, mostrar un diálogo de error obviamente no es una opción. Al mismo tiempo, independientemente de la versión HTML o C/S, la información de configuración se lee en el servidor, y el mensaje de error debe mostrarse en el navegador web o el programa del cliente. readPreferences() también debe tener en cuenta estas necesidades futuras al diseñar. La separación adecuada del código de interfaz de usuario y la lógica del programa pueden mejorar la reutilización de nuestro código.
La captura de una excepción prematuramente antes del manejo condicional a menudo resulta en errores más graves y otras excepciones. Por ejemplo, si readPreferences() anterior captura y registra inmediatamente la FileNotFoundException que puede ser lanzada cuando se llame al constructor FileInputStream, el código se convertirá en este:
public void readPreferences (String filename) {// ... inputStream in = null; // no hagas esto !!! // ...}El código anterior lo captura sin la capacidad de recuperarse de FileNotFoundException. Si no se puede encontrar el archivo, el siguiente método obviamente no puede leerlo. ¿Qué sucede si se pide a ReadPreferences () que lea un archivo que no existe? Por supuesto, se registrará FileNotFoundException, y si miramos el archivo de registro en ese momento, lo sabríamos. Sin embargo, ¿qué sucede cuando un programa intenta leer datos de un archivo? Dado que el archivo no existe, la variable IN está vacía y se lanzará una NullPointerException.
Al depurar un programa, Instinct nos dice que veamos la información al final del registro. Eso sería nullpointerexception, y lo que es muy molesto es que esta excepción es muy inespecífica. El mensaje de error no solo nos engaña lo que salió mal (el error real es FileNotFoundException en lugar de NullPointerException), sino que también engaña la fuente del error. El verdadero problema está fuera del número de filas en el NullPointerException lanzado, que puede tener varias llamadas de métodos y destrucción de clase. Nuestra atención fue llamada el verdadero error por el pequeño pez hasta que volvimos a mirar el registro para encontrar la fuente del problema.
Dado que lo readPreferences() realmente debería hacer es no atrapar estas excepciones, ¿cuáles debería ser? Parece un poco contradictorio, y la forma más apropiada es no hacer nada y no captar excepciones de inmediato. Deje la responsabilidad en la persona que llama de readPreferences() y deje que estudie la forma apropiada de lidiar con los archivos de configuración faltantes. Puede solicitar al usuario que especifique otros archivos o use el valor predeterminado. Si realmente no funciona, puede advertir al usuario y salir del programa.
La forma de aprobar la responsabilidad de un manejo de excepciones aguas arriba de la cadena de llamadas es declarar la excepción en la cláusula de lanzamiento del método. Al declarar posibles excepciones, tenga cuidado, cuanto más específico, mejor. Esto se utiliza para identificar el tipo de excepción que el programa que llama a su método necesita saber y estar preparado para manejar. Por ejemplo, la versión "Delay Capture" de readPreferences() podría verse así:
public void readPreferences (String FileName) arroja ilegalargumentException, filenotfoundException, ioexception {if (filename == null) {throLe ilegalargumentException ("El nombre de archivo es nulo"); } // if // ... inputStream in = new FileInputStream (nombre de archivo); // ...}Técnicamente, la única excepción que necesitamos declarar es una IOException, pero declaramos explícitamente que el método puede lanzar una FileNotFoundException. No es necesario declarar ilegalargumentException porque es una excepción que no es de verificación (es decir, una subclase de RuntimeException). Sin embargo, se declara que documenta nuestro código (estas excepciones también deben marcarse en los Javadocs del método).
Por supuesto, eventualmente su programa necesita ver la excepción, de lo contrario, terminará inesperadamente. Pero el truco aquí es atrapar excepciones en el nivel correcto para que su programa pueda recuperarse de manera significativa de las excepciones y continuar sin causar errores más profundos; o poder proporcionar a los usuarios información clara, incluida la guía para que se recuperen de los errores. Si su método no es competente, no maneje la excepción, déjelo atrás para atraparlo y manejarlo en el nivel correcto.
Resumir
Los desarrolladores experimentados saben que la mayor dificultad para la depuración de los programas es no arreglar defectos, sino descubrir los escondites de defectos de una gran cantidad de código. Mientras siga los tres principios de este artículo, sus excepciones pueden ayudarlo a rastrear y eliminar defectos, hacer que su programa sea más robusto y fácil de usar. Lo anterior es todo el contenido de este artículo, y espero que lo ayude en su estudio o trabajo.