Saturday, March 31, 2012

Handling Return Values

Recently, I was discussing Defensive Programming with a relative newcomer to the industry. I mentioned that the concept was not dependent on languages and more logical in nature. In other words, though a language does not have specific language facilities for Defensive Programming.

To give an example, I used the following:

Consider a function is_data_found() which returns one of the following values:

#define SUCCESS 0
#define FAILURE 1
#define NO_DATA_FOUND 2

int fun1()
{
    //search for data in DB
    //If data not found return NO_DATA_FOUND
    //else if operation failed, then return FAILURE
    //Return SUCCESS
}

Now, one of the common (wrong) ways for a caller fun2() to handle the return values from the call to fun1() is:

void fun2()
{
    //Call fun1
    //if return value is not SUCCESS, continue process
    //else, throw exception
}

Based on code review feedback or testing, they might realize that NO_DATA_FOUND might be a valid scenario. In other words, fun2() might need to do create_if_not_found operation.

To accommodate this, they might change the caller to:

void fun3()
{
    //Call fun1
    //if return value is NO_DATA_FOUND, then create row
    //else if return value is SUCCESS, then continue process
    //else, throw exception
}

This would be fine in the current scenario. But what if a new return value is added to fun1(), say WARNING_POTENTIAL_STALE_DATA:

#define SUCCESS 0
#define FAILURE 1
#define NO_DATA_FOUND 2
#define WARNING_POTENTIAL_STALE_DATA 3

int fun1()
{
    //search for data in DB
    //If data not found return NO_DATA_FOUND
    //else if operation failed but previously fetched data available, then return WARNING_POTENTIAL_STALE_DATA
    //else if operation failed and no previously fetched data available, then return FAILURE
    //Return SUCCESS
}

Now, fun2() would not provide the complete functionality since it does not know about the new functionality. However, no one would notice this, since fun2() does not have the right code for handling such scenarios.

A better way to write the caller is:

void fun4()
{
    //Call fun1
    //if return value is NO_DATA_FOUND, then create row
    //else if return value is SUCCESS, then continue process
    //else if return value is FAILURE, then throw DATA_FETCH_FAILED_EXCEPTION
    //else, return UNKNOWN_RETURN_VALUE_EXCEPTION
}

Now, fun4() will throw an exception when WARNING_POTENTIAL_STALE_DATA is thrown.

Cheers!
Karthick S.