Está en la página 1de 9

Control de excepciones en Delphi

Introduccin
Volviendo al tiempo en que apareci Delphi, el control de excepciones supuso un cambio fundamental tanto en la manera en que concebamos como en la que escribamos nuestro cdigo. Desafortunadamente, despus de diez aos de uso, aun existen errores de concepcin sobre cmo funcionan las excepciones y, especialmente, como deben ser usadas. En este artculo repasar el modo en que se debe llevar a cabo el control de excepciones. Empezar con ejemplos de mal uso de las excepciones. A partir de estos ejemplos, explicar las mejores tcnicas de gestin de excepciones. Usado incorrectamente, la gestin fe excepciones puede ser la causa de ms problemas de los que puede prevenir. Usado de manera correcta, ayudar a escribir cdigo limpio, bien diseado y escalable. Dar por supuesto que el lector est familiarizado con la sintaxis del control de excepciones y que conoce lo fundamental acerca del sistema de excepciones

Control de excepciones Estructurado


Una excepcin es una caracterstica del sistema operativo que permite al programador detener la ejecucin de un proceso inmediatamente e interceptar esa parada en cualquier punto de la pila de llamadas que desee. El control de excepciones estructurado es una combinacin de caractersticas del sistema operativo y de un buen diseo, que hace uso de las excepciones para implementar aserciones en el momento de la codificacin y, sobretodo, asegura que se responda de manera correcta en caso de que estas aserciones no se cumplan. Estas aserciones comnmente son precondiciones - se tienen que cumplir para que un mtodo tenga xito. Por ejemplo, un mtodo que borra un registro de una base de datos puede tener como precondicin que el usuario se haya conectado a la base de datos antes de ejecutarlo. Como la interfaz de usuario del programa est estructurada de tal modo que el mtodo solamente se puede llamar despus de conectarse, el programador puede asumir que la precondicin se cumplir siempre. Pero que pasara si en el servidor de bases de datos se produce un error y este deja de responder? El programador podra solucionar este problema comprobando la conexin al servidor al inicio de cada mtodo. Pero pueden existir otras precondiciones y esto llevar a repetir cdigo, o el programador puede olvidarse de hacer todas las comprobaciones al escribir un nuevo mtodo. El control de excepciones estructurado permite con soluciones elegantes para cada uno de estos casos. Asegura que el mtodo fallar si las precondiciones no se cumplen, es ms, fallar de tal modo que el programador podr reconocer el error y responder en consecuencia. Buena parte del trabajo de consultora que llevo a cabo incluye trabajar en proyectos que estn dando problemas (la mayora de veces debido a un diseo pobre) y rescribiendo

cdigo que no se program bien la primera vez. Un de los errores de cdigo ms comunes con el que me encuentro es el uso errneo de las excepciones (algunas veces tristemente errneo). Para empezar, explicar unas cuantas reglas sobre lo que no se ha de hacer en el control de excepciones y por qu no se ha de hacer.

No comerse excepciones
Probablemente el error ms comn con el que me encuentro es comerse las excepciones. Frecuentemente veo cdigo como ste:
try AlgunaRutinaQueAVecesCausaUnAccessViolationDificilDeEncontrar; except end;

Ep! Como se puede observar, este cdigo se "comer" cualquier excepcin que sea lanzada en la rutina a la que se llama. A menudo, el cdigo en el bloque try ... except lanza una excepcin difcil de localizar, y en lugar de mostrar el lugar del error, el programador toma el camino fcil y, sencillamente, se "come" la excepcin. En ocasiones, el motivo para comerse la excepcin no es ms que el deseo de que el usuario final no vea nunca un mensaje de error. Si ste es el objetivo, en cualquier caso, se debera hacer de tal modo que los errores no se ocultasen entre el resto del cdigo, como mnimo. Muy bien, estamos seguros de que el usuario final no ver ningn mensaje de error como resultado de este cdigo. Cada excepcin que se pudiese producir en este cdigo se suprimir (excepciones de base de datos, producidas por no haber memoria disponible, errores de hardware, cualquiera). Esto quiere decir que el programa puede devolver datos incorrectos mientras, en apariencia, ha tenido xito. Es mejor indicar claramente que se ha producido un error que silenciarlo de manera que se pueda devolver un resultado incorrecto! En la nica situacin que me puedo imaginar en la que simplemente comerse una excepcin es aceptable es cuando se quiere evitar que la excepcin se propague entre mdulos. Si se estn programando mdulos, o lo que es lo mismo, cdigo que se ejecutar en una DLL, ninguna excepcin debera salirse de su mdulo. En este casi, usar un controlador de excepciones vaco puede conseguir este objetivo. Pero excepto en este caso, controladores vacos deberan ser considerados errores graves y cdigo horroroso. Incluso en esta situacin, se debera hacer un registro de la excepcin o guardarla de alguna manera. Comerse una excepcin significa tirar a la basura toda la informacin sobre el error (la cual ayudara a solucionar el problema). El cliente nunca se dar cuenta del error, y si lo hace, el programador no ser capaz de ver porqu ha sucedido y cmo solucionarlo. Moraleja: simplemente no te comas excepciones.

No capturar todas las excepciones de manera genrica


A veces veo cdigo como este:
try cdigoQuePuedeCausarAlgunError; except on E: Exception do

begin MessageDlg(E.Message, mtWarning, [mbOK], 0); end; end;

Y pienso "Esto es como una dieta a base de coca-cola sin cafena". Este cdigo no hace nada ms que informar de la excepcin de la que, de todas maneras, se informar. De hecho, hace una cosa ms y es detener la propagacin de la excepcin. La excepcin se controlar localmente, y nunca alcanzar ms all del mbito local. Adems, se capturarn todas las excepciones, incluso aquellas que tal vez sera mejor no capturar. En la nica situacin en la que se podra considerar usar este patrn (el cual es solo ligeramente mejor que comerse la excepcin) es cuando se sabe que la rutina que llamar a la que se est programando no querr nunca controlar ninguna excepcin o tan solo alguna especfica. Por ejemplo, TClientDataset tiene el evento OnReconcileError al cual se le pasa una excepcin. Se est programando un proceso por lotes con un ClientDataset, permitir que alguna excepcin en este evento contine y suba en la pila de llamadas provocar que el proceso se detenga. En este caso sera aceptable capturar cualquier excepcin que llegase al evento.

No buscar las excepciones


La creacin de excepciones es un proceso muy costoso en trminos de rendimiento, por lo tanto no se deberan de estar creando por sistema. Un contexto en el que acostumbra a usar excepciones como sistema de control es el siguiente.
function StringIsInteger(aStr: string): Boolean; var Temp: integer; begin Result := True; try Temp := StrToInt(aStr); except Result := False; end; end;

Este cdigo har lo que se espera que haga (determinar si una cadena representa un entero vlido), pero tambin provocar que se lancen muchas excepciones, hecho que perjudicar al rendimiento de la aplicacin, especialmente si se espera que la funcin retorne "false" un nmero elevado de veces. Dejando a parte temas de rendimiento, ste es un uso incorrecto de las excepciones ya que no se ha producido una violacin de una precondicin del mtodo. El mtodo esta claramente diseado para aceptar cadenas que no representen enteros, por lo tanto no se puede lanzar una excepcin, ni si quiera de manera interna, para controlar esta situacin. Una mejor implementacin de este mtodo podra ser usar la funcin TryStrToInt de la unit SysUtils.

No usar la gestin de excepciones como sistema genrico de mensajes.


type TSucesoNormalException = class(Exception); begin AlgoDecdigoQueHaceCosasNormalesYNoProduceErrores; raise TSucesoNormalException.Create('Algo completamente normal ' + 'y que es el comportamiento esperado'); end;

Puede existir la tentacin de usar esta tcnica para pasar algn tipo de informacin a la rutina que llama a sta, en especial si la nueva clase de excepciones contiene informacin adicional que se le puede devolver. Es importante recordar es un sistema de control de flujo adems de una herramienta para transportar informacin. Lanzar una excepcin cuando lo que se desea es enviar un mensaje puede acarrear consecuencias inesperadas en el flujo de ejecucin del programa. Utilizar las excepciones de este modo puede resultar muy molesto, incluso si la aplicacin siempre captura y controla la excepcin en cuestin o si la clase hereda de EAbort y nunca muestra una pantalla de error visible para el usuario. En primer lugar, este es un modo costoso a nivel de procesador de pasar informacin. Segundo, el error aparecer en tiempo de diseo y har que los programadores que usen el cdigo se vuelvan locos (una famosa librera de componentes externa a Borland usa esta tcnica y siempre me hace perder tiempo). Como norma general, si se lanza una excepcin que en muchos casos hace que los programadores la aadan a la lista de excepciones ignoradas por el IDE, se debera replantear si realmente es necesario lanzarla.

Cmo usar excepciones correctamente


Ahora que hemos visto algunas de las maneras en las que no se deben usar excepciones, pasaremos a ver algunos consejos sobre como usar de manera correcta el sistema de manejo de excepciones de Delphi. (N. del T.: y en realidad de cualquier entorno moderno). Usar las excepciones para permitir que el cdigo se ejecute si ser interrumpido por la gestin de errores Uno de los principales objetivos de la gestin de excepciones es permitir separar el cdigo de control de errores de aquel que forma la lgica de la aplicacin. Con este sistema, es posible escribir el cdigo como si nunca se produjesen errores y encapsular ese cdigo en bloques try...except cuando se necesite tratar los errores que se produzcan. De esta manera se consigue un cdigo ms eficiente, ya que no se tienen que estar haciendo continuos controles sobre los parmetros y otros datos para asegurar la precondiciones antes de hacer nada. Una manera de conseguir este objetivo es centralizar el control de excepciones. TApplication cuenta con un evento que permite esta tcnica: el evento OnException. Es posible usar este evento para tratar todas las excepciones, sean del tipo que sean, que no se traten en otro punto de la aplicacin. Se puede usar este evento para crear un registro de errores, o controlar de un cierto modo algunas excepciones en concreto.

Los programadores de aplicaciones deberan capturar las excepciones.


Como se explica ms adelante, las excepciones se deberan generar principalmente en el cdigo que forma parte de componentes y libreras. Cuando se escriben aplicaciones, no hay mucha necesidad de crear y lanzar excepciones. Los programadores de aplicaciones deberan centrar su atencin en controlar las excepciones que se lanzar desde los componentes y libreras.

Capturar solamente excepciones concretas.


Como comentbamos antes, un programador nunca se debera comer excepciones. En lugar de esto, lo que se debe hacer es capturar nicamente excepciones concretas que puedan ser razonablemente previsibles. Si se llevan a cabo un montn de operaciones matemticas, es razonable querer capturar excepciones del tipo EMathError. Si se estn haciendo muchas conversiones quiz se quiera capturar aquellas del tipo EConverError. Del mismo modo, cuando se trabaja con bases de datos, es lgico capturar las excepciones que hereden de EDataBaseError. Pero incluso estos errores son demasiado generales. Por ejemplo, cuando trabajamos con bases de datos, pueden producirse excepciones de clases descendientes de EDAtabaseError cuando se toman acciones de base de datos determinadas. Es decir, si se est abriendo una consulta, se deberan capturar tan solo aquellas excepciones que ocurren al abrir datasets y no todas aquellas EDatabaseError. Como deca antes, me encuentro con cdigo que se come las excepciones porque el programador (o su jefe, o alguien que no tiene las ideas claras) quiere que el usuario nunca vea errores. La manera de solucionar esto es capturar la excepcin en concreto que queremos que el usuario no vea. Por ejemplo:
try cdigoQueLevantaUnEConvertError; except on E: EConvertError do begin // tratar aqu la excepcin end; end;

Este cdigo es mejor que comerse todas las excepciones, no importa lo que se haga con la excepcin, porque, en cualquier caso, solamente se capturar esta excepcin y no todas las que se podran producir. Es ms, los errores de base de datos (y algunos otros como los COM) generalmente incluyen un cdigo de error, por lo tanto es posible capturar solamente aquellos con un determinado cdigo y no hacer nada con el resto. Esta tcnica sigue el siguiente patrn:
try cdigoQueLevantaUnEConvertError; except on E: EIBError do begin if E.ErrorCode = UncdigoQueQuieroCapturar then

begin // tratar aqu la excepcin end else begin raise; // volvemos a lanzar la excepcin, ya que no //es la que queramos controlar end; end; end;

Otra razn para capturar las excepciones en la posicin ms baja de la jerarqua posible es que en el futuro podran crearse excepciones que desciendan de alguna de las clases ms genricas. Por ejemplo, si tenemos este cdigo:
try UnDataset.Open except on E: EDatabaseError do begin // controlar la excepcin end; end;

Y entonces se me ocurre declarar lo siguiente:


type ENxDatabaseErrorMuyRaro = class(EDatabaseError)

La nueva y rara excepcin ser capturada por el cdigo original, y probablemente este no sea el comportamiento deseado. Evidentemente es imposible prevenir este comportamiento en todos los casos, ya que se pueden crear descendientes de cualquier excepcin, pero si se capturan las clases que estn en la parte ms baja de la jerarqua, se consigue que suceda menos. Moraleja: Captura las excepciones tan abajo en la jerarqua de clases como sea posible y captura tan solo aquellas que quieras controlar. Los programadores de componentes y libreras lanzan excepciones. Las excepciones no aparecen de manera misteriosa. La inmensa mayora son creadas y lanzadas dentro de la VCL. Y es totalmente aceptable que un programador lance sus propias excepciones tambin. Como norma general, se deberan lanzar excepciones propias dentro de componentes y libreras. De este modo, los programadores de aplicaciones pueden capturar esas excepciones especficas y tratarlas como se ha indicado antes. Cuando se programan libreras y mtodos de componentes, se debera hacer de manera que solamente hubiesen dos casos posibles: o se ejecutan bien y acaban, o lanzan una excepcin. Los programadores de excepciones pueden asumir este comportamiento (que una rutina que es llamada o finalizar de manera satisfactoria (con un resultado vlido si se trata de una funcin) o lanzar una excepcin.

Lanzar excepciones propias.

Cuando se lanza una excepcin, siempre se deberan ser propias. Por ejemplo, si tenemos una librera en un fichero llamado NixUtils.pas, en el propio fichero se debera declarar:
NixUtilsException = class(Exception);

Y cualquier excepcin que se lance en las rutinas del fichero deber ser de esta clase o de una descendiente. De este modo se permite a los usuarios de la librera lo que he defendido antes: capturar tan solo excepciones especficas. No hay que tener miedo de declarar excepciones propias y derivarlas, incluso hasta el punto de tener clases concretas para cada rutina. Esto permitir a los usuarios de la librera el nivel de concrecin que ellos deseen al capturar las excepciones. Me encantara que los autores de la VCL hicieran esto ms a menudo. La jerarqua de excepciones de la VCL es muy simple, y sera genial si se lanzarn excepciones ms especficas, especialmente en lo que se refiere al trabajo con bases de datos, donde las excepciones se pueden producir (y de hecho se producen) en cualquier punto.

Dejar ver los mensajes de las excepciones a los usuarios.


Si aparece la tentacin de ocultar todos los errores a los tiernos ojos de los usuarios, es necesario realizarse la siguiente pregunta: qu es peor, que un usuario vea un mensaje de error o permitir que la aplicacin contine como si no hubiese ocurrido nada, dejando tras de s clculos errneos o datos daados con toda probabilidad? Por desgracia, he visto mucho cdigo que responde a esta pregunta ocultando el error. Es por eso que al final existen un montn de controladores de excepciones vacos que se comen cualquier error que se produzca. Las excepciones se producen porque algo es incorrecto. Ignorarlas puede causar resultados imprevisibles. Comerse una excepcin de memoria agotada (N. del T:en ingls, out of memory) puede tener consecuencias desastrosas porque la aplicacin (y los usuarios) continuara como si nada hubiese ocurrido cuando, en realidad, lo sucedido es muy malo. Los usuarios sienten pnico ante los cuadros de dilogo, como han indicado muchos expertos en interfices de usuario, pero si el cuadro de dialogo otorga al usuario algo que hacer entonces puede ser mucha ms til de lo que acostumbra.

Crear buenos mensajes de error.


Los mensajes de error standard que produce la VCL son muy dispersos y muy poco informativos. Personalmente, mi favorito es el mensaje "List Index out of range" (N. del T: "ndice de la lista fuera de Rango") que da exactamente cero informacin acerca de que lista est fuera de rango. Como mnimo se podra comunicar el nombre de clase de la lista, de tal manera que resultase un poco ms fcil de encontrar, no? No es necesario hacer esto:
type EUnaException = class(Exception); procedure CausaUnaexcepcin;

begin raise EUnaException.Create('Mensage tonto'); end;

Cuando se puede hacer as


type EUnaException = class(Exception); procedure CausaUnaexcepcin; begin raise EUnaException.Create('El procedimiento "CausaUnaexcepcin" ' + 'no hace nada ms que lanzar esta excepcin.' + 'Por qu diablos lo ests llamando?'); end;

En esto es mejor no imitar a los chicos de la VCL, es mejor escribir mensajes de errores completos y descriptivos. Se puede incluir el nombre del procedimiento, el TObject.ClassName, o lo que se desee en el mensaje. De hecho, se puede mejorar el mensaje de error de la excepcin y permitir que esta contine normalmente:
type EExceptionConMensajeTonto = class(Exception); procedure CrearexcepcinConMensajeTonto; begin raise EExceptionConMensajeTonto.Create('Boring message'); end; procedure TForm1.Button1Click(Sender: TObject); begin try CrearexcepcinConMensajeTonto; except on E: EExceptionConMensajeTonto do begin E.Message := 'Este es un error causado de manera intencionada ' + 'en el evento Button1Click en la clase TForm1. El' + 'mensage original era: ' + E.Message; raise; end; end; end;

Como el controlador incluye la llamada a raise, la excepcin no se oculta, pero el cdigo da ms informacin acerca del error, etc. Podemos incluir informacin acerca de dnde puede encontrar ayuda el usuario, qu puede hacer si el error persiste, etc.

Ofrecer dos versiones de cada rutina de librera


Existen programadores a los que no les gustan las rutinas que producen excepciones. Bien, ok, por no cumplir su capricho? A veces la VCL hace lo siguiente: ofrece dos funciones que hacen lo mismo, una levanta una excepcin si falla y la otra devuelve nil o algn cdigo de error, dependiendo del tipo de la rutina. Se me ocurre la pareja FindClass/GetClass. FindClass busca y retorna una clase por su nombre y, si no la

encuentra, levanta una excepcin. GetCalss, por otro lado, simplemente retornara nil si no encuentra la clase.

Conclusin.
Es muy fcil caer en la trampa de usar el control de excepciones de manera inadecuada. La posibilidad de hacer que los errores y problemas "desaparezcan" es muy tentadora. En cualquier caso, hacer un mal uso de las excepciones acarrear verdaderos problemas, incluyendo la cada del sistema y la prdida de informacin. El uso adecuado del control de excepciones har que el cdigo sea ms fcil de mantener. Un uso sabio de las excepciones proporcionar un cdigo robusto y limpio.

Agradecimientos
Estoy muy agradecido a Craig Stuntz, quien repas el borrador de este artculo y realiz algunas excelentes y valiosas contribuciones en l. Adems, mucho de lo que s acerca de las excepciones y su control lo le en "Delphi Component Design" de Danny Thorpe Nick Hodges Traducido por marto Manel Martorana 21 de febrero de 2005

También podría gustarte