Ver Mensaje Individual
  #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
Reputación: 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