Ir al contenido


Foto

¿Tienen alguna utilidad el procedimiento Assert?


  • Por favor identifícate para responder
9 respuestas en este tema

#1 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 24 febrero 2011 - 08:09

Hola,
Nuevamente yo con mis preguntas peliagudas y filosóficas... prometo no hablar de temas de curvas perpendiculares 

Bueno, al grano. Como dice el título: ¿Tienen alguna utilidad los assert?
Por lo que tengo entendido este procedimiento solo sirve para comprobar de que nuestras evaluaciones y comprobaciones se ejecuten como Dios manda.
Assert provoca una excepción en caso de que se detecte que la condición sea evalúe a falso.

¿No es acaso esto equivalente?



delphi
  1. if NOT (Assigned(MiObjeto))
  2.   then LanzarExcepcionPropia;



A algo como:



delphi
  1. Assert(Assigned(MiObjeto),'Una Excepción que confirma que el objeto no ha sido asignado');



Me parece que es mucho más transparente y útil poder trabajar con nuestras propias excepciones. Asssert por su parte crea una excepción EAssertionFailed.

Además tengo entendido que Assert sólo funciona cuando estamos en debug; por lo que serviría en teoría en una aplicativo cuando está en etapa de desarrollo o producción como le llaman algunos. Si no generamos o habilitamos el guardado de información referente a debug (algo aparentemente normal cuando se trata de un aplicativo final) entonces no sirve de nada.

Me pregunto entonces ¿Hay alguna otra utilidad o uso para Assert? ¿En que más me puede beneficiar? 

Saludos,
  • 0

#2 Héctor Randolph

Héctor Randolph

    501st Legion

  • Moderadores
  • PipPipPip
  • 664 mensajes
  • LocationMéxico

Escrito 24 febrero 2011 - 09:33

La principal ventaja de Assert es precisamente que es una excepción que se dispara solamente en tiempo de desarrollo, en específico cuando compilamos con la información de Debug.

La cuestión es diferenciar el tipo de validaciones que solamente nos interesa mostrar a un desarrollador sin que lleguen a nivel usuario.

El ejemplo que pones sirve para ilustrar el tipo de mensajes que al usuario final no le aportan algo útil.



delphi
  1. Assert(Assigned(MiObjeto),'Una Excepción que confirma que el objeto no ha sido asignado');



El mensaje contiene información valiosa que solamente un desarrollador puede interpretar correctamente, este mecanismo se puede utilizar para prevenir el acceso a los métodos o atributos de una clase no instanciada, en otro caso solamente obtendríamos un Access Violation que tal vez no diga mucho.

No instanciar una clase es un error muy común y a través de Assert se pueden dejar pistas que lleven a una pronta detección y solución. (lo deseable será detectar esto en etapa de desarrollo y no cuando estamos en producción)

Además Assert proporciona información adicional pues su comportamiento por default es mostrar el archivo y la línea en donde salta la excepción, por eso requiere que el proyecto se compile con información de debug.

Por otra parte como bien indicas, si eliminamos la información de debug para poner un aplicativo en producción eliminamos también las excepciones generadas por Assert, pero ese efecto es deseado ya que son mensajes que pueden confundir al usuario final.

Saludos.



  • 0

#3 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 25 febrero 2011 - 08:59

Hola Héctor,
Te agradezco tu ayuda.
Eso lo tengo entendido. Se que el Assert es una buena herramienta y que sólo le es para el desarrollador.

El punto es que ya me lo estaba preguntando si por casualidad no habría algo más... He visto por ejemplo en el código de DUnit mucho el uso de Assert, también en alguno que otro ejemplo de algunos foreros y ya me ando creyendo que hay algo más y no logro ver.

Apoyándome en mi ejemplo. He visto cosas como éstas:



delphi
  1. MiObjeto := TMiObjeto.Create;
  2. Assert(Assigned(MiObjecto))
  3. ....
  4. MiObjeto.Free;



Y otro ejemplo que me llama la atención:



delphi
  1. procedure TOtroObjeto.Algo(UnObjeto: TAlgunObjeto; ....)
  2. begin
  3. Assert(Assigned(UnObjeto), 'Objeto no instanciado');
  4. ...



En el primer ejemplo, A mi ver el uso de Assert está demás. En todo caso lo que se debería hacer es controlar la situación con un Try/finally tratando de hacer uso de dicho objeto. Si tenemos ha disposición el uso de try/finally ¿Porqué descartarlo? Además, no es algo muy de esperar que falle la creación de un objeto.

Y en el segundo caso lo lógico es: o bien tratando la situación capturando la excepción y volver a un estado que OtroObjeto entienda y sepa manejar (try/exception/finally). O si el diseño lo amerita dejar propagar la excepción esperando que otro objeto, en otro lugar, la sepa tratar y dar respuesta (raise).
NO se debería tratar con Assert algo que es de esperar en la propia lógica (y/o que lo amerita) del método.

Existe una serie de patrones relacionados con las excepciones. Uno de ellos se conoce como Convertir Excepciones. El patrón sugiere que una excepción de menor nivel que será devuelta (propagada) a un nivel superior debería ser convertida en una excepción acorde al nivel o contexto esperado.

En el caso, como la excepción corresponde al contexto de TOtroObjeto y en la necesidad de propagarla, debería entonces capturarla y elevar alguna clase de excepción que pudiera esperar hacia la clase en cuestión. Digamos que esa tercera clase es TClaseMasAlla, la excepción que podría y debería trabajar TClaseMasAlla debería ser como ExcepcionMasAlla, más alguna referencia en particular sobre TOtroObjeto.

Para hacerlo más concreto, y bajarlo a tierra. Supongamos el caso que una clase encargada sobre persistencia captura una excepción de nombre SQLException, y de no poder manejarla, debe lanzar una nueva... DBNoDisponibleException que la contiene. Luego éste también se ve motivado a pasarla, digamos que va a parar a una clase destinada al manejo de productos y que el contexto a analizar el el caso en que se ha estado intentando extraer cierta información del mismo. La excepción a este nivel podría ser InfoProductoNoDisponibleException que encapsula a DBNODisponibleException.

¿Se entiende? El punto es que a la clase que le va dirigida la excepción debe remitirsele una acorde a lo que maneja. En buena parte, Delphi sigue este patrón  ;) Y Casualmente en el contexto de conexiones a bases de datos. Véase el uso de EDatabaseError, y EDBEngineError por ejemplo.

Yo, o es que encontré un mal uso de Assert en esos ejemplos o es que hay algo más que no logro detectar y comprender... Puedo entender que Assert ofrece mucha info de utilidad como el número de línea, la unidad que lo emite, etc. pero me parece que emplear un Assert cuando la misma lógica y diseño del contexto en el que habita ofrece información y los recursos para evitarlo debería utilizarse éstos y no un Assert.

Lo resumo en una frase: ¿Para que vas a comprobar y tratar a algo obvio y que ya sabes como y cuando se va a dar como un estado NO esperado?

Saludos,
  • 0

#4 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 26 febrero 2011 - 11:09

¿Nadie?   

¿Será que mi pregunta peliaguda filosofal ha encontrado aburrirlos? Lo cual es muy probable, o es que ahora si me rayé del todo y nadie me entiende     

Para comprobar mi tesis, en el foro de Firebird vine con unas dudas menos filosofantes. Si allí me responden se confirma mi teoría: la cantidad de respuestas será inversamente proporcional al contenido filosófico de las preguntas. 

Saludos,
  • 0

#5 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 06 marzo 2011 - 10:29

:( : :(
¿Será que ahora si llegué a confundirlos?  :s ¿Nadie tiene algo más para aportar y/o orientarme más adentro en el tema?

Saludos,
  • 0

#6 Sergio

Sergio

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.092 mensajes
  • LocationMurcia, España

Escrito 07 marzo 2011 - 05:34

Delphius, yo no conocia el assert, pero le veo un par de utilidades, una es lo que dice la ayuda, es decir, evitarte escribir algo como "IF (condicion) THEN raise Exception.create('Ha pasado esto');" y ademas que solo te aparezca el error a ti y no al usuario (ojo, esto no es asi por defecto segun la ayuda de delphi 7).

La otra funcionalidad sería la de implementar comprobaciones de consistencia dentro de tus objetos: Tras crear un objeto, al final, haces assert con todas las cosas que se suponen son ciertas, tipo "suma de las entradas = suma de las salidas" y este tipo de cosas, y lo mismo antes de un free a un objeto, tambien puedes comprobar que lo que vas a eliminar esta "sano" y es consistente, por ejemplo si tu objeto usa un fichero temporal, y se supone que ha de ser eliminado, aqui lo comprobarias antes de matarlo.

Esto lei que ya lo trae el delphi xe pero con unos assert lo puedes añadir a tus objetos mas "delicados".

Incluso puedes poner un procedure al objeto tipo "MyClass.CheckConsistency" con todos estos assert, de fomra que puedas llamarlo tras crearse, antes de liberarlo, o incluso antes de hacer uso del objeto como primera linea de cada procedure, asi, si algun proceso desestabiliza el objeto (normalmente nosotros tecleando tonterias un lunes) lo detectas inmediatamente (y luego compilas con esto desactivado para no recargar el exe en producción).

  • 0

#7 andres1569

andres1569

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 431 mensajes

Escrito 07 marzo 2011 - 06:11

   
¿Será que ahora si llegué a confundirlos?  ¿Nadie tiene algo más para aportar y/o orientarme más adentro en el tema?

Saludos,

No es eso, Delphius, no pongas esa cara,  :D , a mí personalmente es un tema al que nunca le he prestado atención, no le vi mucha utilidad desde un principio, cuando vi la explicación de Marteens en La Cara Oculta de Delphi, aunque ahora leyendo a Sergio pienso que quizás pueda tener sentido utilizar Asserts en rutinas que estén expuestas a cambios frecuentes y donde queramos asegurarnos de que no modifiquemos por error en el futuro algo que altere ciertas condiciones inviolables. Aunque es, digamos, una sentencia algo paranoica  :o

Saludos
  • 0

#8 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 07 marzo 2011 - 11:49

Hola,
El asunto es que yo entiendo el uso de Assert en código de producción y mientras se está en pruebas ya que como se ha dicho esto aporta información sobre la LDC que produce el error, la unidad y más. Pero lo que me cuesta apreciar es una utilidad práctica en un código que se supone que es de carácter final; y les digo que he visto su uso en algunas versiones y liberaciones de algunas bibliotecas como ser el caso de DUnit, que utiliza Assert dentro de su código fuente como también para emitir los informes de errores cuando uno ejecuta los casos de pruebas (este segundo uso está descartado de discusión ya que es de esperarse su uso, por razones obvias... creería que no requiere explicación del porqué).

En los ejemplos que he expuesto, que si bien son simples, se aprecia como el uso de Assert lleva a una comprobación, no sólo redundante, sino que además contraria a los supuestos de la lógica por la cual ha sido encarado el diseño del algoritmo del método en cuestión.

En el primer ejemplo, el de Create. Sabemos que el mismísimo .Create no es más que try/except camuflado que aplica un .Destroy si se detecta un error al momento de su creación. Por tanto, un objeto o se construye o se destruye, no puede crearse un objeto a medias.

El aplicar un Assert() con un Assigned está rompiendo el contrato que se ha definido en el algoritmo. Si se necesitase, realmente, de un control de algún recurso (como el propio objeto a crear, incluso) Delphi nos ofrece las herramientas para garantizar que se procederá y el contrato se podrá celebrar sin romperse añadiendo una excepción explícita (en el apartado * explico mejor el tema del "explícito"). La dichosa herramienta es el uso de try/except.
¡SI VAMOS A HACER USO DE UN OBJETO, ENTONCES CONTROLEMOS SU USO, NO INTENTEMOS VERIFICAR DE NUEVO SU CREACION!. Es decir:



delphi
  1. MiObjecto = TMiObjeto.Create; // Create no regresa una excepción
  2. try
  3.   // aquí intentamos hacer uso del objeto
  4. except
  5.   // aquí vamos a "controlar" el problema
  6. end;



Si dentro del código estamos ya predispuestos a aceptar que algo podría fallar (estamos aceptando algunas de las condiciones contractuales) entonces pongamos los recursos adecuados para ello. No deberíamos tratar al problema escapando los lineamientos contractuales, va en contra de la lógica del algoritmo que estamos esperando.

Y si estamos dispuesto a aceptar las condiciones de devolver a un estado de "normalidad", por contrato, y de la propia liberación de recursos estamos entonces en un caso try/finally:



delphi
  1. // pedimos recurso
  2. MiObjeto := TMiObjeto.Create;
  3. try
  4.   // como pretendemos hacer uso del objeto
  5. finally
  6.   // operaciones de limpieza
  7.   MiObjeto.Free;
  8. end;



¡El pretender lanzar un Assert evaluando si el objeto ha sido creado, sabiendo a conciencia que luego será liberado NO TIENE SENTIDO! Veamos este "peor caso": "Creo" y hay un problema al pedir memoria por lo que se han liberado los posibles recursos consumidos y/o pedidos; informo de que no se ha construido e inmediatamente libero.... ¡No tiene coherencia! Para que liberar (nuevamente) algo que no has construído.
Por ello es que directamente, por simple apreciación lógica, de que es correcto el uso de las posibilidades que nos ofrece try: intentar utilizar un recurso.

Assert nos obliga a aceptar, de forma impuesta, algo contrario a lo que dice el contrato.

El segundo caso es quizá más grave, ya que tiene un grave fallo conceptual. En estos casos el método que recibe como parámetro un objeto ha celebrado un contrato con dicho objeto de comprobar si efectivamente ha sido creado y de actuar en consecuencia. Perfecto, hasta allí estamos bien y es un principio totalmente válido y es más que normal y de esperarse.
Cada objeto es responsable de su propia creación (excepto en los casos de una factoría ó fábrica abstracta) y ya ha quedado en claro que está creado o no lo está.

Asumamos el caso de que TOtroObjeto no asume responsabilidad alguna de creación de TAlgunObjeto; entonces lo máximo a que debería limitarse es de comprobar si el objeto está en condiciones de uso.

Por definición entonces, la lógica indica que es algo de esperar y simple algo como:



delphi
  1. if Assigned()
  2. then



O bien, como se ha visto un uso de try/except. Para el caso no es aceptable como opción válida try/finally porque viola el contrato: no se ha definido y aceptado su liberación, sólo su uso. Mejor lo repito...

El uso Except como seguramente recordarán es que nos regresa a un estado de calma (si... damos por entendido, y explícitamente aceptado, de que algo ha fallado) pero nos permite tomar acciones para al menos intentar corregir la situación por un plan B (si es que lo tenemos).
¿No es acaso nuevamente, otra de las propias herramientas que nos ofrece Delphi y va en la misma tónica con la que se ha estado pensando el algoritmo? ¿No es acaso un caso más de conciencia y de sentido lógico? ¡Pues claro que si! ¡Entonces hagamos uso de eso!



delphi
  1. procedue TOtroObjeto.Algo(UnObjeto: TAlgunObjeto);
  2. begin
  3. try
  4.   // intentamos usar UnObjeto
  5.   UnObjeto.HacerAlgo;
  6. except
  7.   TOtroObjeto.Tranquilizar;
  8. end;



Una vez que hemos tranquilizado al programa este puede seguir operando y no estar rompiendo el circuito normal de operaciones.
Si utilizáramos Assert, no sólo seguimos a la contraria, sino que hemos roto dos contratos: en primera no hemos sido capaces de reconocer que estamos en condiciones (por imposición) de uso de TAlgunObjeto y el segundo contrato violado, de forma indirecta, es que ahora TAlgunObjeto debe ajustarse a nuevas condiciones de uso que TOtroObjeto no supo tratar. Es decir: acabamos de hacer una limpieza de manos por parte de ambos objetos que han intervenido... es como decir que Pepito y Juancito han estado jugando a la pelota, rompieron un vidrio y ninguno se quiere (ni sabe) hacerse cargo de las consecuencias.

En caso de no contar con planes B, lo adecuado y lo que dicta el sentido común es propagar la excepción y dejarlas en manos de quien sabe. Ahora surje el problema: ¿Y si nadie sabe como asumirlas? Entonces algo no encaja aquí... si no hay nadie que traiga calma, es un sistema crítico e inestable; hay un error de concepto y no se han definido de forma claras las propias responsabilidades.

El uso de Assert, es justo eso: aceptar, y rendirnos, de que hay algo que no estamos en condiciones de aceptar.

* El tema del término "explícito" es que Assert emite una excepción. ¡Si, una excepción! no contemplada. Impuesta y que rompe los propios diseños... Se trata de una falta de lectura de letras pequeñas... una excepción implícita no reconocida.

Si ya para llevar tranquilidad hacemos uso del manejo excepciones, ¿porqué he de asustarnos con algo que ya le conocemos la mano y en como tratar? Assert nos genera una excepción que rompe nuestra manera de pensar, un EAssertFailed.
Pensemos más, sepamos trabajar con excepciones que podríamos aceptar... ¡si ya le veníamos trabajando!

Recuerden que el concepto de Excepción no significa romper el contrato o que lo viole. Las EXCEPCIONES NO VIOLAN LOS CONTRATOS, son parte de ellos... son cláusulas de seguridad. ¿Porqué, repito, porqué he de trabajar con una excepción que rompa con los contratos cuando por definición, son partes de éstos?

Por ello es que surge todo este lío. No le veo sentido en un código final... sólo durante pruebas; pero en código de otros he visto su uso tratandose de casos "finales". ¿Qué se me escapa?

Saludos,
  • 0

#9 Sergio

Sergio

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.092 mensajes
  • LocationMurcia, España

Escrito 08 marzo 2011 - 02:51

Delphius, creo que tu esperas que A) Se haga *solo* un uso inteligente del assert, B) que lo eliminen una vez sacan el codigo a la calle.

En A) creo que esperas demasiado, simplemente. Comprobaciones redundantes las hago yo tambien cuando algo raro pasa y no se que es... meto comprobaciones en todo lo que se me ocurre... y luego las elimino, casi todas... pasados los años descubro un trocito olvidado que comprueba que 2+2=4 "por si las moscas"... paranoias de cuando algo te falla y empiezas a dudar de todo (tu sabes que 2*(A/2)=A no es siempre cierto en programacion, asi que si ves un assert de este tipo, pues bueno, tiene hasta su logica).

En B) creo que olvidas -lo dudo, la verdad- que no es necesario eliminarlo del codigo final, solo incluir una orden del precompilador para que no les haga ni caso... nadie va a ir quitando esas lineas una a una si puede desactivarlas.

No le des mas vueltas, las herramientas son para usarlas, no solo para usarlas correctamente, y mucho menos para usarlas para aquello para lo que fueron ideadas, eso último es hasta aburrido!
  • 0

#10 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 08 marzo 2011 - 09:29

Buen Sergio, el texto filosófico y abogatrucho  :D que me mandé anoche es el seguimiento de mi opinión y en como estuve interpetrando y analizando el tema. Esperaba que con ello me pudiera expresar mejor... no es que haya ido con la idea de la verdad absoluta.

Quizá es como dices, que no debería darle demasiada vueltas.
No espero tratar de convencer a ustedes de un uso correcto del Assert, es que me gustaría tratar de entenderle mejor su uso. Se bien que existe la cláusula para que el compilador los ignore; de lo admito (ahora) es que no estoy totalmente seguro de si visto en los ejemplos dicha cláusulas como para decir que estaba inactiva.

Lo que comentas sobre lo de que en ocasiones nos vemos obligados a hacer esas comprobaciones, como lo dije en otra oportunidad en otro hilo, lo entiendo (yo mismo caigo en esas cosas)... pero a eso yo lo veo como una aceptación formal (parte de la misma lógica del programa, y no como vía de escape) de una cláusula o punto contractual... parte del contrato: Tu asumes la lógica, tu asumes los controles y la cuota de error esperado.
Por ejemplo, el caso de las operaciones con punto flotante: el sólo hecho de utilizarlos estás ACEPTANDO cierto margen de error y por tanto debes llevar los contratos en base a ello.

Debo admitir que me están dando ganas de leer el libro que recomienda Craig Larman sobre Diseño basado en contratos.

Saludos,
  • 0




IP.Board spam blocked by CleanTalk.