Monday 12 November 2012

The arrowhead anti-pattern and how to get rid of it

We've all seen code like this before:

public void checkForCats()
{
    if (mStillCounting)
    {
        for (String animal : mAnimals)
        {
            if (animal != null)
            {
                if (animal.startsWith("cat"))
                {
                    mCats++;
                }
            }
        }
    }
}

The above code is a series of nested ifs and whiles. The set of nested statements creates what looks like an arrow head pointing to mCats++. Hence the name of this: the arrowhead anti-pattern.

This code is impossible to read, how do I get rid of it?

Trick #1: Guard Clauses

Let's take a look at that first if statement.

if (mStillCounting)

Because this if contains the rest of the method, we can reverse the condition of it and provide an early return instead.

This is what the new code looks like:

public void checkForCats()
{
    if (!mStillCounting)
    {
        return;
    }
    
    for (String animal : mAnimals)
    {
        if (animal != null)
        {
            if (animal.startsWith("cat"))
            {
                mCats++;
            }
        }
    }
}

It's looking less like an arrow head now, though there's still some more we can do.

Trick #2: Extract Methods


See the code inside the for loop? Lets extract that code into another method.

public void checkForCats()
{
    if (!mStillCounting)
    {
        return;
    }

    for (String animal : mAnimals)
    {
        checkForCat(animal);
    }
}

private void checkForCat(String animal)
{
    if (animal != null)
    {
        if (animal.startsWith("cat"))
        {
            mCats++;
        }
    }
}

Unsurprisingly this refactoring step is known as Extract Method. You're breaking down a larger method into multiple methods that are easier to understand.

To make our new method easier to understand, we'll do another Guard Clause on that first if in checkForCat.

private void checkForCat(String animal)
{
    if (animal == null)
    {
        return;
    }
    
    if (animal.startsWith("cat"))
    {
        mCats++;
    }
}

By reversing the condition of that if from (animal != null) to (animal == null), we now have a positive conditional. This improves the readability of code a lot.

In general you should try to write your conditionals as a positive check.

These two tricks should be enough to get you started. :)

No comments:

Post a Comment