Anti IF Patterns

Design Patterns
2020-11-07

In my last post I described the evils of the IF statement and how to refactor with the Ternary operator to remove them. In this post I will cover a few more patterns that are useful to reduce the number of IF and Switch statements.

Polymorphism & OO

Object Orientated Languages exist to get rid of IF statements. This is like how structural programing languages (languages with functions) served to eliminate GOTO statements.

Before polymorphism and abstractions (interfaces) code would have to keep track of the different types of algorithms and use IF or Switch statements to call the functions.

if (type == "cat")
{
    Meow(animal);
}
else if (type == "dog")
{
    Bark(animal);
}

But with an OO language this IF statement becomes:

animal.Speak();

Technically, there is still an IF statement somewhere buried deep in this implementation. However, it is in the implementation of the OO language. Any error in your logic related to invoking the Speak method will be caught by the type system and reported at compile time.

With OO languages came OO design patterns. Some of these patterns use polymorphism and abstractions in specialized ways remove IF statements. Two that are especially good at this are: Template Method and Strategy pattern.

Enum Maps

A typical way that switch statements get into programs is to support enumerated types. For example, let’s say we had the following definition:

public enum CowboyType
{
    Good,
    Bad,
    Ugly
}

Now we want to add a function that will report back the Hat Color related to the Cowboy Type. Typically this is done with a switch statement.

public static Color GetHatColor(CowboyType type)
{
    switch (type)
    {
        case CowboyType.Good:
            return Color.White;
        case CowboyType.Bad:
            return Color.Black;
        case CowboyType.Ugly:
            return Color.Brown;
        default:
            throw new NotSupportedException();
    }
}

One way to avoid a switch statement is to create a simple dictionary to map the values.

public static readonly IDictionary<CowboyType, Color> HatColorMap = 
    new Dictionary<CowboyType, Color>
    {
        {CowboyType.Good, Color.White},
        {CowboyType.Bad, Color.Black},
        {CowboyType.Ugly, Color.Brown}
    };

public static Color GetHatColor(CowboyType type)
{
    return HatColorMap[type];
}

This is an overly simplistic example. It may not be immediately obviously what the advantage of the Map is over the Switch statement. The advantage is the same as you get from using a ternary operator instead of an IF statement, as I explained in my previous post.

The curly braces below an IF or the Case statements of the Switch can take the code off in completely different directions. How with the Map, the values of the Dictionary constrain the code so that all paths must return a Color. This makes the code more expressive.

There are a few alternative ways to implement this. Instead of defining a Dictionary for this map, these related properties may be defined using custom attribute classes that are used on the enum members.

In addition to mapping to simple types (like Color, string, or integer), the maps can resolve to program logic. The value of the Dictionary may be an Action or Function delegate.

The readability of the code can be improved further by making the GetHatColor method an extension method on CowboyType.

If using the Strongly Typed Enum pattern this logic can be properly encapsulated into an Enum class. Read more on this pattern here or here.

Null Object

When Tony Hoare added Nulls in OO languages he later referred to this as his “billion dollar mistake”. It isn’t that the Null itself is costly but is the requirement to add IF statements to the code to guard against Nulls that is the problem.

The Null Object design pattern is a potential solution to this. Instead of returning a null, return a dummy object that does nothing. It could be a special dummy implementation of an interface. In simple cases this can be as easy as returning an empty array instead of a null IEnumerable or a zero-length string instead of a null string.

Also, the null propagation operator (?.) and null coalescence operator (??) in C# can help avoid IF statements.

Another anti-pattern that is somewhat related to nulls, is using return codes for functions. Like nulls, this forces the caller to surround every function call in IF statements to verify the state of the program. Favour Exceptions over error codes. When using this strategy, the code can be written expressively and describe what will happen 99%+ of the time. All the little picky details of what might go wrong and how to handle that can be moved to separate function dedicated just to those conditions. This yields code with good separation of concerns.

More, Smaller Function

We saw a bit of this in the last post on refactoring where in version 3c we eliminated an IF statement by introducing an overload to the FindEmployee function that could take a Nullable. Breaking large functions into smaller ones often has the side effect that the IF statements tend to just disappear.

When you write large functions with many nested IF statements, those functions tend to have multiple responsibilities. Each path through the maze of IF statements is a different, unique responsibility that needs to be reasoned about and supported. Small functions tend to have only a single responsibility and tend to have no IF statements.

Consider placing the code that controls the flow of the program and code that does the work in separate functions. Keep IF statements and the curly braces below them in separate functions. I would recommend against a coding standard that requires curly braces below all IF statements. This will encourage the code in the branch to be limited to only a single line of code, like the ternary operator does. Adding curly braces so that multiple lines of code can be added to the branch in the same function would be a code smell.

These are just a few techniques that can be used to reduce the number of IF and Switch statements. Whenever you are tempted to reach for that IF statement, please take some time to consider the possible alternatives. There usually are alternatives, and they usually yield more expressive code that is easier to maintain. Remember, every time you use an IF statement a kitten dies.

Kitten

I have joined Anti-IF Campaign