PDA

Ver la Versión Completa : Capturar Excepciones


clanmilano
05-11-2005, 00:39:49
Hola, quisiera saber como generar yo mensaje de error en caso de que el usuario no complete todos los campos declarados como NO nulos del lado de la base de datos, al hacer un insert o update. Lo estoy haciendo en el evento beforepost del clientdataset de la tabla en particular, pero lo que no me termina de convencer es que el mensaje aparece dos veces al producirse la excepción. El código que coloco en ese evento es el siguiente:

if cdsClientes.FieldByName('DESCRIPCION').IsNull then
DatabaseError('Complete los campos marcados como obligatorios!!');

Suponiendo que el único campo que no puede tomar valor nulo es DESCRIPCION de la tabla CLIENTES.

¿Es correcto hacerlo asi?
¿Saben si se puede hacer de alguna otra forma?

Muchas gracias!

Muchas gracias!!

Lepe
05-11-2005, 11:19:17
hazlos todos de una sola vez:


procedure ...BeforePost(....)
var Msg:string;
begin
Msg := EmptyStr;

if campo1.IsNull then
Msg:= msg + #10#13+ 'campo1';

if campo2.IsNull then
Msg:= msg + #10#13+ 'campo2';

if Msg <> EmptyStr then
begin
Msg := ' Los siguientes campos no pueden estar vacios:'+#10#13+ Msg;
Databaseerror(msg);
end;

Tambien puedes hacerlo más eficiente con un bucle sobre Tfields, viendo si tiene la propiedad Required y despues comprobando si es nulo

saludos

luisgutierrezb
05-11-2005, 16:05:08
Porque no lo haces en el evento "OnValidate" del dataset, se supone que para eso es.
Saludos!

GustavoCruz
27-10-2015, 17:00:57
Hola amigos, retomando el tema...

resulta que tengo una db firebird 2.5.4, en la que declaro unas excepciones; una de ellas es 'no_borrar'
with qEjecutar do
begin
Close;
SQL.Clear;
SQL.Add(CadenaSQL);
// ShowMessage(CadenaSQL);
try
ExecSQL;
Result := True;
Transaccion.CommitRetaining;
if notificado then
MsgBox(Titulo, 'Registro actualizado satisfactoriamente...',
mtInformation, ['Aceptar'], 0);
except
on E: exception do
MsgBox(Titulo, 'Clase de error: ' + E.ClassName + chr(13) + chr(13) +
'Mensaje del error: ' + E.Message, mtError, ['Aceptar'], 0);
end;
end;


en el código no sé cómo capturar la excepción de tal manera que sólo me aparezca el texto de la excepción sin que me diga el resto de cosas, ej.
nombre de la excepción, trigger en el cual se ha lanzado, ubicación de la línea de código...
yo sólo quiero la descripción del error "No puedes eliminar el registro porque..."

De antemano gracias por vuestra ayuda

Gustavo Cruz

Ñuño Martínez
27-10-2015, 17:46:32
en el código no sé cómo capturar la excepción de tal manera que sólo me aparezca el texto de la excepción sin que me diga el resto de cosas, ej.
nombre de la excepción, trigger en el cual se ha lanzado, ubicación de la línea de código...
yo sólo quiero la descripción del error "No puedes eliminar el registro porque..."
Si te pone esa información es porque le dices que la ponga. La clase Exception tiene unas cuantas propiedades, la principal de ellas es "Message", que es la que contiene el mensaje de error. El resto de propiedades tienen información adicional, que a veces no es necesaria o deseable.

También has de notar que no es obligatorio usar el objeto lanzado. Incluso puedes hacer:
TRY
RAISE Exception.Create ('Esta es una excepción lanzada por mi.')
EXCEPT
Application.MessageBox ('Hubo una excepción', 'Ignorante')
END;
Ese código mostrará el mensaje "[Ignorante] Hubo una excepción" independientemente de lo que haya en el objeto de excepción. De todas formas, no es recomendable. Mejor usar correctamente las excepciones:

TRY
... { Código aquí... }
IF IntentoBorrarYNoDebe THEN
RAISE Exception.Create ('¡No puede eliminar eso!');
... { Código allá... }
EXCEPT
ON Error: Exception DO Application.MessageBox (Error.Message, 'Error')
END;
Así mostrará el mensaje de error, y únicamente el mensaje de error.

AgustinOrtu
27-10-2015, 17:56:17
Tal y como comenta Ñuño, usa excepciones, y usalas sabiamente. No solo porque muestran un mensaje de error, sino porque ademas cortan la ejecucion del codigo

GustavoCruz
27-10-2015, 18:24:23
Hola Ñuño, gracias por tu respuesta lo que no quiero es lo que aparece en la imagen que te adjunto.
Yo solo quiero es "Error al intentar borrar un registro protegido por el sistema"

Gracias por vuestro tiempo


Gustavo Cruz

Ñuño Martínez
27-10-2015, 19:50:42
Creo que no has entendido bien lo que he escrito. Reléelo, compara tu código con el mio, y haz pruebas.

Lepe
28-10-2015, 19:10:24
El caso es que Firebird añade información al mensaje de error, así que no queda más remedio que buscar texto y eliminar lo que no te hace falta.

El código intuyo que sería algo así:

on E: EUniError do begin
posAt := pos ('At', E.Message);
Texto := Copy( E.Message, 1, posAt );
ShowMessage( Texto) ;// esto es una prueba nada más.
// para que veas como queda.
end;


Creo también aparecerá el texto "exception 1 NO_Borrar" en el mensaje... es que no tengo Delphi a mano para probar y hace mucho que no las uso.

Si se te complica demasiado, en el texto de la excepción en Firebird, defínela como : Exception 1 = '##No puede borrar algo de sistema @@';

Así ya sabes que tienes que copiar solo entre las ## y las @@ ... por ejemplo.

AgustinOrtu
29-10-2015, 00:29:10
Voy a dar mi apreciacion:

Las excepciones no deben ser user friendly. Deben mostrar la mayor cantidad de detalle, por mas "tecnico" que sea, de cual fue la causa del error, el estado de los objetos que intervenian en ese momento.. mientras mas cosas mejor. Una buena excepcion no solo contiene eso, sino que tambien deberia indicar posibles soluciones o donde mirar.

En tu codigo estas cometiendo el grave error de capturar todas las excepciones y mostrar "no pudiste borrar porque es un registro protegido por el sistema". Quiere decir que si ocurre una excepcion EOutOfMemory se va a mostrar ese mensaje, si ocurre una EAccesViolation te va a decir "no podes eliminar un registro protegido por el sistema". Estas basicamente escondiendo errores de implementacion que vaya a saber que repercusion tienen, que estados inconsistentes estas dejando por ahi, o que operaciones estas bloqueando por una razon que no es

Escribe excepciones que sean amigables para el programador (tu mismo!!). Yo antes hacia cosas similares a esa, ponia un mensaje de error "generico" ej: "no se pudo grabar el cliente". Genial gracias, ahora porque no se pudo? Viole una primary o unique key? Deje un campo en null? Me quede sin memoria? No instancie un objeto o lo destrui antes sin querer? Error de sintaxis sql? La tabla no existe? Como puedo saberlo?????? Facil, te abres el Delphi, y que el depurador te lance la excepcion, para nada cool eh

Considera este codigo:


try
{ codigo }
Post, Commit, etc
except
on E: ERegistroProtegido then
// informar del registro protegido
end;


Es una pequeña mejora al anterior.

En realidad la pregunta que yo me hago es, porque existe la posibilidad de llegar a una situacion de "borrar un registro protegido por el sistema"? No es mejor que nunca se pueda llegar a tal cosa? Realmente, vas a iniciar una transaccion, hacer rollback o commit, solo para despues decir: "esto no lo puedes borrar"?

Si hay un conjunto de registros que no se pueden borrar y otros si, entonces ahi la cuestion es, antes de intentar hacer cualquier cosa, validar que sea un registro protegido, y de ser asi, elevar una excepcion


procedure Borrar(const IdRegistro: Int64);
begin
if IsSystemProtectedRec(IdRegistro, ATableName) then
raise ESystemProtectedRec.CreateFmt('Cannot delete the record %d of the table %s because is system protected', [IdRegistro, ATableName]);

{ codigo "normal" de borrado de registros normales }
end;


Las excepciones son eso: excepciones. Algo realmente malo que no deberia pasar. Son casos raros (por evitar el abuso de la palabra, casos excepcionales). Y si algo de esto que no es normal que pase, lo mas logico seria obtener la mayor cantidad de informacion posible, puesto que en algunos casos un tonto EAccessViolation puede ser trivial (en otros no!), pero que tal si no se puede reproducir la excepcion siempre? Tu codigo es 100% deterministico?

Lepe
29-10-2015, 21:11:32
Voy a dar mi apreciacion:

... Pues yo también, ya que estamos en un foro de programación y de eso se trata, de compartir información y opiniones :)

El tratamiento de las excepciones es un libro gordo aparte jejeje.

Mi punto de vista es distinto. Al usuario hay que darle un mensaje amigable, pero aprovecha ese mismo try except para escribir en un fichero log el error, la clase, la línea donde se produce, etc.

Cuando programas es otro mundo, desde hace mucho tiempo se usan las directivas de compilación para eso, aunque los BDS lo popularizaron con el compilar para "release" o para "Debug", ya de sobra conocidos en los XE.

Cnpack ya lo incluye en Delphi 7 con su depurador CnDebug y es bastante fácil hacerlo.

Todo este rollo viene porque en el except, la rutina que hace un log, puedes incluir:
- Si el IDE de delphi está en ejecución, entonces dame toda la información que necesito como programador, Nombre de tabla, campo, valor por defecto, clase del error, etc. y pégame un pantallazo con toda esa información. Incluída la pila de llamadas de la aplicación, porque muchas veces el error está en 3 procedimientos atrás, no en la linea que levanta el error.

- Si el IDE no está, pues escribes en un fichero de log de la aplicación, la hora fecha y toda la información que quieras, pero al usuario déjalo vivir leyendo un mensaje comprensible para él.

- Si la directiva de compilación está anulada (porque la desactivo antes de compilar la versión definitiva), ese código no va en el ejecutable y no molesta ni afecta al rendimiento.

Teniendo todos estos ingredientes, es muy fácil ordenar las cosas. Gestionar las excepciones es hacer otro programa aparte:
- herencia de las excepciones (crear tu propio árbol de excepciones tal y como haces con las clases de delphi). Algo que he pensado sobre la marcha y espero aclare lo que digo:

EMiprograma (hereda de Exception)
EBBDD -> excepciones de la base de datos
EAdministrador -> Error que solo subsana el administrador de la BBDD,
o sea, el programador, cosas que nunca deberían ocurrir, pero ocurren: "shit happens"
EFacturar (hereda de EMiprograma) y ocurren en el módulo de facturación

Cada subnivel de excepciones, tiene sus propiedades e información que puede ayudar a encontrar el error...

- Si se han de mostrar o no al usuario (heredar de EAbort o de Exception).

El concepto es muy simple y poderoso, una vez se levanta una excepción, va subiendo por la pila de llamadas de tu aplicación ejecutando el try ... except que lo contenga, si incluye un "raise ; " seguirá subiendo hasta llegar al nivel más alto, el TApplication.OnException, si no tienes ese componente en tu aplicación, el mensaje se muestra al usuario. Si tienes el componente, tú decides qué hacer en ese evento.

Es cierto que una excepción tiene un coste:
- Se corta el flujo normal del programa (saltos incondicionales del código, cambio de los valores de los registros de la CPU, cambio del contador del programa, etc...)
- Se crean objetos que recopilan información extra.

Pero en mi opinión, debido a la complejidad de las aplicaciones actuales (multiplataforma, con componentes de terceros, etc), ya no son casos tan "excepcionales", ocurren demasiado a menudo y tenemos que saber manejarlos.

Por supuesto el típico "On E:Exception do" hay que erradicarlo, debemos controlar solo las excepciones que realmente nos interesan... como en este caso "On E: EUniError do".

Estoy de acuerdo con AgustinOrtu que más vale prevenir que curar, es decir, si se puede evitar lanzar una excepción inhabilitando un botón en la pantalla de usuario, ocultar ese registro en pantalla o hacerlo de solo lectura... perfecto. Por otra parte, si un sistema Gestor de Bases de Datos, tiene excepciones, es porque ha hecho falta crear ese sistema de notificaciones.

Hay sistemas donde se escribe más código en la BBDD que en el programa Delphi, y en ese caso lanzar una excepción, capturarla en un trigger o procedimiento almacenado y actuar en consecuencia, es la solución perfecta.

Las herramientas las tenemos ahí, ahora solo hay que usarlas cuando "creamos que es necesario", y lo entrecomillo porque a veces, por desconocimiento, las usamos cuando no debemos, pero nos soluciona el problema.

Saludos!

Casimiro Notevi
29-10-2015, 21:22:23
Yo también prefiero mostrar algo simple y amigable al usuario, mientras que guardo en un log toda la información posible. Pero esa no se muestra al usuario.

Ñuño Martínez
02-11-2015, 12:04:58
Otra posibilidad es crear un "modo depuración". Si este está activo, entonces muestra toda la información de la excepción capturada (por ejemplo, llamando a TApplication.ShowException), y si está desactivado sólo muestra un mensaje más corto pero guardando el resto de información. Sería algo así como:
TRY
...
EXCEPT
ON Error: Exception DO
BEGIN
IF Config.Depurando THEN
sysutils.ShowException (Error, ExceptAddr)
ELSE
Application.MessageBox ('No pudo realizarse la operación', 'Error');
Application.Log (etError, Format ('[%s] %s at %p', [Error.ClassName, Error.Message, ExceptAddr]));
END
END;

Nota importante: Acabo de fijarme en que la clase Exception definida por Free Pascal difiere ligeramente de la descrita por Delphi, y lo mismo pasa con otras clases y funciones relacionadas, así que puede variar un poquito la cosa. Aun así creo que queda clara la idea, ¿no?

GustavoCruz
03-11-2015, 15:18:13
Gracias amigos por sus comentarios, voy hacer algunas pruebas y luego les cuento
...

mamcx
03-11-2015, 23:43:35
El problema con esto es meter todo en la misma "bolsa". Las excepciones son problemáticas porque interrumpen el flujo de forma abrupta, y porque se supone que *no deberían usarse/suceder de forma frecuente* y por eso se desaconsejan (la unica excepcion que conozco es python, donde se considera idiomatico).

Ademas, son evidencias de deficiencias en los lenguajes, en especial si 1)No permiten devolver mas de un valor en RETURN 2) Son OO y no hay forma de hacer encadenado de funciones de forma natural, usando un discrimininador de tipo.

El asunto es que los lenguajes normalmente asumen que esta bien retornar en la ruta OK mas de un tipo de valor, de cualquier clase, pero no en la ruta ERROR (ie: No se espera que sea idiomatico retornar errores!), lo que hace la vuelta a veces un poco molesta.

En fin...

-----

En obj-c/Coccoa tienen un concepto util: Hay EXCEPTIONS y hay ERRORS. ERRORS son destinados siempre al usuario (y si hay un EXCEPTION la idea es que se transforma en ERROR de ser el caso...).

Asi que el primer paso es:

1- Separar lo que es de usuario y lo que no

Y si ocurre una excepcion:

2- Transformar de esta a ERROR si es el caso

Esta es la solucion mas sana en un lenguaje con las 2 limitantes anteriores.

P.D: El poner o no LOG es *tangencial* al problema. El que algo se haga den DEBUG/RELEASE tambien es tangencial y no resuelve en nada el asunto. El usar dialogos o escribir en la consola/archivo es tambien tangencial.

----

Si hay soporte a retornar multiples valores, o se usa una estructura como TUPLAS o un RECORD, se puede hacer algo muy elegante (en este pseudocodigo delphi, que estoy oxidado):


TResult =
Ok:<'T'>
Err:<'T'>


En tal caso, se retorna no el valor como tal, sino el RECORD. Lo malo es que en Delphi no hay como forzar el uso del camino "correcto" como en F#:

http://fsharpforfunandprofit.com/posts/exceptions/

Ir hasta el subtitulo: "Should functions throw exceptions or return error structures?". En los lenguajes como F#, es mas comun retornar estructuras, porque permite hacer encadenamientos y automatizar un monton todo el tema.

----

Releyendo esto me di cuenta que no deje nada muy en claro, asi que:

Hay 2 escuelas de como manejar los errores:

1- Uso excepciones, que son GOTOS super-cargados y especiales (Delphi, Java, C#, etc)
2- Uso valores de retorno (C, GO)

La escuela 2 permite hacer codigo mas robusto, pero es muy engorroso de manejar. La primera es mas facil, pero se pierde de vista muy rapido el error y su origen (aparte de que causo una interrupcion abrupta).

La 2 es mas facil de entender, porque retornar un error es quasi-como retornar un valor normal. Lo malo, es que al estilo de C (usando un 0 para OK y otro valor para quien-sabe, a veces NO-OK otras hay que consultar la tabla de errores, que esta en otro lado) es un fiasco.

Ahora bien, en lenguajes funcionales, como F#, tienen una variacion del 2, pero mas util:

2-pero-util: Retorno un valor error tal como un valor OK. Quien recibe el valor decide que hace con el. (F#, ML, Haskell, Elixir)

Lo cual es mil-veces-mejor que C, pero mata la gracia de las excepciones: Que el cliente no tiene que decidir siempre que hacer.

---

Al punto al que voy: Hacer 2) es mas practico para manejar "errores" donde debo tener control, mas 1) es mas practico en todos los demas casos porque no quiere tener el control todo el tiempo!

AgustinOrtu
03-11-2015, 23:56:47
Yo caigo dentro del primer grupo :)


function Foo: Boolean;
begin
Result := CalcSomething > 0;
end;


La pregunta del millon es, si en CalcSomething se eleva una excepcion, realmente importa el valor de retorno de Foo? :)

A mi modo de ver las cosas no, ya que si ocurrio una excepcion (algo que no deberia haber pasado y que esta fuera del control de mi programa)

Otra alternativa es usar Nullables. En delphi no vienen de serie pero se implementan facilmente. Tambien hay frameworks que los implementan, por ej Spring4d (http://spring4d.org/)



El codigo anterior quedaria similar a esto:


function Foo: TNullableBoolean;
begin
Result := TNullableBoolean.Create(CalcSomething > 0);
end;



Los TNullable en spring, son implementados usando record, asi que si falla la creacion no tendriamos nunca un AV si mas adelante quiero consultar su valor

Obviamente, un verdadero nullable no permite acceder al valor en cuestion si nunca se seteo nada; en este caso spring4d, eleva una excepcion EInvalidOperation.

Lo interesante del asunto es que se dispone del metodo Value para recuperar el valor; y el metodo HasValue para ver si tengo un valor valido

Ñuño Martínez
04-11-2015, 11:30:53
Coincido con mamcx. Es más, yo sólo uso excepciones para manejar estados de error; por ejemplo, el usuario indicó el nombre de un archivo que no existe. En el resto de casos, devuelvo valores.

Por cierto mamcs, Delphi sí es capaz de devolver varios valores en sus funciones. Recuerda los parámetros VAR y OUT.

mamcx
04-11-2015, 17:22:19
Por cierto mamcs, Delphi sí es capaz de devolver varios valores en sus funciones. Recuerda los parámetros VAR y OUT.

Ah! se me olvido eso... De hecho, asi es como se hace en obj-c (osea, el manejo de los ERRORS).

roman
04-11-2015, 18:17:43
Yo no comparto la visión que tienen de las excepciones. El manejo de la excepción no es quien corta abruptamente la ejecución sino la excepción misma, si es que se ve la diferencia.

Es cierto que hay un abuso de las excepciones al grado de usarlas a modo de condicional y en muchas ocasiones bastaría examinar el valor de retorno de una función.

Sin embargo, las excepciones son un objeto mucho más complejo que eso, siendo su gran virtud el efecto burbuja, esto es, que la excepción se va elevando hasta encontrar un punto en donde se puede manejar por quien pueda y sepa hacerlo. Lejos de ser una interrupción brusca del código, en realidad el manejo de excepciones es quien permite manejar un problema de la forma más decorosa posible.

Como dije antes, no hay que abusar de las excepciones. El caso que se planteó aquí originalmente (me refiero al de este año no al del inicio del hilo) es un típico caso de abuso de la técnica: se usa la excepción como un condicional para tomar una decisión que, en realidad, tiene que ver más con la lógica de negocios que con el flujo de la aplicación.

// Saludos

Lepe
04-11-2015, 23:25:39
No os hacéis la idea de lo que echo de menos estas charlas donde se derrocha conocimientos, gustos, opiniones, pero siempre desde el entendimiento y buen hacer :).

Más de 12 años en Club delphi y sigo disfrutando como un crío....Sí, hoy tengo el día tonto :p

Un abrazo!!

Casimiro Notevi
05-11-2015, 00:20:08
No os hacéis la idea de lo que echo de menos estas charlas donde se derrocha conocimientos, gustos, opiniones, pero siempre desde el entendimiento y buen hacer :).
Más de 12 años en Club delphi y sigo disfrutando como un crío....Sí, hoy tengo el día tonto :p
Un abrazo!!
||-|| Eso es estupendo :)

roman
05-11-2015, 04:52:08
No os hacéis la idea de lo que echo de menos estas charlas donde se derrocha conocimientos, gustos, opiniones, pero siempre desde el entendimiento y buen hacer :).


Pues no te alejes mi estimado Lepe. Aunque ahora programes en VB, te seguimos queriendo :D

// Saludos

Ñuño Martínez
05-11-2015, 20:21:39
Yo no comparto la visión que tienen de las excepciones. El manejo de la excepción no es quien corta abruptamente la ejecución sino la excepción misma, si es que se ve la diferencia.

Es cierto que hay un abuso de las excepciones al grado de usarlas a modo de condicional y en muchas ocasiones bastaría examinar el valor de retorno de una función.

Sin embargo, las excepciones son un objeto mucho más complejo que eso, siendo su gran virtud el efecto burbuja, esto es, que la excepción se va elevando hasta encontrar un punto en donde se puede manejar por quien pueda y sepa hacerlo. Lejos de ser una interrupción brusca del código, en realidad el manejo de excepciones es quien permite manejar un problema de la forma más decorosa posible.

Como dije antes, no hay que abusar de las excepciones. El caso que se planteó aquí originalmente (me refiero al de este año no al del inicio del hilo) es un típico caso de abuso de la técnica: se usa la excepción como un condicional para tomar una decisión que, en realidad, tiene que ver más con la lógica de negocios que con el flujo de la aplicación.

// Saludos No, si yo uso las excepciones como tú dices. Intento capturarlas lo antes posible en lugar de que "se caigan" del programa.

roman
05-11-2015, 20:31:25
Yo creo que ni tan rápido ni tan lento. Todo depende de la situación. Puedes programar una biblioteca que lance alguna excepción y la aplicación que la use será la encargada de manejarla. Por ejemplo, en el ámbito web, una excepción puede generarse al acceder a una base de datos y sólo la aplicación final sabrá el momento adecuado para manejarla y desplegar el mensaje que considere pertinente al usuario.

Esto proporciona una forma limpia de manejar los errores impredecibles. Mucho más limpia que estar acumulando los resultados de funciones. Es la gran ventaja sobre los métodos antiguos.

// Saludos

Delphius
05-11-2015, 22:52:26
Otra posibilidad es crear un "modo depuración". Si este está activo, entonces muestra toda la información de la excepción capturada (por ejemplo, llamando a TApplication.ShowException), y si está desactivado sólo muestra un mensaje más corto pero guardando el resto de información. Sería algo así como:
Código Delphi [-] (http://clubdelphi.com/foros/#)TRY ... EXCEPT ON Error: Exception DO BEGIN IF Config.Depurando THEN sysutils.ShowException (Error, ExceptAddr) ELSE Application.MessageBox ('No pudo realizarse la operación', 'Error'); Application.Log (etError, Format ('[%s] %s at %p', [Error.ClassName, Error.Message, ExceptAddr])); END END;


Nota importante: Acabo de fijarme en que la clase Exception definida por Free Pascal difiere ligeramente de la descrita por Delphi, y lo mismo pasa con otras clases y funciones relacionadas, así que puede variar un poquito la cosa. Aun así creo que queda clara la idea, ¿no?

Interesante enfoque. Pregunto yo... ¿No existe una directiva de compilación tanto en Delphi como en Lazarus para determinar cuando se está en modo debug, y/o que se pueda (des)activar según se necesite? ¿O realmente se necesita de recurrir a cosas como la de tu ejemplo?
Otra cosa, ¿En que difieren Delphi y Free Pascal en la clase Exception? Yo dejé Delphi por Lazarus, pero no me puse a ver en detalle que cosas varían entre cada IDE... empleo las excepciones y no he visto en que se diferencian y ahora me queda la intriga.
Lo que era nuevo para mi es la función ExceptAddr.

Saludos,

mamcx
06-11-2015, 14:58:30
Lo que veo en el ejemplo de Ñuño es un intento de replicar lo que hace una libreria de logging solida, que permite hacer cosas como:



log.info("A")
log.warning("B")
log.debug("C")



Y se puede setear que los log solo se muestren en el nivel deseado.

Pero eso es tangencial al problema propuesto en este hilo. Osea, un log no tiene que ver con control de flujo.

AgustinOrtu
06-11-2015, 15:03:52
Hasta donde se en Delphi existen las directivas {$RELEASE} y {$DEBUG}. Pero no creo que se puedan activar o desactivar como la RTTI {$M+} y {$M-} por ejemplo

mamcx
06-11-2015, 15:04:26
.. Todo depende de la situación... Mucho más limpia que estar acumulando los resultados de funciones.

Procesar uno a uno los errores no es tan practico en todos los casos. Validar un formulario es el ejemplo clásico: Lo ideal es reportar todos los errores. En cambio, si falla la conexión a un recurso remoto no tiene sentido seguir generando mas errores. Asi que *estar acumulando los resultados * tiene sentido en los casos en donde las validaciones pueden ser paralelas y cuando el error siguiente no depende del estado del anterior.

Es por eso que el manejo de los errores en los lenguajes es algo "cojo", ya que normalmente estan fijado en un unico caso de uso. Curiosamente, el modelo UNIX con su STDERR/IN/OUT es ideal...

mamcx
06-11-2015, 15:09:26
Wow, coincidencialmente este articulo apareció hoy en reddit, que habla del tema:

http://mortoray.com/2015/11/06/everything-can-and-will-go-wrong-all-functions-fail/

AgustinOrtu
06-11-2015, 15:52:34
Acá (https://www.youtube.com/watch?v=_nV5HqOqBmY) se puede ver en accion como "validar formularios" (o mas tecnicamente, como dice mario, validaciones en paralelo), el chico lo hace utilizando atributos y queda bastante limpio y facil de mantener

Delphius
06-11-2015, 16:07:16
Lo que veo en el ejemplo de Ñuño es un intento de replicar lo que hace una libreria de logging solida, que permite hacer cosas como:

Código Delphi [-] (http://clubdelphi.com/foros/#) log.info("A") log.warning("B") log.debug("C")


Y se puede setear que los log solo se muestren en el nivel deseado.

Pero eso es tangencial al problema propuesto en este hilo. Osea, un log no tiene que ver con control de flujo.
Vuelve a leer el hilo, y sobre todo el código propuesto por Ñuño y dime donde ves que está implementando un sistema de Log de error.
En ningún momento lo hace. Se comentó que el uso de las excepciones y su captura puede ser útil para llevar un log, pero el ejemplo de Ñuño no sugiere nada de eso.
Lo que sugiere es disponer de una clase, registro, o algún tipo definido que tenga a modo de flag para definir si está en modo de depuración o no y aportar má o menos información sobre el error detectado.

Otro tema que me animo a exponerte es tu enorme fanatismo para intentar calzar a delphi a que haga o trabaje como lo hace otro lenguaje. Lo haces en cada intervención tuya. No mezcles peras y manzanas. Deja a cada lenguaje a que implemente su propio modelo y forma de hacer las cosas. No necesariamente está mal uno u otro. Simplemente no son comparables, ninguno es el malo o el bueno de la película.

Volviendo al hilo, o al menos, parte del tema discutivo al momento de reavivar el hilo es que como dice roman: se está forzando a redigirir una cuestión de lógica de negocio con el de flujo del programa.

Las excepciones deben planificarse bien. Si tienes planeado lanzar una excepción según un condicional, hay que pensarlo dos voces... ¿Hasta que punto se desea propagar? ¿Que se está por interrumpir? En ocasiones abusamos de las excepciones, cuando deberíamos repensar mejor el diseño y proponer otra forma de indicar que ha ocurrido un error.

Saludos,

Delphius
06-11-2015, 16:14:08
El punto es que una excepción es eso: ¡Algo inesperado, que no hemos contemplado o que al menos no esperamos que debiera suceder de alguna forma o en ciertas condiciones!
Emplear un condicional para evaluar si lanzamos una excepción que puede tener un significado en el contexto de negocio está mal.
Hay que diferenciar lo que es un mero aviso al usuario, de lo que es en realidad una excepción y para que en realidad fueron diseñadas. ;)

Saludos,

Ñuño Martínez
06-11-2015, 17:13:16
En la FCL (que es la base que usan los componentes de Lazarus), la clase TCustomApplication (de la cual salen luego diferentes "TApplication" para proyectos específicos: GUI, servidor, etc.) tiene un método llamado "Log", el cual es el que he usado en el código que he puesto (http://www.clubdelphi.com/foros/showthread.php?p=498727#post498727). Este es un método virtual que por defecto no hace nada. Diversos componentes y bibliotecas lo utilizan para ir dando cuenta de lo que hacen. El objetivo es que el programador extienda dicho componente y, recibiendo los parámetros, haga con ellos la bitácora adecuada: desde guardarlos en un archivo a enviarlos por correo o lo que sea. La verdad es que no sé si Delphi también tiene esto o algo parecido porque hace bastante tiempo que no lo manejo, y además la versión que dispongo es la 6.

En cuanto a la diferencia entre excepciones, tiene que ver con el compilador. Free Pascal y Delphi generan código diferente para manejar excepciones (y sé que el propio Free Pascal usa diferente código para diferentes sistemas operativos y procesadores, y supongo que las últimas versiones de Delphi también). Hay diferencias sutiles que, por lo que he visto, hacen referencia al funcionamiento interno. En general son iguales (la clase Exception, con sus constructores y sus propiedades), pero luego tienen algunas funciones internas en la RTL (system, sysutils, etc) donde hay alguna diferencia. Por ejemplo sé que Free Pascal/Lazarus incluye algunas propiedades en TObject y TComponent que no hacen nada, pero que están ahí por compatibilizar con Delphi, y añade otras que Delphi no tiene por compatibilizar con UNIX o MacOS, por ejemplo. Esto incluye algunas cosas en el manejo de RTTI para que las excepciones sepan de dónde vienen.

En definitiva, aunque en general son muy iguales, Lazarus/FreePascal y Delphi siguen siendo diferentes.

roman
06-11-2015, 18:20:31
Validar un formulario es el ejemplo clásico: Lo ideal es reportar todos los errores. En cambio, si falla la conexión a un recurso remoto no tiene sentido seguir generando mas errores. Asi que *estar acumulando los resultados * tiene sentido en los casos en donde las validaciones pueden ser paralelas y cuando el error siguiente no depende del estado del anterior.


Sip. Estoy de acuerdo contigo. Una falla en la conexión es una excepción. Un dato incorrecto de un formulario no.

// Saludos

mamcx
06-11-2015, 18:32:34
Vuelve a leer el hilo, y sobre todo el código propuesto por Ñuño y dime donde ves que está implementando un sistema de Log de error.



ON Error: Exception DO
BEGIN
IF Config.Depurando THEN
sysutils.ShowException (Error, ExceptAddr)
ELSE
Application.MessageBox ('No pudo realizarse la operación', 'Error');
Application.Log (etError, Format ('[%s] %s at %p', [Error.ClassName, Error.Message, ExceptAddr]));
END


Nota el comentario: "Lo que veo en el ejemplo de Ñuño es un intento de replicar...". No quise decir que estaba haciendo un sistema de Log, sino que replica lo que hace uno de estos, osea, un sistema de log permite hacer llamadas a "info", "debug", "warning", y potencialmente llegar a lo que se esta mostrando: Si quiero, cuando sea "debug" mandalo a consola op archivo y si es "warning", mostrar un mensaje o lo que sea.

O dicho de otra forma, es una implementacion de lo que hace Ñuño!

Re-leyendo veo que me falto aclarar porque rayos menciono el punto, y es que hablaste de usar las directivas de compilacion con relacion a esto. Y cuando preguntas:

¿O realmente se necesita de recurrir a cosas como la de tu ejemplo?

Es algo que es obvio cuando uno esta acostumbrado a usar una libreria de LOG, pero no tanto en el ejemplo propuesto. Osea: Porque usar una condicional en vez de usar la directiva de compilacion? Porque los errores/mensajes no son todos iguales y tienen diversos niveles: Asi como hay "warnings" y "error" a la hora de compilar, una app puede lanzar una excepcion, pero estas son "warnings" otras son "errors" otras son "catastrophic errors" otras son "ignorables/infos", etc, y es el programador y no el compilador que determina a que nivel asignar cada cosa. Lo mismo decir que una excepcion es parte de la logica de negocio, o interna, o que es algo pal' usuario o solo para el desarrollador.

Solo que en el ejemplo de codigo, se esta asumiendo (que ademas es muy normal) que en modo DEBUG solo existe el desarrollador. Y es muy practico en tiempo de ejecucion, sin usar IDEs, setear la app a modo "debug" para diagnosticarla cuando esta corriendo donde el cliente: Cosa que es lo que se hace con una libreria de log robusta ;) Y esa era la conexion que pobremente intente hacer!


Otro tema que me animo a exponerte es tu enorme fanatismo para intentar calzar a delphi a que haga o trabaje como lo hace otro lenguaje. Lo haces en cada intervención tuya. No mezcles peras y manzanas. Deja a cada lenguaje a que implemente su propio modelo y forma de hacer las cosas. No necesariamente está mal uno u otro. Simplemente no son comparables, ninguno es el malo o el bueno de la película.


Sorry. La intención de mostrar como se hace en otros entornos es tratar de dilucidar otras formas de hacer las cosas... Tambien es que asi como cuando programo a veces me veo intentando escribir un lenguaje cuando estoy en otro, se me cruzan los cables cuando hablo de ellos. Pienso que existe un lenguaje, que es superior a todos, pero que solo esta de forma parcial en cada uno.. y me pregunto como hacer que lo mejor de unos llega a los otros que uso...

Delphius
06-11-2015, 20:07:04
En la FCL (que es la base que usan los componentes de Lazarus), la clase TCustomApplication (de la cual salen luego diferentes "TApplication" para proyectos específicos: GUI, servidor, etc.) tiene un método llamado "Log", el cual es el que he usado en el código que he puesto (http://www.clubdelphi.com/foros/showthread.php?p=498727#post498727). Este es un método virtual que por defecto no hace nada. Diversos componentes y bibliotecas lo utilizan para ir dando cuenta de lo que hacen. El objetivo es que el programador extienda dicho componente y, recibiendo los parámetros, haga con ellos la bitácora adecuada: desde guardarlos en un archivo a enviarlos por correo o lo que sea. La verdad es que no sé si Delphi también tiene esto o algo parecido porque hace bastante tiempo que no lo manejo, y además la versión que dispongo es la 6.

En cuanto a la diferencia entre excepciones, tiene que ver con el compilador. Free Pascal y Delphi generan código diferente para manejar excepciones (y sé que el propio Free Pascal usa diferente código para diferentes sistemas operativos y procesadores, y supongo que las últimas versiones de Delphi también). Hay diferencias sutiles que, por lo que he visto, hacen referencia al funcionamiento interno. En general son iguales (la clase Exception, con sus constructores y sus propiedades), pero luego tienen algunas funciones internas en la RTL (system, sysutils, etc) donde hay alguna diferencia. Por ejemplo sé que Free Pascal/Lazarus incluye algunas propiedades en TObject y TComponent que no hacen nada, pero que están ahí por compatibilizar con Delphi, y añade otras que Delphi no tiene por compatibilizar con UNIX o MacOS, por ejemplo. Esto incluye algunas cosas en el manejo de RTTI para que las excepciones sepan de dónde vienen.

En definitiva, aunque en general son muy iguales, Lazarus/FreePascal y Delphi siguen siendo diferentes.
El método Log es medio nuevo para mi. Te comento que tienes un pequeño desliz: el método virtual y que puede sobreescribirse es el DoLog() y no Log()

Esta es la implementación de Log en TCustomApplication:

procedure TCustomApplication.Log(EventType: TEventType; const Msg: String);

begin
If (FEventLogFilter=[]) or (EventType in FEventLogFilter) then
DoLog(EventType,Msg);
end;

Que invoca al método virtual DoLog:

Protected
// ...
Procedure DoLog(EventType : TEventType; const Msg : String); virtual;

Cuya implementación está vacía:


procedure TCustomApplication.DoLog(EventType: TEventType; const Msg: String);

begin
// Do nothing, override in descendants
end;


Tu código, aún intentando hacer un sistema de logging no tiene sentido que invoques a Log(), al menos en aplicación tradicionales, porque no hará nada: ¡la clase TApplication no lo sobre escribe!

Lo que comentas sobre las excepciones me parece por demás lógico que internamente, como en buena parte de la LCL, haga trabajo duro para adaptarse a lo multiplataforma. Yo más bien me preguntaba de cara al desarrollador, en lo que es público y/o publicado. Explorando hace un tiempo en TObject me pareció ver cosas como la que dices, que hay cosas que no hacen nada... Y vi cosas que D6 no tiene... como el ToString().

Es verdad que dices que ha pesar de ser similares, tienen también sus diferencias significativas.

Código Delphi [-] (http://clubdelphi.com/foros/#)ON Error: Exception DO BEGIN IF Config.Depurando THEN sysutils.ShowException (Error, ExceptAddr) ELSE Application.MessageBox ('No pudo realizarse la operación', 'Error'); Application.Log (etError, Format ('[%s] %s at %p', [Error.ClassName, Error.Message, ExceptAddr])); END


Nota el comentario: "Lo que veo en el ejemplo de Ñuño es un intento de replicar...". No quise decir que estaba haciendo un sistema de Log, sino que replica lo que hace uno de estos, osea, un sistema de log permite hacer llamadas a "info", "debug", "warning", y potencialmente llegar a lo que se esta mostrando: Si quiero, cuando sea "debug" mandalo a consola op archivo y si es "warning", mostrar un mensaje o lo que sea.

O dicho de otra forma, es una implementacion de lo que hace Ñuño!

Re-leyendo veo que me falto aclarar porque rayos menciono el punto, y es que hablaste de usar las directivas de compilacion con relacion a esto. Y cuando preguntas:



Es algo que es obvio cuando uno esta acostumbrado a usar una libreria de LOG, pero no tanto en el ejemplo propuesto. Osea: Porque usar una condicional en vez de usar la directiva de compilacion? Porque los errores/mensajes no son todos iguales y tienen diversos niveles: Asi como hay "warnings" y "error" a la hora de compilar, una app puede lanzar una excepcion, pero estas son "warnings" otras son "errors" otras son "catastrophic errors" otras son "ignorables/infos", etc, y es el programador y no el compilador que determina a que nivel asignar cada cosa. Lo mismo decir que una excepcion es parte de la logica de negocio, o interna, o que es algo pal' usuario o solo para el desarrollador.

Solo que en el ejemplo de codigo, se esta asumiendo (que ademas es muy normal) que en modo DEBUG solo existe el desarrollador. Y es muy practico en tiempo de ejecucion, sin usar IDEs, setear la app a modo "debug" para diagnosticarla cuando esta corriendo donde el cliente: Cosa que es lo que se hace con una libreria de log robusta ;) Y esa era la conexion que pobremente intente hacer!



Sorry. La intención de mostrar como se hace en otros entornos es tratar de dilucidar otras formas de hacer las cosas... Tambien es que asi como cuando programo a veces me veo intentando escribir un lenguaje cuando estoy en otro, se me cruzan los cables cuando hablo de ellos. Pienso que existe un lenguaje, que es superior a todos, pero que solo esta de forma parcial en cada uno.. y me pregunto como hacer que lo mejor de unos llega a los otros que uso...
Es cierto, lo intenta... pero como ya he dicho: no lleva a nada el Log().

Ahora que lo aclaras y mencionas lo de "clasificar" los tipos de errores, tiene bastante sentido y su lógica el emplear alguna forma de condicional para jugar con los niveles de advertencias. Más a lo que apuntaba yo es que tan necesario es recurrir a "inventos" como éste y quizá habría una forma de indicar cuando se está en modo debug y/o empleando el IDE y cuando se está en "modo release" mediante alguna directiva de compilación y no tener que estar alterando el código fuente demasiado.

La otra pata que se ha debatido en el hilo y también lleva a confusión y mezclado con otros temas discutidos es que esto:


TRY ... { Código aquí... } IF IntentoBorrarYNoDebe THEN RAISE Exception.Create ('¡No puede eliminar eso!'); ... { Código allá... } EXCEPT ON Error: Exception DO Application.MessageBox (Error.Message, 'Error') END;
No debería hacerse así. Si el contexto del negocio ya sugiere que hay elementos no deben ser borrables, sugerir una excepción para lidiarlo no es apropiada. Es un poco más limpio la propuesta de AgustinOrtu:


procedure Borrar(const IdRegistro: Int64); begin if IsSystemProtectedRec(IdRegistro, ATableName) then raise ESystemProtectedRec.CreateFmt('Cannot delete the record %d of the table %s because is system protected', [IdRegistro, ATableName]); { codigo "normal" de borrado de registros normales } end;


Pero igual tiene el problema que bien a señalado roman. Mezclar y abusar excepciones con el contexto de negocio. ¡Aquí la excepción sobra!:

procedure DeleteRec(const IdRegistro: int64);
begin
if NOT IsSystemProtectRec(IdRegistro, ATableName)
then DoDelete(idRegistro, ATableName)
else ShowMessage('Por razones de seguridad el registro seleccionado no está permitido eliminar. Verifique que esté todo en orden o elija otro);
end;

Saludos,

Delphius
06-11-2015, 20:16:06
Para generar Logging, en Lazarus/Free Pascal, hay además de sobreescribir el DoLog() como pretendía sugerir Ñuño otras formas. En el artículo Logging Exceptions (http://wiki.freepascal.org/index.php?title=Logging_exceptions) hay material de estudio. Dependiendo de como se configure el Debugger se puede tener más o menos información. Al menos en "modo debug" y mientras se configure al proyecto para lleve registro de debug.

Saludos,