Balanceando Recursos

Todos administramos nuestros recursos cada vez que codificamos: memoria, operaciones, hilos, temporizadores, todo tipo de cosas con una disponibilidad limitada. La mayoría de las veces usar los recursos siguen un patrón predecible:

  • Asignar el recurso,
  • Usarlo
  • Liberarlo

Sin embargo muchos desarrolladores no tienen ningún plan coherente para hacer frente a la asignación y cancelación de recursos.

Tip: "Termina lo que empiezas"

Este tip es fácil de aplicar en la mayoría de las circunstancias. Simplemente significa que la rutina o el objeto que asigna un recurso debe ser responsable de eliminarlo. Vamos a ver cómo se aplica al mirar un ejemplo de mal código, una aplicación que abre un archivo, lee la información del cliente, actualiza un campo, y escribe el resultado.



void readCustomer(const char *fName, Customer *cRec) {
cFile = fopen(fName, "r+");
fread(cRec, sizeof(*cRec), 1, cFile);
}

void writeCustomer(Customer *cRec) {
rewind(cFile);
fwrite (cRec, sizeof(*cRec), 1, cFile);
fclose(cFile);
}

void updateCustomer(const char *fName, double newBalance) {
Customer cRec;
readCustomer(fName, &cRec);
cRec.balance = newBalance;
writeCustomer(&cRec);
}





A primera vista, el updateCustomer rutina se ve muy bien. Parece aplicar la lógica que requiere la lectura de un registro, la actualización del balance y escribir el registro. Sin embargo, esta limpieza se esconde una de los principales problemas. Las rutinas readCustomer y writeCustomer están fuertemente acoplados. Comparten la variable global cFile.readCustomer abre el archivo y
almacena el puntero de archivo en CFile y utiliza writeCustomer que almacena el puntero para cerrar el archivo cuando termine. Esta variable global ni siquiera aparece en la rutina updateCustomer.


¿Por qué esto está mal? Vamos a considerar que el programador de mantenimiento lamentablemente se le dice que las condiciones han cambiado, el proceso debe realizarse solo cuando el nuevo valor no es negativo. En updateCustomer el cambio sería algo así:



void updateCustomer(const char *fName, double newBalance) {
Customer cRec;
readCustomer(fName, &cRec);
if (newBalance >= 0.0) {
cRec.balance = newBalance;
writeCustomer(&cRec);
}
}






Todo parece muy bien durante la prueba. Sin embargo, cuando el código va a producción, se derrumba después de varias horas, porque hay archivos abiertos y el writeBalance no está recibiendo llamadas en algunas circunstancias, por lo tanto el archivo no se está cerrando.
Una mala solución para este problema sería el de tratar con el caso especial en el updateCustomer:



void updateCustomer(const char *fName, double newBalance) {
Customer cRec;
readCustomer(fName, &cRec);
if (newBalance >= 0.0) {
cRec.balance = newBalance;
writeCustomer(&cRec);
}
else
fclose(cFile);
}



Esto solucionará el problema en el archivo para que el archivo se cierre, pero aún está presente el acoplamiento en las rutinas por la variable global CFile. Esto es una mala práctica, y las cosas van a empezar a ir derrumbarse rápidamente si seguimos por este camino.
El tip nos dice que, idealmente, la rutina que asigna un recurso también debe liberarlo.
Aplicando el código de refactorización, tenemos:



void readCustomer(FILE *cFile, Customer *cRec) {
fread(cRec, sizeof(*cRec), 1, cFile);
}
void writeCustomer(FILE *cFile, Customer *cRec) {
rewind(cFile);
fwrite(cRec, sizeof(*cRec), 1, cFile);
}
void updateCustomer(const char *fName, double newBalance) {
FILE *cFile;
Customer cRec;
cFile = fopen(fName, "r+"); // >---
readCustomer(cFile, &cRec); // /
if (newBalance >= 0.0) { // /
cRec.balance = newBalance; // /
writeCustomer(cFile, &cRec); // /
} // /
fclose(cFile); // <--- }



Ahora toda la responsabilidad por el archivo está en la rutina updateCustomer. Ello abre el archivo y (terminar lo que comienza) lo cierra antes de salir.
La refactorización también elimina una variable horrible variable global XD

Fuente: the Pragmatic Programmer: From Journeyman to Master

Comentarios