Club Delphi  
    FTP   CCD     Buscar   Trucos   Trabajo   Foros

Retroceder   Foros Club Delphi > Principal > OOP
Registrarse FAQ Miembros Calendario Guía de estilo Buscar Temas de Hoy Marcar Foros Como Leídos

Grupo de Teaming del ClubDelphi

Respuesta
 
Herramientas Buscar en Tema Desplegado
  #1  
Antiguo 02-02-2018
Avatar de movorack
[movorack] movorack is offline
Miguel A. Valero
 
Registrado: feb 2007
Ubicación: Bogotá - Colombia
Posts: 1.346
Poder: 20
movorack Va camino a la famamovorack Va camino a la fama
Bloques Try-Finally para proteger múltiples recursos en Delphi (Traducción)

Entrada original (En ingés)

El lenguaje Delphi comparte con muchos otros un patrón de asignación de recursos estándar para garantizar que, en el caso de una excepción, los recursos se liberen de forma adecuada. Los recursos en este contexto son objetos de memoria, archivos,
objetos y manejadores del sistema operativo, y similares. En Delphi, en comparación con otros lenguajes con un recolector de basura, la relevancia aumentó por consideraciones de administración de memoria.

Protegiendo un solo objeto

En los casos más simples, escribirías código como:

Código Delphi [-]
Asignar recurso 
try 
  usar recurso 
finally 
  liberar recurso 
end;

Un ejemplo más específico sería como:

Código Delphi [-]
A1: = TTest.Create; 
try 
  A1.DoSomething; 
finally
  A1.Free; 
end;

Hasta aquí todo bien. Tenga en cuenta que si se produce un error durante la ejecución del constructor, Delphi ejecutará automáticamente el destructor para el objeto parcialmente inicializado (pero este podría ser un tema para otra publicación de blog).


Protección de dos objetos: cómo NO escribir el código

El problema en el que me quiero enfocar es cómo escribir el mismo tipo de código si necesita asignar y eliminar dos recursos. Aquí hay múltiples opciones. Lo que no debes hacer (pero es bastante común) es escribir:

Código Delphi [-]
A1 := TTest.Create; 
A2 := TTest.Create; 
try 
  A1.DoSomething; 
  A2.DoSomething(A1); 
finally 
  A2.Free; 
  A1.Free; 
end;

El problema con este código es que, en caso de que falle la creación de A2 (y podría haber muchas razones), el objeto A1 permanecería en la memoria. Y simplemente colocar la segunda asignación dentro del bloque TRY tampoco es bueno:

Código Delphi [-]
A1 := TTest.Create; 
try 
  A2 := TTest.Create; 
  A1.DoSomething; 
  A2.DoSomething(A1); 
finally 
  A2.Free; 
  A1.Free; 
end;

Con este código en caso de una falla en la llamada al constructor del objeto A2, el bloque finally intentará liberar un objeto no inicializado (el valor predeterminado de una referencia de objeto local no está definido). Esta es la razón por la cual una posible solución es establecer A2 a cero al comienzo, ya que llamar Free en un objeto nulo no tiene ningún efecto. O configurar todas las referencias de objetos como nulas para simplificar y uniformizar. O escriba dos bloques try anidados para proteger cada uno de los recursos.


Protección de dos objetos: una historia de tres soluciones

Esta larga introducción nos lleva al punto de esta publicación de blog. Hay al menos 3 soluciones correctas diferentes para la protección de dos recursos en el mismo bloque de código, como acabo de mencionar. Estas son las tres soluciones que "tomé prestada" de uno de nuestros arquitectos de RAD Studio R & D, Bruneau Babet.

#1
Código Delphi [-]
var
  A1, A2: TTest;
begin
  A1 := TTest.Create;
  try
    A2 := TTest.Create;
    try
      //Use A1 and A2
    finally
      A2.free;
    end;
  finally
    A1.free;
  end;
end

#2
Código Delphi [-]
var
  A1, A2: TTest;
begin
  A2 := nil;
  A1 := TTest.Create;
  try
    A2 := TTest.Create;
    //Use A1 and A2
  finally
    A2.free;
    A1.free;
  end;
end

#3
Código Delphi [-]
var
  A1, A2: TTest;
begin
  A2 := nil;
  A1 := nil;  
  try
    A1 := TTest.Create;
    A2 := TTest.Create;
    //Use A1 and A2
  finally
    A2.free;
    A1.free;
  end;
end

Siempre que todos sean correctos en términos de gestión de recursos adecuada en todos los escenarios, ¿cuáles son las ventajas y desventajas de estas 3 soluciones? Lo que es importante considerar es que los 2 recursos podrían ser 3, o 4 o media docena.

¿Cuál elegir?
La primera solución con los bloques de intentos anidados es bastante limpia pero más detallada (más líneas de código) y tiene anidamiento adicional que podría convertirse en una molestia con múltiples recursos. Además, hay un costo de tiempo de ejecución asociado con los bloques try, claramente limitado pero no cero.

La segunda solución tiene la menor cantidad de líneas de código y la menor cantidad de tiempo de ejecución. El único adicional es configurar A2 a nil. El código sigue siendo legible también con muchos recursos. Sin embargo, el código es "desequilibrado" y podría ser un poco confuso.

La tercera solución va en la misma dirección, pero agrega una asignación adicional técnicamente inútil a nil (para A1), ofreciendo la ventaja de ser más limpio y más equilibrado, y probablemente más legible después de todo.

Entonces, ¿cuál es la mejor solución? Esto es realmente difícil de decir. Personalmente usé principalmente el número 1 en mis libros, pero en Embarcadero tendemos a preferir los más limpios y más rápidos (es decir, los n. ° 2 o n. ° 3) para el código de la biblioteca
__________________
Buena caza y buen remar... http://mivaler.blogspot.com
Responder Con Cita
  #2  
Antiguo 02-02-2018
Avatar de roman
roman roman is offline
Moderador
 
Registrado: may 2003
Ubicación: Ciudad de México
Posts: 20.269
Poder: 10
roman Es un diamante en brutoroman Es un diamante en brutoroman Es un diamante en bruto
Alguna vez discutimos esto por aquí. Yo uso (o usaba) la tercera opción desde que la vi mencionada -no recuerdo si por Marco Cantú o por Peter Below. Me parece la más limpia y simétrica. La versión anidada es horrible ya que habría que agregar una anidación por cada objeto.

No obstante, hay quienes objetan la tercera opción (me acuerdo de Al González) ya que el destructor de A2 podría causar una excepción y dejar A1 sin liberar. Pero, como decía el mismo Cantú o Below, un destructor no debe nunca, pero NUNCA, causar una excepción. Y si la causa significa un mal diseño que hay que corregir antes que preocuparse por A1.

LinneComment Saludos
Responder Con Cita
  #3  
Antiguo 02-02-2018
Avatar de roman
roman roman is offline
Moderador
 
Registrado: may 2003
Ubicación: Ciudad de México
Posts: 20.269
Poder: 10
roman Es un diamante en brutoroman Es un diamante en brutoroman Es un diamante en bruto
Ya encontré la discusión

http://clubdelphi.com/foros/showthread.php?t=59241

LineComment Saludos
Responder Con Cita
  #4  
Antiguo 06-02-2018
Avatar de Al González
[Al González] Al González is offline
In .pas since 1991
 
Registrado: may 2003
Posts: 5.604
Poder: 29
Al González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en bruto
Eso de buscarse a uno mismo en Google tiene sus sorpresas, je-je-je.

¡Un gran saludo, Román!
Responder Con Cita
  #5  
Antiguo 12-02-2018
fremen fremen is offline
Miembro
 
Registrado: sep 2010
Posts: 20
Poder: 0
fremen Va por buen camino
Hola... yo siempre uso el metodo 3, pero asegurando mas el finally

Código:
var
  A1, A2: TTest;
begin
  A2 := nil;
  A1 := nil;  
  try
    A1 := TTest.Create;
    A2 := TTest.Create;
    //Use A1 and A2
  finally
    if Assigned(A2) then FreeAndNil(A2);
    if Assigned(A1) then FreeAndNil(A1);
  end;
end
Responder Con Cita
  #6  
Antiguo 12-02-2018
Avatar de Ñuño Martínez
Ñuño Martínez Ñuño Martínez is offline
Moderador
 
Registrado: jul 2006
Ubicación: Ciudad Catedral, Españistán
Posts: 6.000
Poder: 25
Ñuño Martínez Tiene un aura espectacularÑuño Martínez Tiene un aura espectacular
Tu solución, fremen, es redundante (que no mala). Lo digo porque ya el método "Free" comprueba si el objeto existe (hay un "IF Self <> Nil", si no recuerdo mal), al igual que en FreeAndNil. Eso sí, el uso de FreeAndNil me gusta, y yo lo uso bastante, aunque no suelo poner el "IF Assigned" delante.
__________________
Proyectos actuales --> Allegro 5 Pascal ¡y Delphi!|MinGRo Game Engine
Responder Con Cita
  #7  
Antiguo 12-02-2018
Avatar de AgustinOrtu
[AgustinOrtu] AgustinOrtu is offline
Miembro Premium
NULL
 
Registrado: ago 2013
Ubicación: Argentina
Posts: 1.858
Poder: 15
AgustinOrtu Es un diamante en brutoAgustinOrtu Es un diamante en brutoAgustinOrtu Es un diamante en brutoAgustinOrtu Es un diamante en bruto
Yo creo que lo mas pragmatico es tener constructores y destructores simples (por lo general yo no redefino destructores). Si se tienen constructores simples que lo unico que hacen es asignar los valores que reciben, pero no crean objetos, entonces la forma #3 es totalmente segura

Con esto me refiero a que, en lugar de que un objeto cree un descendiente de TStream (lo cual puede tener complicaciones), mejor que lo reciba ya correctamente inicializado; de este modo, el no tendra que tambien ser responsable de saber como crear el stream, que hacer si no se puede crear, ni responsable de destruirlo
Responder Con Cita
  #8  
Antiguo 12-02-2018
fremen fremen is offline
Miembro
 
Registrado: sep 2010
Posts: 20
Poder: 0
fremen Va por buen camino
Cita:
Empezado por Ñuño Martínez Ver Mensaje
Tu solución, fremen, es redundante (que no mala). Lo digo porque ya el método "Free" comprueba si el objeto existe (hay un "IF Self <> Nil", si no recuerdo mal), al igual que en FreeAndNil. Eso sí, el uso de FreeAndNil me gusta, y yo lo uso bastante, aunque no suelo poner el "IF Assigned" delante.
Es redundante según la casuística. Pero con esta estructura, todos los casos los tengo controlados. Te expongo mi caso "raro"

El porque del FreeAndNil.
Código:
var
  A1, A2: TTest;
begin
  A2 := nil;
  A1 := nil;  
  try
    A1 := TTest.Create;
    A2 := TTest.Create;
    //Use A1 and A2
  finally
    if Assigned(A2) then FreeAndNil(A2);
    A1.Free;
  end;
  // Aquí A2 siempre es Nil. Así, si tengo un descuido tal que... 
  A2.Propiedad_X := 1;   // Genera excepción siempre y en el periodo de pruebas lo detecto a la primera  
 
  // Aquí A1 tiene un valor indeterminado. 
  A1.Propiedad_X := 1;   // Según el estado de la memoria que apunte A1, unas veces generara excepción y otras no. Resultado: Errores aleatorios
end;
El Assigned, es simplemente porque FreeAndNil no esta protegido ante un parámetro a Nil. Este es el código en un XE7
Código:
procedure FreeAndNil(var Obj);
{$IF not Defined(AUTOREFCOUNT)} 
var
  Temp: TObject;
begin
  Temp := TObject(Obj);
  Pointer(Obj) := nil;                  => Error
  Temp.Free;
end;
{$ELSE}
begin
  TObject(Obj) := nil;                => Error
end;
{$ENDIF}
Larga vida a Delphi ejejeje
Responder Con Cita
  #9  
Antiguo 12-02-2018
fremen fremen is offline
Miembro
 
Registrado: sep 2010
Posts: 20
Poder: 0
fremen Va por buen camino
Cita:
Empezado por AgustinOrtu Ver Mensaje
Yo creo que lo mas pragmatico es tener constructores y destructores simples (por lo general yo no redefino destructores). Si se tienen constructores simples que lo unico que hacen es asignar los valores que reciben, pero no crean objetos, entonces la forma #3 es totalmente segura

Con esto me refiero a que, en lugar de que un objeto cree un descendiente de TStream (lo cual puede tener complicaciones), mejor que lo reciba ya correctamente inicializado; de este modo, el no tendra que tambien ser responsable de saber como crear el stream, que hacer si no se puede crear, ni responsable de destruirlo
Creo que no te he entendido bien... lo que sugieres es que el encapsulamiento de objetos, no es una buena idea???
Responder Con Cita
  #10  
Antiguo 13-02-2018
Avatar de AgustinOrtu
[AgustinOrtu] AgustinOrtu is offline
Miembro Premium
NULL
 
Registrado: ago 2013
Ubicación: Argentina
Posts: 1.858
Poder: 15
AgustinOrtu Es un diamante en brutoAgustinOrtu Es un diamante en brutoAgustinOrtu Es un diamante en brutoAgustinOrtu Es un diamante en bruto
Cita:
Empezado por fremen Ver Mensaje
Creo que no te he entendido bien... lo que sugieres es que el encapsulamiento de objetos, no es una buena idea???
No, justamente es lo contrario
Responder Con Cita
  #11  
Antiguo 13-02-2018
fremen fremen is offline
Miembro
 
Registrado: sep 2010
Posts: 20
Poder: 0
fremen Va por buen camino
Cita:
Empezado por AgustinOrtu Ver Mensaje
No, justamente es lo contrario
Ok. Ya entendí.

Aunque veo sus ventajas a este patrón, a mi personalmente no me convence. Pero para gustos, colores. Y otra cosa no, pero colores en programación tenemos cientos...
Responder Con Cita
  #12  
Antiguo 13-02-2018
Avatar de roman
roman roman is offline
Moderador
 
Registrado: may 2003
Ubicación: Ciudad de México
Posts: 20.269
Poder: 10
roman Es un diamante en brutoroman Es un diamante en brutoroman Es un diamante en bruto
Cita:
Empezado por fremen Ver Mensaje
Te expongo mi caso "raro"
Este caso raro es más bien un caso erróneo: estás haciendo referencia a tus objetos A1 y A2 fuera del bloque try-except que está justamente para protegerlos. Además, la sección finally se usa para liberar los objetos por lo que es un error querer usarlos fuera de esa cláusula.

LineComment Saludos
Responder Con Cita
  #13  
Antiguo 13-02-2018
fremen fremen is offline
Miembro
 
Registrado: sep 2010
Posts: 20
Poder: 0
fremen Va por buen camino
Cita:
Empezado por roman Ver Mensaje
Este caso raro es más bien un caso erróneo: estás haciendo referencia a tus objetos A1 y A2 fuera del bloque try-except que está justamente para protegerlos. Además, la sección finally se usa para liberar los objetos por lo que es un error querer usarlos fuera de esa cláusula.

LineComment Saludos
Esta claro que es un caso erroneo... pero que tire la primera piedra el que no ha tenido casos erroneos... puede que yo tenga demasiados y por eso hago este tipo de bloques try-finally, para cazar los errores antes de llevarlos a producción.
Responder Con Cita
  #14  
Antiguo 14-02-2018
Avatar de Casimiro Notevi
Casimiro Notevi Casimiro Notevi is online now
Moderador
 
Registrado: sep 2004
Ubicación: En algún lugar.
Posts: 32.021
Poder: 10
Casimiro Notevi Tiene un aura espectacularCasimiro Notevi Tiene un aura espectacular
Cita:
Empezado por fremen Ver Mensaje
... por eso hago este tipo de bloques try-finally, para cazar los errores antes de llevarlos a producción.
Es que no es la forma de hacerlo. No necesitas "cazar un error" que tú mismo estás provocando. Simplemente no provoques ese error.
Responder Con Cita
Respuesta


Herramientas Buscar en Tema
Buscar en Tema:

Búsqueda Avanzada
Desplegado

Normas de Publicación
no Puedes crear nuevos temas
no Puedes responder a temas
no Puedes adjuntar archivos
no Puedes editar tus mensajes

El código vB está habilitado
Las caritas están habilitado
Código [IMG] está habilitado
Código HTML está deshabilitado
Saltar a Foro

Temas Similares
Tema Autor Foro Respuestas Último mensaje
Recursos en Español para delphi 6 GerTorresM Varios 2 04-01-2010 16:11:16
Ayuda para completar código:Traducción de Delphi a Builder Pernorak C++ Builder 3 30-05-2007 13:45:16
Traduccion de recursos (VCL) arm Varios 0 22-09-2004 14:11:09
Recursos en castellano para Delphi 7 xerkan Varios 1 05-03-2004 10:27:42


La franja horaria es GMT +2. Ahora son las 23:58:44.


Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi
Copyright 1996-2007 Club Delphi