21 marzo 2011

Checked vs. unchecked exceptions

Después de desarrollar durante una semana una librería de clases en java me he vuelto a plantear una pregunta que ya tenía algo olvidada en el fondo del subconsciente: ¿excepciones checked o unchecked? Ya sé que es una pregunta muy sobada y seguro que todos tenéis vuestra propia opinión al respecto pero vamos a recordar un poco el debate.

Java soporta los dos tipos de excepciones: checked y unchecked. Java obliga a declarar en la signatura de los métodos todas las excepciones checked que pueda lanzar ese método y todos los métodos que llamen al método deben o capturarlas y tratarlas en un bloque try-catch o declararlas en la firma del método. Por el contrario, las excepciones unchecked que no deben declararse.

La cuestión que se nos plantea al diseñar una librería de clases en java es: ¿Hacemos que nuestras excepciones sean checked o, por el contrario, las hacemos unchecked? Parece evidente que java se diseño para que usáramos checked exceptions en nuestros desarrollos y la posición “oficial” de Oracle al respecto así lo indica (podéis ver la postura oficial de Oracle en este enlace): las excepciones que creamos en nuestras librerías de clases deben ser checked por norma, solo la máquina virtual debería lanzar unchecked exceptions ya que estas solo deben usarse para indicar problemas de programación que el cliente no puede tratar:

“Here's the bottom line guideline: If a client can reasonably be expected to recover from an exception, make it a checked exception. If a client cannot do anything to recover from the exception, make it an unchecked exception.”

Esta postura “oficial” tiene sus defensores y sus detractores, veamos por encima algunos de estos argumentos:

  • El hecho de que el compilador te fuerce a tratar o a propagar explícitamente una excepción asegura que no nos olvidemos de tratarlas. ¿Cuántas veces habéis visto algo del estilo try… catch(Exception e) {e.printStackTrace()}, o, todavía peor try… catch(Exception e) { }?
  • Con el uso de excepciones unchecked es más fácil olvidar el tratamiento de errores por qué el compilador no te fuerza a tratarlas. Si usas unchecked exceptions cualquier parte de tu librería puede lanzar excepciones y como eres consciente de ello, eres más cuidadoso al tratar los casos de error. Tened en cuenta que aunque uses checked exceptions se pueden producir unchecked exceptions en cualquier llamada (NullPointerException, etc…)
  • Las excepciones unchecked se deben documentar incluso mejor que las checked: la documentación en este caso es el único mecanismo que tiene el usuario de la librería de conocer las excepciones que lanza un método, no tendrá ninguna ayuda del compilador.
  • Las excepciones checked exponen los detalles de la implementación en niveles de abstracción muy diferentes. Cuantas veces habéis visto métodos de negocio que tratan o propagan SQLException sin que el método tenga (en este nivel de abstracción) nada que ver con SQL.
  • Las excepciones checked provocan inestabilidad en las firmas de los métodos. Si cambia la implementación de un método que lance excepciones checked se deben cambiar las firmas de todos los métodos que propaguen esas excepciones para adecuarlas a la nueva implementación. El hecho de que las excepciones formen parte de la interface hace que sea difícil añadir o quitar excepciones en posteriores versiones.
  • El uso de checked exceptions puede provocar código poco claro y muy enrevesado. Los bloques try…catch en el código mezclan la lógica de negoció con el tratamiento de las excepciones y hacen que el código resultante sea liado y poco claro.
  • El uso de checked exceptions puede provocar Shallowing (no hacer nada en el catch o hacer catch muy generales de Exception son muestras claras de shallowing).

Creo que nadie discute que la introducción de las excepciones en los lenguajes de programación es una excelente decisión que nos proporciona una forma unificada de tratar las condiciones de error que se producen durante la ejecución de nuestros programas, el tema es si se debe obligar a declarar y verificar las excepciones que lance un método, o, por el contrario, se puede dejar al programador la decisión de que excepciones quiere tratar.

Como todo el mundo, inicialmente construía mis librerías de clases usando excepciones checked. Después de leer a varios “gurus” debatir sobre este tema y reflexionar sobre los “pros” i los “contras” de cada una de las posturas, decidí usar unchecked exceptions en mis desarrollos y me ha proporcionado más beneficios que problemas. I vosotros, ¿qué opináis? ¿usáis unchecked exceptions en vuestros desarrollos?

Fuentes:

15 marzo 2011

¿Delegates en java?

Hola a todos, en este post intentaré exponer por qué java no tiene soporte del lenguaje para los delegates y como pasar un método como parámetro en java. Si queréis una excelente introducción a los delegates no dejéis de leer este post que publico Eduard Tomàs en su blog Burbujas en .NET.

Bueno, a lo que iba, esta semana, después de un período inmerso en un proyecto con tecnología .NET he vuelto a Java i Eclipse. En este desarrollo me ha surgido la necesidad de pasar un método como parámetro. Bueno pues nada, definimos un delegate y listos:

public void delegate MyDelegate();

Sorpresa: en java NO existe la palabra reservada delegate! Oooops! ¿Qué quiere decir esto? ¿No se puede pasar un método como parámetro en java? No me lo creo… pero si se puede, ¿cómo lo hacemos sin soporte del lenguaje? Veamos, vamos por partes:


1.- ¿Tiene java soporte para los delegates?


NO, java no tiene soporte explicito para los delegates, es decir, java no tiene una palabra reservada delegate pero, evidentemente, SI tiene soporte para pasar métodos como parámetros. En el white paper About Microsoft's "Delegates" de Sun (de Oracle quise decir ;-)) se exponen los motivos para no incluir los delegates como parte del lenguaje:



  • Los delegates innecesarios por qué existen otras alternativas de diseño que proporcionan una funcionalidad igual o superior: las inner classes.
  • Los delegates añaden complejidad.
  • La introducción de los delegates en el lenguaje implica una pérdida de orientación a objeto del mismo.
  • Etc…

Como conclusión vemos que Java ofrece soporte a los delegates mediante las inner classes.


En el siguiente punto veremos como pasar métodos como parámetros con un lenguaje que proporciona soporte explícito a los delegates como C# y cómo se podría hacer en un lenguaje si soporte explicito como java.


Usaremos un ejemplo para ilustrar los conceptos anteriores, supongamos que tenemos una clase Foo con dos atributos de tipo entero i la queremos dotar de un método Operate que permita a sus clientes realizar operaciones con los valores de estos dos atributos (sumar, restar, multiplicar, dividir y cualquier operación que se le ocurra al cliente) .


2.- Uso de delegate para pasar métodos como parámetro


Con un lenguaje que proporciona soporte explícito a los delegates como C# este método se podría implementar definiendo un delegate con dos parámetros enteros y que devuelve un entero:

delegate int Operate(int x, int y);

y definiendo la clase Foo así:

class Foo
{
    public int x;
    public int y;
    public int Operate(OperationDelegate myDelegate)
    {
        return myDelegate(x, y);
    }        
}

con esto si un cliente de la clase Foo quiere sumar los dos valores de x e y lo puede hacer así:

Foo f = new Foo() { x=2, y=2};
OperationDelegate suma = new OperationDelegate((x, y) => x + y);
f.Operate(suma);

y si el cliente quiere multiplicarlos:

Foo f = new Foo() { x=2, y=2};
OperationDelegate multiplica = new OperationDelegate((x, y) => x * y);
f.Operate(multiplica);

y si el cliente quiere primero sumarlos, al resultado añadirle 15 y restar 45:

Foo f = new Foo() { x=2, y=2};
OperationDelegate otherOperation= new OperationDelegate((x, y) => 15 + x + y - 45);
f.Operate(otherOperation);

Nota: he realizado la implementación de los delegates con expresiones lambda, si no usamos lambdas se haría así:

Foo f = new Foo() { x=2, y=2};
OperationDelegate suma = new OperationDelegate(SumaDelegate);
f.Operate(suma);
int SumaDelegate(int x, int y)
{
    return x + y;
}

Fácil no? Vamos ahora a ver como se implementaría el ejemplo anterior con java, que recordemos, no ofrece soporte explicito para delegates:


3.- Uso de las inner classes para pasar métodos como parámetros


Una posible forma de implementar el ejemplo anterior en java usando anonymous inner classes seria definir una interface para el método que se quiere pasar como parámetro:

interface OperationDelegate { int invoke(int x, int y);}

y definir la clase Foo como sigue:

public class Foo {
    public int x;
    public int y;
    public int Operate(OperationDelegate myDelegate) {
	return myDelegate.invoke(x, y);
    }
}

con esto si un cliente de la clase Foo quiere sumar los dos valores de x e y lo puede hacer así:

Foo f = new Foo();
f.x = 2;
f.y = 2;
OperationDelegate suma= new OperationDelegate() {
	public int invoke(int x, int y) { return Client.Suma(x, y);}
};
f.Operate(suma);
static int Suma(int x, int y)
{
	return x + y;
}

Notas:



  • Java no soporta ni expresiones lambda ni propiedades automáticas en los constructores.

  • Se ha usado una interface para declarar el “delegado”, también se podría haber hecho mediante una clase.
  • En la definición de los métodos de una inner class solo se puede acceder a variables de tipo final.
  • Evidentemente se pueden usar inner classes para definir delegates en C#, pero parece un poco absurdo, ¿no?
  • En este artículo podéis encontrar una implementación que simula el modo de operar de los delegates de C# en java.

4.- Conclusiones


El uso de inner classes nos proporciona una funcionalidad equivalente a la que proporcionan los delegates en lenguajes que no ofrecen soporte nativo a ellos. Sin entrar a fondo en motivaciones metafísicas, creo que la inclusión de los delegates como parte de la especificación del lenguaje ofrecen una ventaja a nivel de claridad y usabilidad notable.


Saludos! Xampi