How to Balance Resources

How to Balance Resources

"I brought you into this world, " my father would say," and I can take you out. It don't make no difference to me. I'll just make another one like you."

Bill Cosby, Fatherhood

We all manage resources whenever we code: memory, transactions, threads, flies, timersall kinds of things with limited availability. Most of the time, resource usage follows a predictable pattern: you allocate the resource, use it, and then deallocate it.

However, many developers have no consistent plan for dealing with resource allocation and deallocation. So let us suggest a simple tip:

Tip 35

Finish What You Start



This tip is easy to apply in most circumstances. It simply means that the routine or object that allocates a resource should be responsible for deallocating it. Let's see how it applies by looking at an example of some bad codean application that opens a file, reads customer information from it, updates a field, and writes the result back. We've eliminated error handling to make the example clearer.

  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);     } 

At first sight, the routine updateCustomer looks pretty good. It seems to implement the logic we requirereading a record, updating the balance, and writing the record back out. However, this tidiness hides a major problem. The routines readCustomer and writeCustomer are tightly coupled [3] they share the global variable cFile.readCustomer opens the file and stores the file pointer in cFile, and writeCustomer uses that stored pointer to close the file when it finishes. This global variable doesn't even appear in the updateCustomer routine.

[3] For a discussion of the dangers of coupled code, see Decoupling and the Law of Demeter.

Why is this bad? Let's consider the unfortunate maintenance programmer who is told that the specification has changedthe balance should be updated only if the new value is not negative. She goes into the source and changes updateCustomer:

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

All seems fine during testing. However, when the code goes into production, it collapses after several hours, complaining of too many open files. Because writeBalance is not getting called in some circumstances, the file is not getting closed.

A very bad solution to this problem would be to deal with the special case in 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);     } 

This will fix the problemthe file will now get closed regardless of the new balancebut the fix now means that three routines are coupled through the global cFile. We're falling into a trap, and things are going to start going downhill rapidly if we continue on this course.

The finish what you start tip tells us that, ideally , the routine that allocates a resource should also free it. We can apply it here by refactoring the code slightly:

  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);                     // <---     } 

Now all the responsibility for the file is in the updateCustomer routine. It opens the file and (finishing what it starts) closes it before exiting. The routine balances the use of the file: the open and close are in the same place, and it is apparent that for every open there will be a corresponding close. The refactoring also removes an ugly global variable.

Nest Allocations

The basic pattern for resource allocation can be extended for routines that need more than one resource at a time. There are just two more suggestions:

  1. Deallocate resources in the opposite order to that in which you allocate them. That way you won't orphan resources if one resource contains references to another.

  2. When allocating the same set of resources in different places in your code, always allocate them in the same order. This will reduce the possibility of deadlock. (If process A claims resource1 and is about to claim resource2, while process B has claimed resource2 and is trying to get resource1, the two processes will wait forever.)

It doesn't matter what kind of resources we're usingtransactions, memory, files, threads, windows the basic pattern applies: whoever allocates a resource should be responsible for deallocating it. However, in some languages we can develop the concept further.



The Pragmatic Programmer(c) From Journeyman to Master
The Pragmatic Programmer: From Journeyman to Master
ISBN: 020161622X
EAN: 2147483647
Year: 2005
Pages: 81

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net