Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Exceptions
Exceptions
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
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.
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.
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.
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.
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;
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.
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.
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.
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