viernes, 31 de octubre de 2014

Parte 2ª - Excepciones, tratamiento de errores en Lazarus

En la última publicación acordemos que veríamos una serie de situaciones comunes donde es necesario un control de errores y cómo se corrigen los mismos. Así que ahí es donde entraremos en materia a continuación ...


División por cero

Cada vez que hacemos una división estamos propensos al error que se produce si el divisor es cero. Según se trate de una división entera (div) o de una de punto flotante, se dispara una excepción eDivByZero o eZeroDivide, respectivamente.

Las siguientes funciones (líneas de abajo) permiten probar un método de recuperación para este tipo de error. En este caso no se presenta ningún mensaje al usuario, sólo se devuelve un valor por defecto.

function DivisionEntera (a,b: integer): integer;
begin
  try
    Result:= a div b;
  except
    on eDivByZero do
      Result:= 0;
  end;
end;

function DivisionReal (a,b:real): real;
begin
  try
    Result:= a/b;
  except
    on eZeroDivide do
      Result:= 0.0;
  end;
end;
Acceso a un archivo

Abrir un archivo y leer datos.
El siguiente listado abre un archivo y lee 50 bytes en un array, mostrando un mensaje si se produce cualquier error en el proceso(líneas después de Except).

var
  f: file;
  b: array[0..49 of byte];
begin
  try
    AssignFile(f,’nombre’);
    ResetFile(f,1);
    BlockRead(f,b,50);
    CloseFile(f);
  except
    on eInOutError do
      ShowMessage(‘Error al trabajar con el archivo’);
  end;
end;

Abrir un archivo. Bloques anidados
El listado siguiente se puede usar cuando necesitamos utilizar un archivo. Si existe, se abre y si no se crea. Tenemos dos bloques protegidos anidados, uno dentro del otro. El interior captura un error al abrir (por ejemplo, cuando no existe el archivo), mientras que el exterior intercepta un posible error de creación, presentando un mensaje al usuario y abandonando inmediatamente el procedimiento. 

Si todo va bien, podemos utilizar el archivo abierto (con posible comprobación de errores al leer o escribir) y finalmente se cierra el archivo.

Var
  f: file;
begin
  try
    try
      AssignFile(f,’ARCH.TXT’);
      Reset(f,1);
    except
      On eInOutError do
        Rewrite(f,1);
    end;
  except
    on eInOutError do 
    begin
        ShowMessage(‘No se puede crear el archivo’);
        Exit;
    end;
  end;

  {cerramos el archivo}
  CloseFile(f);
end;

Pero tenemos que tener claro que si se produce un error al abrir o leer el archivo, la instrucción de cierre no se ejecutará. En el caso de una lectura no hay mayores problemas, pero cuando modificamos el contenido del archivo es indispensable cerrarlo correctamente. Veremos más sobre esto al tratar el bloque de protección try..finally.


La estructura try ... finally

Como vimos en el ejemplo anterior de acceso a archivos, hay veces que es necesario ejecutar una porción de código (suceda un error o no). Para ello tenemos la estructura try..finally. Cuando se produce un error dentro de este bloque, se suspende el tratamiento de la excepción para ejecutar el código que sigue a finally. Luego de terminado, se sigue con el proceso normal de proceso del error.

La estructura es una variación de los bloques de protección que vimos antes, donde en lugar de except utilizamos la palabra reservada finally.

try
  {código expuesto a errores}
finally
  {código de ejecución obligatoria}
end;

En el ejemplo siguiente controlamos la excepción de un error mediante try..finally. Su finalidad es la siguiente, cuando realizamos una tarea que puede extenderse en el tiempo, es bueno indicarlo al usuario mediante el cursor de espera (el reloj de arena ... crHourglass). Al terminar la operación debemos restituir el cursor anterior. Ahora bien, si ocurre un error durante el proceso, la instrucción de restitución del cursor no se ejecutará nunca y el usuario puede quedarse horas esperando que el cursor le indique que puede seguir trabajando. 

Podemos evitar esta situación con el código del listado siguiente:

try
  screen.cursor:= crHourglass; {ponemos el cursor de espera}
  {aquí se hace el proceso}
finally
  screen.cursor:= crDefault; {restituimos el cursor por defecto}
end;

Pero hay que tener algo muy claro, si en la sección finally se produce un error, la ejecución saltará inmediatamente a la siguiente capa de protección (sección except correspondiente o al manejador por defecto de la aplicación). Por lo tanto, debemos evitar en esta sección utilizar código propenso a errores, o desactivar la detección de los mismos.

Los bloques try..except y try..finally pueden anidarse. Podemos ver un ejemplo de esta técnica
en el listado de más abajo. Este código nos permite ver en una ventana el texto de un archivo junto a los códigos hexadecimales que corresponden a cada carácter. La imagen del form principal, también os la pongo más abajo, despues del listado.

unit Excep_1;

interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, Buttons;
const
  Linea = 10;
type
  tArra = array[0..linea*10-1] of byte;
  TForm1 = class(TForm)
    Edit1: TEdit;
    Label1: TLabel;
    Memo1: TMemo;
    Button1: TButton;
    SpeedButton1: TSpeedButton;
    OpenDialog1: TOpenDialog;
    Label2: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
    procedure FormResize(Sender: TObject);
  private
    procedure MuestraHexa(b:tArra; Cant:word);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
implementation

{$R *.DFM}

procedure tForm1.MuestraHexa(b:tArra; Cant:word);
var
  i,j:word;
  s,s2:string;
  bt: byte;
begin
  i:= 0;
  while i*linea<Cant do 
  begin
    s:= '';
    s2:= '';
    for j:= 0 to linea-1 do 
    begin
      if i*linea+j>cant then break;

      bt:= b[i*linea+j];
      s:= s+IntToHex(bt,2)+' ';

      if bt>31 then
           s2:= s2+Chr(bt)
      else s2:= s2+' ';
    end;

    try
      Memo1.lines.add(s+s2);
    except
      on Exception do 
      begin
        ShowMessage('No se pueden ingresar más líneas');
        abort;
      end;
    end;

    inc(i);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
Var
  f: file;
  buf: tArra;
  leidos:integer;
begin
  try
    AssignFile(f,Edit1.text);
    Reset(f,1);
  except
    On eInOutError do 
    begin
      ShowMessage('No se puede abrir el archivo');
      exit;
    end;
  end;

  try
    try
      while not eof(f) do 
      begin
        BlockRead(f,buf,SizeOf(buf),Leidos);
        MuestraHexa(buf,leidos);
      end;
    except
      on eInOutError do
        ShowMessage('Error al leer el archivo');
    end;
  finally
    CloseFile(f);
  end;
end;

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
  begin
    edit1.text:= OpenDialog1.Filename;
  end;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  Memo1.height:= ClientHeight-64;
end;

end.


Uso de finally, ejemplos comunes

Repasaremos a continuación los casos más comunes que necesitan código de ejecución obligatoria.

Cerrar un archivo.
Retomemos el ejemplo de lectura de archivos que vimos antes. Como mencionamos allí, si se produce un error mientras accedemos al archivo o procesamos sus datos, no se ejecutará la orden CloseFile y el archivo permanecerá abierto. Esto puede ocasionar pérdida de datos ya que el sistema de directorios se vuelve inestable. Agreguemos el código para cerrar el archivo aunque se produzca un error

var
  f: file;
  b: array[0..49 of byte];
begin
  try
    try
      AssignFile(f,’nombre’);
      ResetFile(f,1);
      BlockRead(f,b,50);
    except
      on eInOutError do
        ShowMessage(‘Error al trabajar con el archivo’);
    end;  
  finally
    CloseFile(f);
  end;
end;

Liberar memoria.
Si creamos una estructura dinámica, es nuestra responsabilidad devolver la memoria utilizada al sistema. Veamos un ejemplo utilizando GetMem y FreeMem:

type
  aInt= array[0..0] of integer;
  paInt = ^aInt;
var
  a: paInt;
begin
  {solicitamos memoria para 10000 enteros}
  GetMem(a,SizeOf(integer)*10000);

  try
    {utilización de la memoria}
  finally
    if assigned(a) then FreeMem(a,SizeOf(integer)*10000);
  end;
end;

Utilizar el objeto de la Excepción

Cuando se dispara una excepción se crea una instancia de la clase correspondiente. Por lo tanto, podemos en principio tener acceso a las propiedades y métodos de la misma. La definición de la clase Exception es la siguiente (SYSUTILS.PAS):

Exception = class(TObject)
private
  FMessage: string;
  FHelpContext: Integer;
public
  constructor Create(const Msg: string);
  constructor CreateFmt(const Msg: string; const Args: array of
              const);
  constructor CreateRes(Ident: Integer); overload;
  constructor CreateRes(ResStringRec: PResStringRec); overload;
  constructor CreateResFmt(Ident: Integer; const Args: array of
              const); overload;
  constructor CreateResFmt(ResStringRec: PResStringRec; const Args:
              array of const); overload;
  constructor CreateHelp(const Msg: string; AHelpContext: Integer);
  constructor CreateFmtHelp(const Msg: string; const Args: array of
              const; AHelpContext: Integer);
  constructor CreateResHelp(Ident: Integer; AHelpContext: Integer);
              overload;
  constructor CreateResHelp(ResStringRec: PResStringRec;
              AHelpContext: Integer); overload;
  constructor CreateResFmtHelp(ResStringRec: PResStringRec; const
              Args: array of const; AHelpContext: Integer); 
              overload;
  constructor CreateResFmtHelp(Ident: Integer; const Args: array of
              const; AHelpContext: Integer); overload;
  property HelpContext: Integer read FHelpContext write 
           FHelpContext;
  property Message: string read FMessage write FMessage;
end;

Como vemos en el listado anterior, tenemos disponibles tres propiedades: una indica el contexto de ayuda, y las otras dos dan acceso al mensaje de error en los dos formatos utilizados en Windows. Veamos cómo utilizar estas propiedades para mostrar un mensaje personalizado. Para acceder al objeto de la excepción debemos utilizar una variable temporal. La forma de hacerlo es la siguiente:

try
  {Algún proceso}
except
  on e: eDivByZero do
    ShowMessage(‘Error!’#13+e.Message);
end;

Con este código no estamos creando una nueva instancia de la excepción; únicamente definimos una variable que apunta al objeto creado por Lazarus. La clase Exception (y la mayoría de los descendientes directos) tienen pocas propiedades de utilidad, pero nada nos impide en los descendientes creados por nosotros agregar otras nuevas. Esto es precisamente lo que hacemos con la clase eInOutError, definiendo una propiedad que almacena el código de error devuelto por el sistema:

EInOutError = class(Exception)
public
  ErrorCode: Integer;
end;

Por lo tanto, podemos saber cual fue el error que provocó la excepción. El siguiente fragmento indica cómo mostrar este código al usuario, a la vez que muestra una forma de asegurar el cierre del archivo:

var
  f:file;
  st: array[0..50] of char;
begin
  try
    try
      AssignFile(f,’PRUEBA.TXT’);
      Reset(f,1);
      BlockRead(f,st,SizeOF(st));
    except
      on e: eInOutError do
        ShowMessage( format(‘Error %d al acceder al archivo' +
                     PRUEBA.TXT’, [e.ErrorCode]));
    end;
  finally
    {$I-} {desactiva detección de errores}
    CloseFile(f); {puede dar error si f no está abierto}

    {$I+} {activa la detección de errores de nuevo}
  end;
end;

Note la utilización de {$I-} y {$I+} en la parte Finally, si el archivo no se pudo abrir se produce el error, se muestra el mensaje... y se ejecuta CloseFile sobre un archivo que no está abierto, lo que genera otro error. Para evitar que la ejecución salte al bloque protegido superior con este último error, desactivamos la detección de los mismos cuando tratamos de cerrar el archivo. De esta manera si el archivo está abierto se cierra normalmente, y si no está abierto no pasa nada. 

Una aplicación más elaborada podría mantener una lista con los mensajes de error correspondientes y utilizar el código de error como índice.

Provocar una excepción

Cuando se dispara una excepción se crea una instancia de la clase correspondiente. Por lo tanto, Hay ocasiones en las que es conveniente llamar a una capa de tratamiento de errores superior. Por ejemplo, podríamos mostrar un mensaje en un bloque local y después dejar que la capa superior libere recursos. Pero una vez que hemos tratado una excepción, ésta se da por terminada y el código de los bloques protegidos superiores no se ejecuta. Debemos indicar a Pascal que deseamos mantener la excepción para que pueda tratarla el bloque superior, algo así como llamar a un método heredado desde una clase descendiente.

Para lograrlo, el lenguaje contempla la opción de relanzar la excepción, como si se volviera a
producir. Sólo debemos insertar en el código la palabra reservada raise:

try
  StrToInt(‘a45');
except
  on exception do
  begin
    ShowMessage(‘Se ha producido un error’);
    raise;
  end;
end;

En el ejemplo anterior utilizamos raise para disparar la misma excepción que estabamos procesando. La ejecución salta inmediatamente al bloque protegido anterior.

También utilizamos raise para disparar nuestras propias excepciones, como veremos a continuación.

Podríamos expresarlo de otro modo, cuando se provoca una excepción, una vez la hemos procesado con la sentencia E:Exception, la ejecución continua hacia el siguiente bloque de código. Si queremos detener la ejecución del programa debemos utilizar el comando raise:

var
  F: TextFile;
begin
  AssignFile(F, 'C:\noexiste.txt');
  ShowMessage('1');

  try
    Reset(F);
  Except
    on E: Exception do
      raise;
  end;

  ShowMessage('2');
end;

En el ejemplo anterior nunca llegaría a ejecutarse el segundo ShowMessage ya que raise detiene la ejecución del procedimiento.


Definir nuevas excepciones

Cuando se produce un error en un procedimiento nuestro (ya sea de un objeto o no), podemos tratar de recuperarnos en el lugar o bien lanzar una excepción indicando que algo anduvo mal.

Esta segunda alternativa es la preferida, porque le permite al programador tratar todos los errores del mismo modo.

Para definir una nueva excepción, debemos crear un descendiente de Exception o alguna clase más especializada. En este descendiente podemos definir nuevas propiedades o métodos de la manera usual, lo cual nos permitirá un mejor tratamiento del error.

Vayamos a la práctica. Supongamos que implementamos una función que transforma un número entero en una cadena de unos y ceros que conforman su representación binaria, pero no queremos tratar los números negativos. Podemos entonces definir una excepción nueva y dispararla cuando detectamos que el número es incorrecto. No necesitamos ninguna propiedad ni método nuevo, por lo que la excepción puede ser un descendiente directo de Exception. No obstante, como se trata en realidad de un error durante una conversión, haremos que descienda de eConvertError:

type
  eSoloPosit = class (eConvertError)
  end;

y ahora escribimos el procedimiento de conversión:

function DecToBin(v:integer): string;
var
  temps: string;
begin
  if v<0 then
  begin
    raise eSoloPosit.Create('No se permiten números negativos');
  end;

  if v>0 then 
       temps:= ''
  else temps:= '0';

  while v>0 do 
  begin
    temps:= Chr(v Mod 2+48)+temps;
    v:= v div 2;
  end;

  result:= temps;
end;

El proyecto muestra la llamada de esta función desde un bloque protegido, en una aplicación simple de conversión:

procedure TForm1.Button1Click(Sender: TObject);
begin
  try
    Label3.caption:= DecToBin(StrToInt(edit1.text));
  except
    on e: eConvertError do
      ShowMessage('Error! '+e.Message);
  end;
end;

Note que el tratamiento jerárquico nos permite procesar también los errores de la conversión a entero con el mismo bloque.


jueves, 30 de octubre de 2014

Parte 1ª - Excepciones, tratamiento de errores en Lazarus

Bien, ha pasado ya tiempo desde que no toco algún tema por aquí. He mantenido todo mi tiempo y atención en otros asuntos. Así que continuaré detallando, de vez en cuando, un poquito lo que todos hemos visto alguna vez que debemos conservar. Así que intentaré crear una pequeña recopilación de las ideas recogidas de otros blog's sobre programación.

Para ser sincero y que me perdone su autor, parte de la información que estoy publicando en esta publicación pertenece a un autor desconocido para mi. En su día fue un manual que recopile en formato pdf y que no apunté su procedencia. Así que si alguien reconoce a su autor, agradecería me lo hiciese llegar para detallar aquí a su autor original. Otra parte de su contenido la fuente ha sido substraida de otro blog bastante interesante, Delphi al Límite, muy nombrado en www.clubdelphi.com

Hoy trataré el tema de las excepciones en Pascal. ¿Porqué?, pues porque en toda aplicación alguna vez se producirá un error y entre tanto código ya desarrollado, nos puede costar tiempo y dolores de cabeza protegernos de los errores. Por lo que quiero dejar claro que es indispensable contar con algún método para protegerse de ellos.

Utilizaremos un programa muy simple que calcula el cuadrado de un número entero. A continuación vemos el form principal y el único método del programa, asignado al evento OnClick del botón:


procedure TForm1.Button1Click(Sender: TObject);
var
  i:integer;
begin
  i:= StrToInt(edit1.text);
  label1.caption:= format('El cuadrado es: %d',[i*i]);
end;

   Cuando pulsamos el botón, se convierte el texto (introducido en el TEdit) en un número entero, lo eleva al cuadrado y se muestra el resultado en el TLabel. Pero si escribimos algo que no sea un número entero (incluso dejando vacío el TEdit),se producirá un error de conversión.

   Pascal (en este caso Lazarus / freePascal) procesa este error mostrando un pequeño mensaje:


   Como vemos, no pasa nada grave. La respuesta por defecto de a las excepciones es sólo mostrar el mensaje correspondiente en una caja de diálogo como la anterior.

   Pero y si nos interesara controlar las excepciones, ¿cómo lo conseguiríamos?


Cambiar la respuesta por defecto a las excepciones

   Para modificar la respuesta estándar ante un error debemos escribir un procedimiento de respuesta al evento OnException, que se produce en el objeto Application

   El objeto Application no es un componente visible; se crea automáticamente cada vez que se ejecuta una aplicación y se referencia con la variable global Application. Para indicar que queremos que se ejecute un método al producirse el evento OnException, debemos entender que los eventos son propiedades a las que se les puede asignar un valor (en el caso de los eventos, un procedimiento que se ejecutará al producirse el evento). La definición de la propiedad OnException nos indica qué tipo de método podemos asignarle. En el caso de OnException:

property OnException: TExceptionEvent;

El tipo TExceptionEvent es un tipo procedural:

TExceptionEvent = procedure (Sender: TObject; E: Exception) of object;

Por lo tanto, el procedimiento debe esperar dos parámetros, uno de tipo tObject y otro de tipo
Exception, y debe ser un método de un objeto (el método puede pertenecer a cualquiera de los forms que componen el proyecto). Escribamos entonces un método nuevo al form principal:

procedure tForm1.TratarExcepciones(sender:tObject; e:Exception);
begin
  MessageDlg('Se ha producido un error. Por favor intente de nuevo',
    mtError,[mbOk],0);
end;

   La declaración de este método debe ser escrita en la definición del tipo Tform1. A continuación detallamos la clase entera:

TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    procedure Button1Click(Sender: TObject);
  private
    procedure TratarExcepciones(sender:tObject; e:Exception);
  public
    { Public declarations }
  end;

   Haremos la asignación al crear el form principal, en el evento OnCreate:

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnException:= TratarExcepciones;
end;

Bien, ahora el programa presenta un mensaje más amable cuando se produce un error de cualquier tipo. El paso siguiente es tratar los errores, respondiendo de manera diferente a cada uno de ellos. Podemos ver de qué tipo es una excepción mirando la clase a que pertenece el parámetro E.

Debemos primero repasar la jerarquía de clases de excepciones definida en Pascal.


Clases de excepciones

Las excepciones se pueden dividir en las siguientes categorías:
  • Conversión de tipo. Se producen cuando tratamos de convertir un tipo de dato en otro, por ejemplo utilizando las funciones IntToStr, StrToInt, StrToFloat. Se dispara una excepción EConvertError.
  • Tipo forzado (typecast). Se producen cuando tratamos de forzar el reconocimiento de una expresión de un tipo como si fuera de otro usando el operador as. Si no son compatibles, se dispara la excepción EInvalidCast.
  • Aritmética de punto flotante. Se producen al hacer operaciones con expresiones de tipo real. Existe una clase general para este tipo de excepciones EmathError, pero Lazarus utiliza sólo los descendientes de ésta:
    • EinvalidOp: el procesador encontró una instrucción inválida.
    • EzeroDivide: división por cero.
    • Eoverflow: se excede en más la capacidad aritmética (números demasiado grandes).
    • Eunderflow: se excede en menos la capacidad aritmética (números demasiados
    • queños).
  • Aritmética entera. Se producen al hacer operaciones con expresiones de tipo entero. Existe una clase general definida para este tipo de excepciones llamada EintError, pero Lazarus sólo utiliza los descendientes:
    • EDivByZero: división por cero.
    • ERangeError: número fuera del rango disponible según el tipo de dato. La comprobación de rango debe estar activada (indicador $R).
    • EIntOverflow: se excede en más la capacidad aritmética (números demasiado grandes). La comprobación de sobrepasamiento debe estar activada (indicador $O).
  • Falta de memoria. Se producen cuando hay un problema al acceder o reservar memoria. Se definen dos clases:
    • EOutOfMemory: no hay suficiente memoria disponible para completar la operación.
    • EInvalidPointer: la aplicación trata de disponer de la memoria referenciada por un puntero que indica una dirección inválida (fuera del rango de direcciones permitido a la aplicación). Generalmente significa que la memoria ya ha sido liberada.
  • Entrada/Salida. Se producen cuando hay un error al acceder a dispositivos de entrada/salida o archivos. Se define una clase genérica EInOutError con una propiedad que contiene el código de error ErrorCode.
  • Hardware. Se producen cuando el procesador detecta un error que no puede manejar o cuando la aplicación genera intencionalmente una interrupción para detener la ejecución. El código para manejar estas excepciones no se incluye en las DLL compiladas, sólo en las aplicaciones. Se define una clase base que no es directamente utilizada EProcessorException. Las clases útiles son los descendientes:
    • EFault: es una clase base para todas las excepciones de faltas del procesador.
    • EGPFault: error de Protección General, cuando un puntero trata de acceder posiciones de memoria protegidas.
    • EStackFault: acceso ilegal al segmento de pila del procesador.
    • EPageFault: el manejador de memoria de Windows tuvo problemas al utilizar el archivo de intercambio.
    • EInvalidOpcode: el procesador trata de ejecutar una instrucción inválida.
    • EBreakpoint: la aplicación ha generado una interrupción de la ejecución (punto de ruptura, utilizado por el debugger de Lazarus para inspeccionar las variables en un punto).
    • ESingleStep: la aplicación ha generado una interrupción de ejecución paso a paso. Luego de cada paso de programa se produce la interrupción. Es utilizada también por el debugger.
  • Excepciones silenciosas. Se disparan intencionalmente por la aplicación para interrumpir el flujo de ejecución. No generan mensajes de error. Se define una clase EAbort. Es digno de mencionar que esta excepción es automáticamente generada cuando invocamos el procedimiento global Abort, que lo podemos usar para interrumpir la ejecución del programa en cualquier punto. Por ejemplo, para dar un toque profesional a un programa hay ocasiones en que nos interesa controlar la excepción pero que no se entere el usuario del programa. Lo que no se puede hacer es abandonar la excepción con Break o con Exit ya que puede ser peor el remedio que la enfermedad. Habría que como hemos visto anteriormente usar Abort:
    try
      {sentencias}
    except
      Abort;
    end;

Como podemos ver en la imagen siguiente, la jerarquía de clases descendientes de Exception es bastante amplia, sin embargo muchos de los descendientes son de uso interno de los componentes y no los trabajamos directamente. 


Otras excepciones no tratadas anteriormente, pero que las he encontrado en otro blog (Delphi al límite) son las siguientes:

EAccessViolation: Comprueba errores de acceso a memoria inválidos.
EBitsError: Previene intentos para acceder a arrays de elementos booleanos.
EComponentError: Nos informa de un intento inválido de registar o renombar un componente.
EDatabaseError: Especifica un error de acceso a bases de datos.
EDBEditError: Error al introducir datos incompatibles con una máscara de texto.
EExternalException: Significa que no reconoce el tipo de excepción (viene de fuera).
EIntOutError: Representa un error de entrada/salida a archivos.
EInvalidGraphic: Indica un intento de trabajar con gráficos que tienen un formato desconocido.
EInvalidOperation: Ocurre cuando se ha intentado realizar una operación inválida sobre un componente.
EMenuError: Controla todos los errores relacionados con componentes de menú.
EOleCtrlError: Detecta problemas con controles ActiveX.
EOleError: Especifica errores de automatización de objetos OLE.
EPrinterError: Errores al imprimir.
EPropertyError: Ocurre cuando se intenta asignar un valor erroneo a una propiedad del componente.
ERegistryExcepcion: Controla los errores en el resigtro.

Bien, con todo lo que hasta ahora hemos podido ver podríamos tratar las excepciones que se produzcan y responder a cada clase de manera diferente si fuera necesario. Este tratamiento se utiliza para responder a distintas excepciones en el lugar donde se producen, de manera tal que podamos recuperarnos del error y poder proseguir el código. El ejemplo más común es el error de lectura de un archivo, normalmente permitiremos al usuario reintentar la operación además de cancelarla. Pero recordemos que el evento OnException se produce en el objeto Application, después de lo cual la ejecución queda a la espera de nuevos eventos. Debemos encontrar una forma de detectar y corregir el error sin abandonar el procedimiento en curso.

Esto se logra mediante la protección de bloques de código. 


Bloques de código controlados
Try ... Except ... End

Para proteger una porción de código debemos encerrarla en un bloque try...except. Entre estas dos palabras reservadas se ubica el código que está expuesto a errores; después de except se procesan estos últimos, cerrando todo el bloque con end. La sintaxis sería:

try
  {Bloque de código propenso a errores}
except
  on <clase de excepción> do
  {una sola instrucción o un bloque begin..end}

  on <otra excepción diferente> do
  begin
  end;
end;

Por ejemplo, en la aplicación que creamos al principio, cuando se producía un error la ejecución saltaba al procedimiento TratarExcepciones, y el texto del label 1 no se actualizaba. Podríamos cambiar el mensaje del label cuando se produce la excepción. ¿Cómo?, cambiando el código de respuesta al botón "Convertir" por el del listado siguiente:

procedure TForm1.Button1Click(Sender: TObject);
var
  i:integer;
begin
  try
    i:= StrToInt(edit1.text);
    label1.caption:= format('El cuadrado es: %d',[i*i]);
  except
    on eConvertError do
    Label1.Caption:= 'Número erróneo';
  end;
end;

Los bloques protegidos forman como capas al resto del programa, cuando se produce una excepción en un lugar del código la ejecución salta directamente a la capa de protección donde se está ejecutando.

Si el error no se procesa en esta capa se pasa a la siguiente, y así sucesivamente. En Lazarus las aplicaciones corren dentro de un bloque protegido, por lo que todas las excepciones no tratadas se procesan en el objeto Application.

En la imagen siguiente observamos un esquema de una aplicación con varios bloques protegidos y cómo se redirige la ejecución cuando se produce un error.


En el gráfico vemos que los errores que se producen fuera de las estructuras try..except son tratadas en la parte correspondiente al bloque anterior y que en última instancia se procesan en el evento OnException de la aplicación. Es como si toda la aplicación estuviera encerrada en un gran bloque try..except.

La técnica de declarar como clases a las excepciones permite el tratamiento jerárquico de las mismas. Esto quiere decir que un manejador de una clase de excepciones trata también las clases descendientes, con un código como el del listado:

Try
  {código}
except
  on <clase de la excepción> do
  {manejar la excepción o sus descendientes}

  on <otra clase> do
end;

Por ejemplo si queremos atrapar cualquier error aritmético que se pueda producir en una operación, podemos escribir:

try
  {operación}
except
  on eMathError do
    ShowMessage('Error de punto flotante');

  on eIntError do
    ShowMessage('Error aritmético entero');
end;

Podemos hacer algo más que mostrar un mensaje, por ejemplo asignar al resultado de la operación un valor por defecto.

Pero ahora imaginemos que queremos saber el tipo de excepción y sacarlo por pantalla, pues podríamos usar estas líneas de abajo:

var
  s: string;
  i: Integer;
begin
  s := 'prueba';

  try
    i := StrToInt( s );
  except
    on E: Exception  do
      Application.MessageBox( PChar( E.ClassName + ': ' + 
          E.Message ), 'Error', MB_ICONSTOP );
  end;
end;

Hemos incluido en el mensaje de la excepción la clase y el mensaje de error. Este sería el resultado:

EConvertError: 'prueba' not is a valid integer value

Así, mediante la propiedad ClassName de la clase Exception podemos averiguar la clase a la que pertenece la excepción. Ahora mediante la setencia on podemos aislar la excepción de la forma:

on tipo do sentencia

En nuestro caso sería así:

  try
    i := StrToInt( s );
  except
    on E: EConvertError  do
      Application.MessageBox( 'Error de conversión', 'Error',
                  MB_ICONSTOP )
    else
      Application.MessageBox( 'Error desconocido', 'Error', 
                  MB_ICONSTOP );
  end;

Si se produjera una excepción que no fuese de la clase EConvertError mostraría el mensaje Error desconocido.

De este modo podemos aislar dentro de un mismo bloque de código los distintos tipos de excepción que se puedan producir.

En próximas publicaciones, veremos una serie de situaciones comunes donde es necesario un control de errores y cómo se corrigen los mismos.

Hasta la próxima y gracias por vuestra atención.