La controversia sobre las excepciones checked y unchecked

Escrito por picodotdev el , actualizado el .
java planeta-codigo programacion
Enlace permanente Comentarios

Las excepciones son una forma de gestionar las condiciones de error que se dan en los programas. En el lenguaje C se utiliza el valor de retorno de la función para determinar la condición de error que se ha producido, el problema es que comprobar el valor de retorno puede ignorarse y la gestión de errores está mezclada con la tarea del programa.

El lenguaje Java utiliza un mecanismo de excepciones, las excepciones son objetos que se lanzan cuando se produce una condición de error. Todas las excepciones en Java heredan de Throwable subdividiéndose en Error y Exception, las primeras son condiciones de error del sistema y las segundas condiciones de error del programa. A su vez las Exception pueden ser checked si heredan de esta y son aquellas que el compilador fuerza a que sean capturadas no pudiendo ignorarse, han de capturarse en una construcción try catch o declarar que el método puede lanzar la excepción no capturada. Las excepciones unchecked heredan de RuntimeException que heredan a su vez de Exception pero tienen la particularidad de que no es necesario capturarlas ni declararlas como que se pueden lanzar debido a que se consideran condiciones de error en la programación como un acceso a un array fuera de rango que produce un ArrayIndexOutOfBounds, el conocido NullPointerException cuando se utiliza una referencia nula, otro es ArithmeticException que se produce al dividir por 0.

Algunas ventajas de las excepciones son:

  • Separar el código que gestiona los errores del código con el caso principal del programa.
  • Propagar errores hacia arriba en la pila de llamadas.
  • Agrupar y diferenciar entre diferentes tipos de errores.

Los problemas de las excepciones checked

Hay una cierta polémica sobre si las excepciones checked son una buena idea. Entre los motivos que se alegan en contra de su uso están que cambiar la firma de un método añadiendo una nueva excepción como lanzable hace que el código que usase ese método podría ocasionar errores de compilación y que hace necesario el tratarla o declararla en la cadena de métodos hasta que sea tratada. Otro motivo es que a mayor nivel en la jerarquía de llamada en los métodos se necesitarán manejar una lista amplia de excepciones.

En el lado contrario las excepciones se consideran que son buenas porque conocer las condiciones de error o excepción que puede lanzar el método forma parte del contrato del método y es necesario para realizar un correcto manejo de errores. Las excepciones checked pueden parecer un incordio pero son necesarias para hacer un correcto manejo de errores y evitar que el programa falle por no tratar las condiciones de error de las que advertirían. Por otro lado no deberían silenciarse con un bloque catch vacío sin una buena razón. En las excepciones checked el compilador es capaz de advertir si alguna excepción no ha sido capturada o lanzada.

Como regla general las excepciones checked se usan cuando el programa es capaz de recuperarse del error y tratarlo adecuadamente, las unchecked cuando se trata de un error de programación o no se puede hacer nada para recuperarse.

En el siguiente código se observa como capturar, lanzar y declarar excepciones en las firmas de los métodos en Java en una construcción try catch finally.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Bank {

    public void transfer(BigDecimal source, BigDecimal target, BigDecimal amount) throws InvalidAmountException, InsufficientFoundsException {
        if (amount < 0) {
            throw new InvalidAmountException();
        }
        if (source.getAmount() < amount) {
            throw new InsufficientFoundsException();
        }
        target.substract(amount);
        source.add(amount);
    }
}
Bank.java
1
2
3
4
5
6
7
8
9
try {
    brank.transfer(source, destine, amount);
} catch (InvalidAmountException e) {
    System.out.println(e.getMessage());
} catch (InsufficientFoundsException e) {
    System.out.println(e.getMessage());
} finally {
    System.out.println("Acción finalizada");
}
Main.java

Las excepciones checked presentan los siguientes problemas:

  • Son verbosas: requieren utilizar bloques de sentencias try-catch y declarar en la firma del método la excepción en caso de que las lancen. Al mismo tiempo si hay niveles intermedios de métodos entre el método que la lanza y el que la trata la excepción debe ser declarada en los métodos intermedios.
  • Promueven malas prácticas: si se intentan manejar en el lugar incorrecto, si se usan bloques de código catch que no hacen nada que las ocultan, si se capturan excepciones generales como Exception en vez de tipos específicos y si se transforman excepciones Exception en RuntimeException.
  • Incrementan el acoplamiento entre módulos: si se usa un método de una librería que añade una excepción, entonces todo el código que llame a ese método debe ser actualizado para manejar la excepción o relanzarla.
  • Requieren transformar excepciones entre diferentes niveles de abstracción: por ejemplo la capa de acceso a base de datos puede requerir convertir uan excepción por un problema de red en una excepción de base de datos no disponible. Esto es tedioso pero necesario para evitar exponer detalles de implementación.
  • No funciona bien con la herencia: si un método se sobreescribe no puede lanzar una excepción si el método sobreescrito no declara que lanza una excepción.
  • No funcionan bien con las lambdas: en Java las lambdas se implementan usando interfaces con un único método abstracto, ese método debe o declarar la posible excepción, en cuyo caso todo método que lo use debe manejarla, o el cuerpo de la lambda debe capturar la excepción. Ambas opciones incrementan el código necesario para usar las lambdas reduciendo uno de sus beneficios de ser más concisas.

La solución a las excepciones checked de otros lenguajes posteriores a Java

La solución por la que han optado otros lenguajes más modernos que Java como C# y Kotlin es considerar a todas las excepciones como unchecked. Dado que en muchos casos el único tratamiento posible de una excepción posible es relanzarla, para manejar las excepciones en muchos casos es suficiente un manejador global de excepciones que las capture. Otro beneficio del manejador global de excepciones es que centraliza en un único punto el tratamiento de las excepciones.

Por el contrario dado que las excepciones unchecked no necesitan declararse en los métodos ni el compilador produce ningún error en caso de que una excepción no se capture o lance al usar un método se desconoce que ese método puede lanzar una excepción. Para mitigar el problema de no conocer si un método lanzan una excepción en Java se pueden usar las anotaciones de Javadoc para declararlas al menos en la documentación. Dado que los IDEs muestran la documentación del Javadoc al usar un método no es necesario inspeccionar el cuerpo de un método para conocer si lanza una excepción.

En el aparatado referencia incluyo unos buenos enlaces que amplían y detallan más apropiadamente la controversia sobre las excepciones checked y unchecked.

Comparte el artículo: