Leave defensive programming behind

The general rule of designing an interface (in general sense) is: make it easy to do what is right, and hard what is wrong. In other words, if you created an API for printing an image, then it should be trivial for the programmer to print a .png file that is stored on his disk. On the other hand, you should protect him from doing something stupid, like printing the image -1 times.

In most cases it pays off to make the code filled with various validation snippets. throw on invalid arguments (in public interface), asserts in bowels of your logic, etc. Having played with Python a bit I must admit that allowing potentially senseless input is sometimes better than throwing exceptions all around.

Lets assume we are creating a trivial code in C++ to repeat the given string multiple times. Here is our first attempt.

std::string multiply(const std::string& str,
                     const unsigned int count)
{
    if (count < 2)
    {
        throw std::invalid_argument(
            "Why to multiply when you don't have to?"
        );
    }

    auto result = std::string();
    result.reserve(str.size() * count);
    for (size_t i = 0; i < count; ++i) result += str;
    return result;
}

Everything seems all right, but the question arises: how much should we prohibit the user from entering ‘senseless’ count? Most of the time, I would say, we should stick with assert(), throw and log warnings/errors. This way we we protect ourselves from allowing silent errors.

That is all and good, but think about the client of your API and how he will have to deal with the restrictions of your design.

int count;
// Do some complex stuff and operations with count...

std::string lineOfDots;
if (count > 1)
{
    lineOfDots = multiply(".", count);
}
else if (count == 1)
{
    lineOfDots = ".";
}

What you have basically done is you forced the user to check the conditions in every piece of his code. Why not to move this logic to multiply instead?

std::string multiply(const std::string& str,
                     const int count)
{
    const unsigned int correctCount = count < 1 ? 0 : count;

    auto result = std::string();
    result.reserve(str.size() * correctCount);
    for (size_t i = 0; i < correctCount; ++i) result += str;
    return result;
}

The multiply logic didn’t get terribly complex and thanks to acceptance of potentially invalid input, the client code becomes much cleaner and simpler.

Advertisements

Tags: , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: