07 noviembre, 2006

Manejo de Excepciones

Un método puede ejecutarse y tener un resultado exitoso o no. Supongamos un método como el siguiente:
  • public boolean login(String user, String password) { … }
Este método retorna true si user/password son correctos o false si al menos uno de los datos son incorrectos.

El método sería perfecto de no ser por un detalle: ¿Que pasa si no se puede verificar el usuario/password porque (por ejemplo) la base de datos está caida? ¿Retornamos false?

Para este método, cualquiera sea el resultado (true o false) implica que el método se ejecutó correctamente. No estamos contemplamdo la posibilidad de que el método no haya podido realizar su trabajo por cuestiones ajenas al sistema.

Lo anterior nos lleva a pensar que el método login() retorna true o false según los parámetros que se le pasen sean correctos o no. Y si por algún motivo el método no puede realizar la verificación entonces debe abortarse abruptamente. Debe tirar una excepción.



En este ejemplo estamos suponiendo que disponemos de un método que recibe usuario y password y retorna cero si la verificación fue exitosa, mayor que sero si los datos pasados no corresponden a los que existen en el sistema y menor que cero y por algún motivo no se pudo verificar. En este caso corresponde tirar una excepción ya que no podemos retornar ni true ni false.

Una función main que use lo anterior podría ser:



Vemos que podemos utilizar libremente el método login() de la clase Usuario. Si el método tira una excepcion entonces no se llegará a imprimir ningún resultado porque el programa terminará al intentar realizar la llamada al método.


La cláusula throws

Podemos obligar al llamador de nuestro método a “hacerce cargo” de la excepción que (en caso de error) vamos a disparar. Para esto tenemos de prototipar el método login() con la cláusula: throws.



En este ejemplo hay dos diferencias con el ejemplo anterior:
  1. En la firma (o prototipo) del método agregamos throws Exception. Esto obligará al llamador del método a encerrar la llamada en un bloque try – catch.
  2. La excepción que disparamos es Exception en lugar de RuntimeException.
RuntimeException es la única excepción que se puede disparar sin tener que declararla en la firma del método. Cualquier otra excepción debe declararse y por lo tanto el llamador deberá tratarla como veremos a continuación.



Claro que también podríamos “dejarla pasar”: propagarla. Si la propagamos obligamos al llamador del método a hacerce cargo de la excepción que estamos propagando. (en este caso el llamador es la máquina virtual porque estamos propagando la excepción en la función main).




Excepciones Definidas por el Programador

Las excepciones son clases que heredan de la clase base Exception. Por lo tanto podemos programar nuestras propias excepciones.



ahora en el método login(), en cado de error tiramos nuestra propia excepción:



entonces en la función main tenemos que try-cachear ErrorFisicoException




Algunos Ejemplos Típicos

El código que se muestra a continuación corresponde a un pequeño servidor.



Vemos que primero instancia un ServerSocket. El constructor de ServerSocket está prototipado con un throws IOException por lo tanto tenemos que hacernos cargo de manejar esa excepción. En nuestro caso la estamos dejando pasar con el throws Exception que pusimos en el main. Decimos entonces que si el ServerSocket falla al momento de contruirlo el server termina y el programa “sale por el throws”.

Luego utilizamos los métodos accept() (de ServerSocket) y getOutputStream() (de Socket). Estos métodos también están definidos con un throws IOException. Sin embargo encerramos las llamadas dentro de un bloque try-catch. Con esto nos aseguramos que si falla un cliente en particular el server siga funcionando para atender a los próximos clientes.


El bloque try-catch-finally

Opcionalmente podemos incorporar el finally al try-catch. El finally nos garantiza que siempre se va a ejecutar el código que encerremos allí.



En el código anterior vemos que, pese al return que hace finalizar la función main, primero se ejecuta el finally y luego el return. La salida del programa será:

hola-chau
esto sale siempre !!!


Veamos otro ejemplo (más sofisticado):



La llamada a Integer.parseInt(“esto-tira-un-NumberFormatException”) tira una excepción de tipo NumberFormatException porque no podrá convertir ese string a número. Si bien el código prevee que va a ocurrir un error y por eso define el catch(ArrayIndexOutOfBoundsException) es obvio que el error será diferente al esperado. Es decir: al intentar el Integer.parseInt(…) el programa sale por el throws. Pero primero pasa por el finally.

La salida es:



Por último, veamos como utilizamos el finally para asegurarnos de cerrar la conexión con una base de datos. Esto lo veremos más en detalle en el capítulo de JDBC.