aprendiendo ( Erlang ).

viernes, 9 de septiembre de 2011

try...catch. Manejo de excepciones I

| 0 comentarios |

Erlang levanta automáticamente una excepción cuando se produce un error. Los errores típicos son el pattern matching o llamada a funciones con un tipo incorrecto en los argumentos.

Nosotros podemos generar un error o elevar una excepción con las siguientes funciones:
  • exit(Motivo): esta función es utilizada cuando se desea terminar el proceso actual. Si esta excepción no es capturada, el mensaje {'EXIT',Pid,Motivo} se difundiría a todos los procesos que están linkados al proceso actual.
  • throw(Motivo): es usado para elevar una excepción. El usuario de una función que eleva una excepción tiene dos posibilidades: ignorarla o, tratarla con un try ... catch.
  • erlang:error(Motivo): es usado para errores graves. Y no se espera de manejo por parte del usuario de la función que lo utiliza.
Es decir, existen tres tipos de excepciones o errores que podemos capturar: exit, throw o error.

Try...Catch

Supongo que este tipo de sentencia os resultará familiar. Según Joe Armstrong: se trata de una sentencia case con esteroides. Su sintaxis es:
try funciónOExpresión of
  Patrón1 [when Gruada1] -> Expresiones1;
  ...
  PatrónN [when GuardaN] -> ExpresionesN
catch
  TipoExcepcion1: ExcepciónPatrón1 [when ExcepciónGuarda1] -> ExcepciónExpresiones1;
  ...
  TipoExcepciónN: ExcepciónPatrónN [when ExcepciónGuardaN] -> ExcepciónExpresionesN
after
  AfterExpresiones
end
Vemos como funciona la sentencia try...catch:
  • Evalúa la función o expresiones englobadas en try...of.
    • Si finaliza sin lanzar excepciones, tenemos el mismo comportamiento que en una sentencia case, comprueba la coincidencia con el patrón1 (condicionado a una guarda) y si se produce la coincidencia ejecuta la secuencia de comandos correspondiente.
    • Si se produce una excepción pasa a la sentencia catch e intenta a hacer pattern matching con una ExcepciónPatrónX, condicionado a una guarda y al tipo de excepción producida.
  • El código a continuación del after se ejecutará una vez terminada la evaluación de la sentencia try...catch. Está garantizada su ejecución aunque se produzcan excepciones. A tener en cuenta, que el valor de retorno de este bloque será ignorado.

Si queremos podemos omitir alguna/s parte/s de la sentencia, por ejemplo:
try funciónOExpresión
catch
  TipoExcepcion1: ExcepciónPatrón1 [when ExcepciónGuarda1] -> ExcepciónExpresiones1;
  ...
  TipoExcepciónN: ExcepciónPatrónN [when ExcepciónGuardaN] -> ExcepciónExpresionesN
end

Veamos un ejemplo básico de try...catch.
-module(trycatch).
-compile(export_all).

provocar_error(1) ->
    a;
provocar_error(2) ->
    throw(a);
provocar_error(3) ->
    exit(a);
provocar_error(4) ->
    {'EXIT', a};
provocar_error(5) ->
    erlang:error(a).


prueba() ->
    [capturar(I) || I <- [1,2,3,4,5]].

capturar(N) ->
    try provocar_error(N) of
        Val -> {N, noCapturado, Val}
    catch
        throw:X -> {N, capturado, throw, X};
        exit:X -> {N, capturado, exit, X};
        error:X -> {N, capturado, error, X}
    end.

1> c(trycatch). {ok,trycatch} 2> trycatch:prueba(). [{1,noCapturado,a}, {2,capturado,throw,a}, {3,capturado,exit,a}, {4,noCapturado,{'EXIT',a}}, {5,capturado,error,a}]
La función trycatch:provocar_error/1 se encarga de provocar o lanzar un error que capturaremos en la función trycatch:capturar/1.
Como podemos comprobar la función trycatch:provocar_error/1 no levanta una excepción para la llamada 1 y 4 que pasan al bloque try y en el resto pasan a ser tratadas por el bloque catch.
Otra manera de capturar las excepciones es realizando un uso creativo de la primitiva catch. Cuando capturas una excepción, esta se convierte en una tupla que describe el error. Veamos un ejemplo, ya que así dicho queda poco claro:
prueba2() ->
[{I, (catch provocar_error(I))} || I <- [1,2,3,4,5]].

3> c(trycatch). 4> trycatch:prueba2(). [{1,a}, {2,a}, {3,{'EXIT',a}}, {4,{'EXIT',a}}, {5, {'EXIT',{a,[{trycatch,provocar_error,1}, {trycatch,'-prueba2/0-lc$^0/1-0-',1}, {trycatch,'-prueba2/0-lc$^0/1-0-',1}, {erl_eval,do_apply,5}, {shell,exprs,6}, {shell,eval_exprs,6}, {shell,eval_loop,3}]}}}]
Como se puede observar, efectivamente se han generado las tuplas. Lo curioso del tema, es que el resultado de capturar las llamadas con la primitiva catch es igual para las llamadas 1 y 2 (también para 3 y 4). Si confrontamos los resultados con la primera prueba podemos observar que una de las repeticiones fue capturada y otra no, esto es debido a que una eleva una excepción y la otra es un simple resultado que coincide con el que se captura. También es importante saber que cuando capturamos una excepción del tipo error (última tupla) perdemos la pila de llamadas.

Programación con tratamiento de errores

En Erlang existen básicamente dos formas de manejar los errores:
  1. La función llamada retorna el error: en Erlang existen funciones que devuelve algo como {ok, Valor}, o en caso de error {error, Motivo}. Para este tipo de funciones podemos utilizar una sentencia case para tratar el posible error o bien, ignorar el caso de error para que lo trate otro.
    devuelveError(0) ->
        {error, valor_cero};
    devuelveError(N) ->
        {ok, N}.
    
    tratarDevuelveError(N) ->
        case devuelveError(N) of
            {ok, Valor} ->
                io:format("valor: ~p~n", [Valor]); %% tratamos el valor
            {error, Motivo} ->
                io:format("error: ~p~n", [Motivo]) %% tratamos el error
        end.
    
    ignorarDevelveError(N) ->
        {ok, Valor} = devuelveError(N), %% ignoramos el error
        io:format("valor: ~p~n", [Valor]).
    
    5> lists:map (fun trycatch:tratarDevuelveError/1, [0,1]). error: valor_cero valor: 1 [ok,ok]
  2. La función llamada eleva excepciones: se trata del caso típico, donde la función llamada puede provocar una excepción.
    elevaExcepcion(N) ->
        case N of
            0 ->
                throw({errorCero, N, "Un mensaje por ejemplo"});
            1 ->
                throw({errorUno, N, eraUno});
            2 ->
                throw({errorDos, N});
            _ ->
                {ok, N}
        end.
    
    tratarExcepcion(N) ->
        try elevaExcepcion(N) of
            {ok, Valor} ->
                io:format(" todo ok : ~p~n", [Valor])
        catch
            throw:{errorCero, Valor, Mensaje} ->
                io:format(" Capturado errorCero : ~p - ~p~n", [Valor, Mensaje]);
            throw:{errorUno, Valor, Atomo} ->
                io:format(" Capturado errorUno : ~p - ~p~n", [Valor, Atomo]);
            throw:{errorDos, Valor} ->
                io:format(" Capturado errorDos : ~p~n", [Valor])
        end.
    
    6> lists:map (fun trycatch:tratarExcepcion/1, [0,1,4,5]). Capturado errorCero : 0 - "Un mensaje por ejemplo" Capturado errorUno : 1 - eraUno todo ok : 4 todo ok : 5 [ok,ok,ok,ok]

Publicar un comentario en la entrada

0 comentarios:

 
Licencia Creative Commons
Aprendiendo Erlang por Verdi se encuentra bajo una Licencia Creative Commons Atribución-NoComercial-CompartirIgual 3.0 Unported.