Artículo original: Exception Handling Dilemma in VFP
http://west-wind.com/weblog/posts/4538.aspx
Autor: Rick Strahl
Traducido por: Ana María Bisbé York
Me ha llamado la atención, hace un par de días, que cambiando el mecanismo de control de errores de Web Connection por uno nuevo basado en TRY/CATCH, en lugar del método tradicional de controlar el error, que tiene mucha menos funcionalidad.
En Web Connection 4.0 todas las clases para procesar errores se controlan en el método Error en la clase Process la que básicamente captura todos los errores no controlados. El problema con el método de error es que no puede inhabilitarlo fácilmente, entonces, para tener un entorno de desarrollo donde los errores no sean controlados y el entorno de ejecución donde están tengo que utilizar un código como este:
*** Este bloque anula el método Error para que pueda tener los errores en *** tiempo de depuración interactiva mientras ejecuta dentro de VFP. #IF !DEBUGMODE ************************************** FUNCTION Error(nError, cMethod, nLine) ************************************** LOCAL lcLogString, llOldLogging, lcOldError *** ¿Hemos sobrecargado la pila de llamadas? Si es así, nos vamos IF PROGLEVEL() > 125 RETURN TO PROCESSHIT ENDIF nError = IIF(VARTYPE(nError) = "N", nError, 0) cMethod = IIF(VARTYPE(cMethod) = "C", cMethod, "") nLine = IIF(VARTYPE(nLine) = "N", nLine, 0) *** Guardamos el valor actual lcOldError = ON("ERROR") ON ERROR * *** Cerramos esta petición con una página de error *** - SAFE MESSAGE (no confiar en ningún objeto) This.ErrorMsg("Ha ocurrido un error..",; "En este momento no se puede servir esta petición " + ; "debido a dificultades técnicas.<P>" + ; "No. Error: " + STR(nError) + "<BR>" + CRLF + ; "Error: " + MESSAGE()+ "<P>" + CRLF + ; "Método: " + cMethod + "<BR>" + CRLF + ; "Código actual: "+ MESSAGE(1) + "<BR>" + CRLF + ; "Línea de código actual: " + STR(nLine) + "<p>" + ; "Excepción controlada por " + This.CLASS + ".Error()") *** Obriga a cerrar el archivo y es recuperable por wc.dll/exe *** NOTA: Los objetos HTML no se liberan aquí debido a otra *** referencia de objeto como Response. IF TYPE("This.oResponse") = "O" AND !ISNULL(This.oResponse) This.oResponse.DESTROY() ENDIF * wait window MESSAGE() + CRLF + MESSAGE(1) + ; * CRLF + "Método: " + cMethod nowait IF TYPE("This.oServer") = "O" AND !ISNULL(This.oServer) *** Intenta obtener un log del error - Fuerza el log!!! * llOldLogging = This.oServer.GetLogToFile() llOldLogging = This.oServer.lLogToFile lcLogString = "Procesando Error - " + ; This.oServer.oRequest.GetCurrentUrl() + ; CRLF + CRLF + "<PRE>" + CRLF + ; " Error: " + STR(nError) + CRLF + ; " Mensaje: " + MESSAGE() + CRLF + ; " Código: " + MESSAGE(2) + CRLF + ; " Programa: " + cMethod + CRLF + ; " Línea No: " + STR(nLine) + CRLF + ; " Cliente: " + This.oRequest.GetIpAddress() + CRLF + ; "Post Buffer: " + This.oRequest.cFormVars + CRLF + ; "</PRE>" + CRLF + ; "Excepción controlada por: " + This.CLASS+".Error()" This.oServer.LogRequest(lcLogString,"Local",0,.T.) This.oServer.SetLogging(llOldLogging) This.SendErrorEmail("Web Connection Error - " + ; This.oServer.oRequest.GetCurrentUrl(), lcLogString) ENDIF ON ERROR &lcOldError *** Regresa al método Process! RETURN TO ROUTEREQUEST ENDFUNC * EOF wwProcess::Error #ENDIF
Ahora está trabajando bien; pero siempre ha sido un esquema que huele mal. El primer punto es que existe un requerimiento del compilador para habilitar y deshabilitar el interruptor para el modo debug. Entonces, necesita ser recompilado para hacer que cambie entre los dos.
El otro problema más delicado, es que confía en RETURN TO para devolver a un método especificado que lo haya llamado, lo que no es siempre confiable. De hecho, si en algún lugar en la cadena de llamada causa error una ejecución EVAL() el mecanismo de error entero se rompe porque VFP 8 y 9 no admiten RETURN TO en llamadas EVALUATE(). Cuando este es el caso, RETURN TO hace return sencillo y usted termina la ejecución del código SIGUIENDO el error.
Nunca me había gustado el método Error en estos casos porque no existe una forma determinista para devolverlo a algún lugar, entonces VFP 8 llegó con algo que me gustó mucho ver: TRY/CATCH y la capacidad de tener más control determinista del error que permite volver a un lugar específico del código.
Entonces, con Web Connection 5.0 el método Error y el proceder DEBUGMODE (el que de paso, es aplicado también a otras clases) fue sustituido por un controlador TRY/CATCH como centro del motor de proceso. He aquí la implementación que no tiene ningún método de error especial; pero en su lugar, el lo llama:
************************ FUNCTION Process() ************************ LOCAL loException PRIVATE Response, REQUEST, Server, Session, Process Process = THIS Server = This.oServer Request = This.oRequest Response = This.oResponse Session = This.oSession Config = This.oConfig *** Método gancho IF !This.OnProcessInit() RETURN ENDIF IF Server.lDebugMode This.RouteRequest() ELSE TRY This.RouteRequest() CATCH TO loException This.OnError(loException) ENDTRY ENDIF This.OnProcessComplete() RETURN .T. ENDFUNC * EOF wwProcess::Process
El controlador TRY/CATCH captura cualquier error y entonces, sencillamente llama a un método sobreescribible que es utilizado para controlar el error. Un controlador predeterminado es proporcionado o el desarrollador puede sustituir OnError para hacer cualquier cosa que necesite. Desde la perspectiva del diseñador esto está mucho más claro y no tiene un error potencial utilizando RETURN TO.
Pero, existe desafortunadamente, una funcionalidad perdida. TRY/CATCH está bien; pero su empleo termina con algunas limitaciones. Este controlador:
- No nos brinda una información detallada del error
- No tenemos la pila de llamada - el control se devuelve al nivel que lo llama.
CATCH permite recibir un objeto exception; pero desafortunadamente este objeto no brinda mucha información y la información disponible no está completa como en el método Error. La razón principal para esto es que cuando ocurre una operación CATCH limpia la pila de llamada con lo que lo coloca en el método inicial que ha realizado la llamada. Esto significa que puede obtener información detallada del error desde el nivel de la pila de llamadas donde, realmente ocurra el error. Cualquier variable, PRIVATE o LOCAL se habrá perdido y ASTACKINFO() solamente devolverá la pila actual, la que en realidad no se corresponde con callstack de el lugar donde ocurrió el error.
Esto significa además, que su información de error está limitada por lo que provee el objeto Exception y no es tan completa como lo que brinda LINENO(), PROCEDURE y SYS(16). El resultado es que en muchas situaciones puede obtener LineNo y ejecutar el nombre del programa de la forma en que tiene en el método Error, especialmente en tiempo de ejecución sin tener información de depuración.
Alternativas? Realmente no ...
Entonces, estuve intentando obtener algunas soluciones para esta limitación - desafortunadamente no he encontrado la forma correcta de hacerlo. Mi primera idea fue hacer que funcione el método Error además del controlador TRY/CATCH. Un par de ideas que vinieron a mi mente:
- Sobreescribir Try/Catch con el método Error
- Utilizar un método Error junto al Try/Catch y emplear THROW para crear una excepción personalizada
FUNCTION Error(lnerror,lcMethod,lnLine) *** Aquí su controlador de error personalizado RETURN TO RouteRequest ENDFUNC
Desafortunadamente esto no va a funcionar: VFP no permitirá RETURN TO ni RETRY dentro de un bloque TRY/CATCH. Denegado.
La segunda opción tendría un proceder similar; pero en su lugar de devuelve la información del error capturado y luego crea una excepción personalizada que contenga información adicional:
FUNCTION Error(lnerror,lcMethod,lnLine) This.StandardPage("Error Ocurrido","Prueba") LOCAL loException as Exception loException = CREATEOBJECT("Exception") loException.LineNo = lnLine loException.ErrorNo = lnError loException.Message = MESSAGE() THROW loException ENDFUNC
Desafortunadamente, esto tampoco funciona el error arrojado en el método Error es arrojado a su vez, al nuevo controlador de errores. Lo único que puede controlar una excepción en el método Error es ON ERROR. Por tanto, esto tampoco funciona.
Próxima idea: Capturar la excepción y adjuntarla en una propiedad, para que la información adicional esté disponible. Entonces, sencillamente devolvemos el Error del método Error. Desafortunadamente esto tampoco funciona, porque no se puede acortar el circuito de procesamiento del error - Cualquier código siguiente al error original continúa ejecutándose.
Entonces, al final está claro que el método Error() que necesita mostrar la cadena de error no coexiste de manera limpia con el controlador TRY/CATCH. No veo al forma que puedo hacer que esto funcione utilizando un proceder donde se empleen ambos .
Al final, mi solución es agregar un indicador lUseErrorMethodErrorHandling a la clase que procesa y luego, basarme en el salto de la llamada TRY/CATCH
IF Server.lDebugMode OR ; This.lUseErrorMethodErrorHandling This.RouteRequest() ELSE TRY This.RouteRequest() CATCH TO loException This.OnError(loException) ENDTRY ENDIF
Entonces, el controlador de error funciona como en la versión vieja de Web Connection, excepto en que el desarrollador es responsable por hacer su propio controlador de error. Para simular un comportamiento similar y para componer el mensaje de error como TRY/CATCH puede hacer lo siguiente:
FUNCTION Error(lnerror,lcMethod,lnLine) LOCAL loException as Exception loException = CREATEOBJECT("Exception") loException.LineNo = lnLine loException.LineContents = MESSAGE(1) loException.ErrorNo = lnError loException.Message = MESSAGE() loException.Procedure = SYS(16) This.OnError(loException) RETURN TO RouteRequest ENDFUNC
Esto funciona; pero no es lo que yo considero la implementación más limpia. Sería muy bueno que VFP nos permitiera alguna opción para tener un gancho que pueda controlarse cuando es creado un objeto Exception. En lugar de pasar una referencia a un objeto Exception podría pasar un tipo - VFP podría instanciar el tipo y usted podría sobreescribir, digamos el Init() de ese tipo para reunir toda la información relevante de la pila de llamadas hasta ese punto. O tener la referencia que tenga un método que sea llamado en la inicialización. O al menos, si TRY/CATCH admite THROW para encadenar el método error.
Bueno, esto funciona aunque tiene truco...
No hay comentarios. :
Publicar un comentario
Los comentarios son moderados, por lo que pueden demorar varias horas para su publicación.