Inverse Logic Pattern

August 20, 2021
Tags: Logic

When I first started programming I learned the basic set of logic controls and began doing what came naturally. As you can expect, inexperience can often lead to poor practices. Over time I began to realize that programming was much less about writing code and much more about composing legible, comprehensible webs of tasks. Most often experience and training will seem intuitive in retrospect but occasionally the best solution to a problem is the opposite of what you expect to do. In this case what I learned was that much of what I was taught about methods, loops and logic were backwards. In fact the best way to work was inverted. 

Thanks to a friend I also was recently informed that these are called 'Guard Statements'

Inversion of Logic

What do i mean by inverted? I mean that for every 'if' condition (ie: if(x > 0)) there's likely an inverse that's more elegant. 

Take for example this code block:

public string GetPanelClass(string panel, string classPrefix, int row){

    if(panel == "right"){

        if(row > 0){

            if(row % 2 == 0){

                return classPrefix + "-odd";

            }

            else {

                return classPrefix + "-even";

            }

        }

        return classPrefix + "first";

    }    

    return "";

}

So what's wrong with this code? Logically nothing. It works as expected. But legibly it's poor with three levels of nested conditions and this is a simple example. Now consider the same code with inverted logic:

 

public string GetPanelClass(string panel, string classPrefix, int row){

    if(panel != "right")

        return "";

 

    if(row < 1)

        return classPrefix + "first";

 

    if(row % 2 == 0)

        return classPrefix + "-odd";

 

    return classPrefix + "-even";

}

 

By inverting the first two conditions, I can immediately return a result and flatten the nested logic. At this point I can clearly identify the conditions required to get me to any single point in the code because it's much easier to follow vertically than horizontally. More complex examples with further nesting will result in deep trees of nested logic. Given sufficient nesting or dense enough conditional logic and those deep trees are nearly impossible to read or modify because you just can't determine what conditions are required to get you into a specific line. Accounting for all the possibilities would take a lot of effort and would certainly only get worse as you add more conditions.  

What's interesting is that when you commit to this type of thinking you'll inevitably hit a point where you notice some conditions contain the same or similar code. You may also notice a single case that's challenging to invert because the logic is so dense. The solution is to break that logic into it's own method and swap out all that logic for a single line call to that method. Slowly you'll start to see the methods get smaller and more readable. Each method will become more manageable and maintainable. For such a simple change in thinking it has had a dramatic improvement on my code. It even makes everything more testable. 

I don't write a lot about coding practices or patterns but I haven't really seen anyone else write this yet so I figured I'd throw it out there. Happy coding!