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:
MiObjecto = TMiObjeto.Create; // Create no regresa una excepción
try
// aquí intentamos hacer uso del objeto
except
// aquí vamos a "controlar" el problema
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:
// pedimos recurso
MiObjeto := TMiObjeto.Create;
try
// como pretendemos hacer uso del objeto
finally
// operaciones de limpieza
MiObjeto.Free;
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: i
ntentar 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:
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!
procedue TOtroObjeto.Algo(UnObjeto: TAlgunObjeto);
begin
try
// intentamos usar UnObjeto
UnObjeto.HacerAlgo;
except
TOtroObjeto.Tranquilizar;
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,