‒ Excepciones.
Las excepciones son clases que se usan para manejar los errores de programación, la mayoría de las excepciones, se encuentran en la unidad sysutils. Una de las ventajas de usar excepciones, es el permitir hacer un código más ordenado para el manejo de los errores de programación, separando el flujo normal de un conjunto de instrucciones, del conjunto de instrucciones que manejan los errores de programación. Es decir, si hacemos un programa tan simple, que nos permita dividir dos números reales ingresados por teclado, y tomando en cuenta los siguiente errores de programación:
- El usuario ingresa datos no numéricos.
- El usuario ingresa dos ceros a dividir 0/0, una operación de punto flotante invalida.
- El usuario intenta dividir un numero cualquiera con cero, n/0.
El programa se podría hacer del siguiente modo:
El tratamiento de los errores se hace a través de una serie de if/then/else anidados, que son como una serie de compuertas que verifican si los datos ingresados son correctos, en caso contrario sean correctos realizará la división.
Para hacer lo mismo usando excepciones, se debe primero incluir la unidad sysutils, y usar la estructura try/except para el manejo de excepciones. A continuación el programa de ejemplo:
Las instrucciones, que se usan para la lectura de datos, conversión y para la división se colocan dentro del bloque try, y dentro del bloque except, se colocan las excepciones que se manejarán cuando el usuario coloque datos incorrectos al realizar la operación. Una de las ventajas de usar este modo de tratar los errores de programación, es el de tener un código claro y bastante manejable, sobre todo cuando los programas requieren de muchas comprobaciones para evitar los errores de programación.
Cada vez que se produce un error como por ejemplo la división por cero, el flujo del programa se interrumpe y se ejecuta la excepción, en este caso la excepción EdivByZero.
Toda Excepción, se debe colocar entre las palabras reservadas on y do, después del do se puede colocar también un bloque de instrucciones, que debe empezar con Begin y terminar con End y su punto y coma.
Una excepción puede tener un identificador tal como se muestra a continuación:
on E:EConvertError do Writeln(E.Message);
on E:EDivByZero do Writeln(E.Message);
on E:EInvalidOp do Writeln(E.Message);
De esa manera se puede acceder a los métodos de una excepción, pero sólo dentro del bloque de instrucciones que aparecen después del do, en este ejemplo se accede a la propiedad Message de la clase correspondiente. Los identificadores, pueden ser el mismo identificador para cualquier excepción, ya que este identificador sólo se usará dentro del bloque de instrucciones después del do.
A diferencia del programa anterior en donde se usa Val para convertir la cadena de caracteres en un número, en este caso se hace uso de StrTofloat, esta función no devuelve un valor numérico cuando se produce un error, si no que se ejecuta una excepción, la excepción es EconvertError.
Para lo que viene a continuación se necesita de la siguiente unidad Complejos06.pp, esta unidad tiene la Clase TComplejo, la clase TComplejo tiene los métodos para realizar operaciones sobre números complejos, de entre ellos tenemos la división de números complejos. El siguiente programa hace uso de esta clases, para hacer una división errónea entre 0.
Al ejecutar el programa, este no ejecutará, las instrucciones correspondiente a EdivByZero, si no que por el contrario ejecutará las que corresponden a EInvalidOp, esto es debido a la manera en como se dividen los números complejos. Observando el código del método División de la clase TComplejo de la unidad Complejos06.pp:
Se puede observar que lo primero que hace este método es obtener la raíz cuadrada de la parte real del divisor sumado con la raíz cuadrada de la parte imaginaria del divisor, y como el divisor es 0, entonces al intentar obtener la raíz cuadrada de 0, se ejecuta o se lanza la excepción EinvalidOp. Podemos cambiar este comportamiento, haciendo uso de la palabra reservada raise:
Ahora cuando ejecutemos el programa, que divide un numero complejo con 0, entonces al producirse el error se lanzará la excepción EdivByZero. Cuando creamos una excepción, debemos incluir el mensaje, ya que si no la instrucción E.Message del programa no mostrará el mensaje. El siguiente archivo Complejos07.pp, tiene la versión modificada del método Division.
No siempre podemos predecir con certeza, que error puede ocurrir en determinado momento en la ejecución del programa, si por ejemplo no supiéramos que la división 0/0 generá la excepción EinvalidOp, entonces para poder saberlo, debemos de capturar de algún modo la excepción desconocida. Esto se puede hacer con la siguiente instrucción:
on E: Exception do Writeln(E.classname,'::',E.Message);
En donde el identificador E es de la clase Exception, el método classname nos da el nombre de la excepción, y como una excepción es una clase este método en realidad nos da el nombre de la clase. Classname es un método que toda clase tiene por defecto o por omisión.
El método message nos da el mensaje de error, este método es virtual y sólo pertenece a la clase Exception. Toda excepción, es en realidad descendiente de la clase Exception. Asumiendo que no sabemos que la división 0/0 genera la excepción EinvalidOp, se modifico el programa anterior, a modo de ejemplo:
Al ejecutar el programa, cada vez que ingresemos una división 0/0, nos mostrará el nombre de la clase (EinvalidOp) y el mensaje de error de la excepción generada.
Una vez conocido la excepción, el programa se debe modificar para que de un tratamiento, más adecuado a la excepción. Es importante entender que es recomendable usar:
on E: Exception do Writeln(E.classname,'::',E.Message);
sólo para saber de algún error que no hayamos podido predecir. Si sabemos del error no es necesario usar la clase Exception.
El último bloque para el manejo de las excepcione es el bloque try/finally, a diferencia de lo que hemos visto este tiene la particularidad de que si ocurre un error el programa se detiene y ejecuta lo que se encuentra en el bloque finally, y si en caso no ocurre un error, el programa no se detiene pero ejecuta lo que esta en el bloque finally. Es decir, así haya o no haya un error lo que se encuentra en el bloque finally siempre se ejecuta.
Este bloque es muy útil para realizar tares de limpieza en la manipulación de archivos, liberación de memoria cuando se trabaja con punteros o se destruyen objetos. Veamos el siguiente ejemplo:
En este ejemplo, dentro del bloque try se realiza un cálculo erróneo ocasionando una división con cero, lo que hará que se ejecute lo que se encuentre en el bloque finally, asumiendo que no ocurra el error siempre se ejecutará lo que se encuentra dentro del bloque finally. Es decir el programa siempre cerrará el archivo, así se produzca o no un error. El inconveniente que presenta la estructura try/finally, es que no permite manejar o gestionar las excepciones. La solución si queremos gestionar las excepciones, es usar los bloques try/except y try/finally anidados, como se muestra en el siguiente ejemplo: