En «Java eficaz«, Joshua Bloch escribió 9 consejos sobre cómo manejar excepciones en Java. Estos consejos se han convertido en el estándar de facto para el manejo de excepciones de Java. En esta publicación, enumero algunos ejemplos del manejo de excepciones de Java en algunos proyectos de código abierto y comento el uso de siguiendo los 9 consejos de manejo de excepciones.
Los 9 consejos sobre el manejo de excepciones de Java son:
1. Use exceptions only for exceptional conditions 2. Use checked exceptions for recoverable conditions and runtime exceptions for programming errors 3. Avoid unnecessary use of checked exceptions 4. Favor the use of standard exceptions 5. Throw exceptions appropriate to the abstraction 6. Document all exceptions thrown by each method 7. Include failure-capture information in detail messages 8. Strive for failure atomicity 9. Don't ignore exceptions
1. Utilice excepciones solo para condiciones excepcionales
Este elemento trata principalmente de evitar el uso de excepciones para el flujo de control ordinario.
Por ejemplo, en lugar de usar una excepción para terminar un flujo de control de bucle:
try{ Iterator<Foo> iter = ...; while(true) { Foo foo = i.next(); ... } } catch (NoSuchElementException e){ } |
se debe usar la iteración regular sobre una colección:
for(Iterator<Foo> iter = ...; i.hasNext();){ Foo foo = i.next(); ... } |
No encontré ningún ejemplo que use excepciones para el flujo de control regular.
2. Utilice excepciones marcadas para condiciones recuperables y excepciones de tiempo de ejecución para errores de programación
En la mayoría de los casos, si la persona que llama puede recuperar la excepción, se deben utilizar las excepciones marcadas. De lo contrario, se debe utilizar la excepción de tiempo de ejecución. Las excepciones en tiempo de ejecución indican errores de programación que pueden evitarse comprobando algunas condiciones previas, como los límites de la matriz y las comprobaciones de nulidad.
En el siguiente método, IllegalArgumentException es una RuntimeException, cuyo uso indica errores de programación. Los errores de programación a menudo se pueden evitar comprobando las condiciones previas. Así que este es un mal ejemplo basado en este consejo. La excepción puede evitarse comprobando las condiciones previas, es decir, el método «hasNext ()» aquí. (enlace al código fuente)
/** * Convert a tag string into a tag map. * * @param tagString a space-delimited string of key-value pairs. For example, {@code "key1=value1 key_n=value_n"} * @return a tag {@link Map} * @throws IllegalArgumentException if the tag string is corrupted. */ public static Map<String, String> parseTags(final String tagString) throws IllegalArgumentException { // delimit by whitespace or '=' Scanner scanner = new Scanner(tagString).useDelimiter("\s+|="); Map<String, String> tagMap = new HashMap<String, String>(); try { while (scanner.hasNext()) { String tagName = scanner.next(); String tagValue = scanner.next(); tagMap.put(tagName, tagValue); } } catch (NoSuchElementException e) { // The tag string is corrupted. throw new IllegalArgumentException("Invalid tag string '" + tagString + "'"); } finally { scanner.close(); } return tagMap; } |
3. Evite el uso innecesario de excepciones marcadas
Las excepciones marcadas obligan a las personas que llaman a lidiar con las condiciones excepcionales porque el compilador se quejará si no. El uso excesivo de las excepciones marcadas conlleva a las personas que llaman la carga de manejar las condiciones excepcionales. Por lo tanto, las excepciones marcadas deben usarse cuando sea necesario. La regla general para usar una excepción marcada es cuando la excepción no se puede evitar verificando las condiciones previas y la persona que llama puede tomar algunas acciones útiles para manejar la excepción.
Las propias excepciones de tiempo de ejecución de uso común son ejemplos de NO uso excesivo de excepciones comprobadas. Las excepciones comunes en tiempo de ejecución son: ArithmeticException, ClassCastException, IllegalArgumentException, IllegalStateException, IndexOutOfBoundExceptions, NoSuchElementException y NullPointerException.
En el siguiente método (enlace al código fuente), cuando propertyName no es uno de los casos de destino, no hay mucho que pueda hacer la persona que llama, por lo que se lanza una excepción de tiempo de ejecución.
@Override public Object get(String propertyName) { switch (propertyName.hashCode()) { case 842855857: // marketDataName return marketDataName; case -1169106440: // parameterMetadata return parameterMetadata; case 106006350: // order return order; case 575402001: // currency return currency; case 564403871: // sensitivity return sensitivity; default: throw new NoSuchElementException("Unknown property: " + propertyName); } } |
4. Favorecer el uso de excepciones estándar
Las clases de excepción de Java que se reutilizan con más frecuencia son las siguientes. Puede consultar la lista completa aquí.
1. java.io.IOException 2. java.io.FileNotFoundException 3. java.io.UnsupportedEncodingException 4. java.lang.reflect.InvocationTargetException 5. java.security.NoSuchAlgorithmException 6. java.net.MalformedURLException 7. java.text.ParseException 8. java.net.URISyntaxException 9. java.util.concurrent.ExecutionException 10. java.net.UnknownHostException
Ninguno de los 10 primeros es el más utilizado que se muestra en el libro. Pero tenga en cuenta que estos se cuentan por proyectos, es decir, si se usa una clase en un proyecto, se cuenta solo una vez sin importar cuántos métodos en el proyecto la estén usando. Así que esto es por # de proyectos, pero por # de ocurrencias en el código.
5. Lanzar excepciones apropiadas a la abstracción.
La excepción lanzada debe tener conexión con la tarea que realiza la persona que llama. Este elemento introduce la conversión de excepciones (captura una excepción y lanza otra) y el encadenamiento de excepciones (envuelve una excepción en una nueva excepción para mantener la cadena causal de la excepción).
private void serializeBillingDetails(BillingResult billingResult, BillingDetailsType billingDetails) { try { final JAXBContext context = JAXBContext .newInstance(BillingdataType.class); final ByteArrayOutputStream out = new ByteArrayOutputStream(); final Marshaller marshaller = context.createMarshaller(); marshaller.setProperty("jaxb.formatted.output", Boolean.FALSE); final BillingdataType billingdataType = new BillingdataType(); billingdataType.getBillingDetails().add(billingDetails); marshaller.marshal(factory.createBillingdata(billingdataType), out); final String xml = new String(out.toByteArray(), "UTF-8"); billingResult.setResultXML(xml.substring( xml.indexOf("<Billingdata>") + 13, xml.indexOf("</Billingdata>")).trim()); billingResult.setGrossAmount(billingDetails.getOverallCosts() .getGrossAmount()); billingResult.setNetAmount(billingDetails.getOverallCosts() .getNetAmount()); } catch (JAXBException | UnsupportedEncodingException ex) { throw new BillingRunFailed(ex); } } |
El método anterior detecta JAXBException y UnsupportedEncodingException, y vuelve a generar una nueva excepción que es apropiada para el nivel de abstracción del método. La nueva excepción BillingRunFailed envuelve la excepción original. Este es un buen ejemplo de encadenamiento de excepciones. El beneficio del encadenamiento de excepciones es mantener la excepción de nivel inferior que es útil para depurar el problema. (enlace al código fuente)
6. Documente todas las excepciones generadas por cada método.
Esto está muy infrautilizado. La mayoría de las API públicas carecen del documento @throws Java para explicar la excepción que se lanza.
He aquí un buen ejemplo. (enlace al código fuente)
... * * @throws MalformedURLException The formal system identifier of a * subordinate catalog cannot be turned into a valid URL. * @throws IOException Error reading subordinate catalog file. */ public String resolveSystem(String systemId) throws MalformedURLException, IOException { ... |
Este es un mal ejemplo de falta de información sobre en qué casos se lanza la excepción.
* @throws Exception exception */ public void startServer() throws Exception { if (!externalDatabaseHost) { |
7. Incluya información de captura de fallas en mensajes detallados
private OutputStream openOutputStream(File file) throws IOException { if (file.exists()) { if (file.isDirectory()) { throw new IOException("File '" + file + "' exists but is a directory"); } if (!file.canWrite()) { throw new IOException("File '" + file + "' cannot be written to"); } } else { final File parent = file.getParentFile(); if (parent != null) { if (!parent.mkdirs() && !parent.isDirectory()) { throw new IOException("Directory '" + parent + "' could not be created"); } } } return new FileOutputStream(file, false); } |
En este método, IOException usa una cadena diferente para pasar la información de captura de fallas diferente.
8. Luchar por la atomicidad del fracaso
El ítem 8 trata sobre fallar. La regla general es que un método fallido no debería cambiar el estado de los objetos en el método. Para fallar temprano, una forma es verificar la validez de los parámetros antes de realizar la operación. El siguiente es un buen ejemplo de cómo seguir este consejo.
/** * Assigns a new int value to location index of the buffer instance. * @param index int * @param newValue int */ public void modifyEntry(int index, int newValue) { if (index < 0 || index > size - 1) { throw new IndexOutOfBoundsException(); } // ((int[]) bufferArrayList.get((int) (index / pageSize)))[index % pageSize] = ((int[]) bufferArrayList.get((index >> exp)))[index & r] = newValue; } |
9. No ignore las excepciones
public static Bundle decodeUrl(String s) { Bundle params = new Bundle(); if (s != null) { String array[] = s.split("&"); for (String parameter : array) { String v[] = parameter.split("="); try { params.putString(URLDecoder.decode(v[0], "UTF-8"), URLDecoder.decode(v[1], "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } return params; } |
Casi siempre se debe evitar la impresión de trazas de pila en el código de producción. Esto es tan malo como ignorar las excepciones. Esto escribe en el flujo de errores estándar, que no es donde va el registro al usar un marco de registro. (enlace al código fuente)
Es posible que desee consultar las 10 preguntas principales sobre las excepciones de Java en StackOverflow.