Friday, May 19, 2006

Well Paired Operations

All "paired" operations should take place at the same level.

What's a paired operation? An operation is paired with another operation if invoking one operation requires the other operation to be invoked. Examples:
  • malloc / free
  • new / delete
  • CreateDC / DeleteDC
There are many others. The most problematic are the paired operations we ourselves create:
  • MyStartRoutine / MyStopRoutine
What does it mean for paired operations to take place "at the same level"? Two operations are said to take place in the same context level if:
  • They occur in the same routine (i.e. in the immediate execution scope, not in a child routine)
  • They occur in two different routines who have the same degree of separation from a common ancestor
  • Others?
Here is an example of a set of well paired operations:
void foo ()
{
void * someData = malloc (10);

// some code //

free (someData);
}
The malloc and free operations are well paired because they occur in the same immediate execution context.

Let's look at an example of a set of poorly paired operations. Aaside from the fact that the following code breaks about a billion other best coding practices it is a surprisingly common anti-pattern!
void * formatSomeMemory()
{
void * tmp = malloc (10);

// do some stuff

return tmp;
}

void foo ()
{
void * someData = formatSomeMemory();

// some code //

free (someData);
}

In this context although the malloc and free operations share the same decendent routine - foo - they have differing degrees of separation and are therefore not well paired.



So, who's fault is this? it's the author of formatSomeMemory()'s fault.

Fundamentally, the problem of poorly paired operations is the result of poorly formed APIs. In this case, the author of the API that included the formatSomeMemory() routine did not provide an appropriate routine for freeing the data that formatSomeMemory allocated. The author of formatSomeMemory() is forcing the author of foo() to write poorly paired code!

In reality, the author of formatSomeMemory() and foo() are probably the same person! The reason that this person wrote a poorly formed API is that they inadvertantly broke an even more fundamental well formed coding principle: The user of an API should not be required to have intimate understandings of the internal framework of the API in order to properly use it.

In many cases, since the user is also the developer, this rule gets broken unconsiously. In our case, since the author of formatSomeMemory() knew that it malloc'ed some memory, he innocently added the free to foo and went on his merry way, not knowing he had just created a debugging pitfall for future developers.

So, what might be some alternatives to the malformed API.

Option 1 - Make user responsible for well pairing operations:
void * formatSomeMemory(void * mem, int size)
{
// do some stuff to the memory
// (perhaps even reallocing it)

return tmp;
}

void foo ()
{
void * someData = malloc(10);

someData = formatSomeMemory (someData, 10);

// do stuff

free (someData);
}


Option 2 - Handle well pairing behind the scenes:
void * formatSomeMemory()
{
void * tmp = malloc(10);

// do some stuff to the memory

return tmp;
}

void * freeFormattedMemory (void * tmp)
{
free (tmp);
}

void foo ()
{
void * someData = formatSomeMemory ();

// do stuff

freeFormattedMemory (someData);
}


In both these examples the paired operations malloc amd free free are now well paired because in both cases they share the same degree of separation from the root routine foo.

I personally prefer Option 1 to Option 2 simply because Option 2 requires good documentation to work properly. I hate documentation, don't you? :)

0 Comments:

Post a Comment

<< Home