A Tale of Refactoring: Dealing with Multiple Conditional Statements the Python Way

Photo by Shang Liu on Unsplash

A Tale of Refactoring: Dealing with Multiple Conditional Statements the Python Way

From extracting methods to the "Visitor" pattern...

ยท

6 min read

Are you tired of dealing with messy and hard-to-maintain code filled with multiple conditional statements in Python?

Learn how to improve your code's readability, maintainability, and testability by exploring different techniques for refactoring code with multiple conditional statements.

From extracting functionalities into methods to using object-oriented programming and the Visitor pattern, this tutorial will guide you step by step through the process.

Say goodbye to the headache of long chains of conditional statements and hello to a more efficient and effective coding experience.

Let's go!

Initial Codebase

Let's say we are writing python code to generate an HTML page. There are different types of components on the page. Those types are common, href, span, and div.

For some reason, the code we use for generating every type of element is a bit different, so we come up with a solution like the following:

# main.py

if component_type == "common":
    # Generate common component
    ...
    ...
    ...
elif component_type == "href":
    # Generate href component
    ...
    ...
    ...
elif component_type == "span":
    # Generate span component
    ...
    ...
    ...
elif component_type == "div":
    # Generate div component
    ...
    ...
    ...

Note: We have omitted the implementation details for generating the specific components in the code because it is not relevant to the purposes of this tutorial.

What's wrong with this approach?

In the first place, since we have multiple cases to handle and the implementation for each can keep growing, we would end up with a huge codebase that will be harder to read and maintain later.

Let's start refactoring!

Step 1: Extracting Methods

A reasonable way to improve this code is by extracting some of it into different methods, making the solution more modular.

We start by creating a different method to handle every specific component. This will allow us to locate the code that handles specific component types more easily. A possible implementation could be the following:

# html_generator.py

def generate_common_component():
    # Generate common component
    ...
    ...
    ...

def generate_href_component():
    # Generate href component
    ...
    ...
    ...

def generate_span_component():
    # Generate span component
    ...
    ...
    ...

def generate_div_component():
    # Generate div component
    ...
    ...
    ...

This will allow us to change our main code to something like the following:

# main.py

from html_generator import (
    generate_common_component,
    generate_href_component,
    generate_span_component,
    generate_div_component
)

if component_type == "common":
    generate_common_component()
elif component_type == "href":
    generate_href_component()
elif component_type == "span":
    generate_span_component()
elif component_type == "div":
    generate_div_component()

The benefits of this refactoring are clear. The code automatically becomes more:

  • Readable

  • Maintainable

  • Testable

But we still have some issues to solve. The more obvious of them is that even if we extracted the logical parts that deal with specific types of components, we are still in danger of having our main code grow, making it harder to read, test and maintain again.

This will happen if we want to generate new types of components in our code. With the current solution, we would have to keep adding additional elif statements, resulting in the same problems we were trying to solve in the first place.

Still, it is a good starting point. Let's keep improving it!

Step 2: Dictionary Dispatch

As we have seen, extracting most of our code into methods doesn't solve the issues that the long chain of if-elif causes. The question is, how to get rid of them in a way that ensures our code is still correct and improves readability, maintainability, and testability as well?

The answer to that is the Dictionary Dispatch pattern, which will allow executing methods depending on the values of variables without using conditional statements.

We start by creating a dictionary whose keys are the possible values that our variable used in the conditional statements can have:

# main.py

from html_generator import (
    generate_common_component,
    generate_href_component,
    generate_span_component,
    generate_div_component
)

generator_functions = {
    "common": generate_common_component,
    "href": generate_href_component,
    "span": generate_span_component,
    "div": generate_div_component,
}

By doing this, we can replace our conditional statements with the following code:

# main.py

generator_functions[component_type]()

Which will still call a function to generate the component we want depending on the value of a variable. Only this time, we are sure this code won't grow any longer.

Now, if we want to add a new component to generate, we would only need to implement the logic for it in a different method and add the corresponding key-value pair to our dictionary.

Let's see an alternative if we want to make this solution more robust.

Step 3: The Visitor Pattern

As a last step of refactoring, we will see an alternative using object-oriented programming: The Visitor Pattern.

We will start by creating a class that handles the writing of HTML components. This class will have the methods for writing every specific component but also a more general method that decides which of these specific methods to invoke depending on the component type.

# html_generator.py

class HTMLGenerator:
    @classmethod
    def generate_component(cls, component_type: str):
        writer_method = getattr(
            self, f"_generate_{component_type}_component", "generate_common_component"
        )
        writer_method()

    def _generate_common_component():
        ...
        ...
        ...

    def _generate_href_component():
        ...
        ...
        ...

    def _generate_span_component():
        ...
        ...
        ...

    def _generate_div_component():
        ...
        ...
        ...

What the generate_component method does is try to match any method with the pattern _generate_<component_type>_component, and invoke it to generate the component type passed as an argument. In case there is no method matching the pattern, we take the _generate_common_component by default.

Now, our main code will look like this:

# main.py

from html_writer import HTMLGenerator

HTMLGenerator.generate_component(component_type)

Like with the Dictionary Dispatch approach, if we want to add a new component type we would only need to implement the corresponding method in the HTMLGenerator class.

Conclusions

In this tutorial, we have seen different techniques for refactoring code with multiple conditional statements.

We started by extracting functionalities into methods and applying the Dictionary Dispatch pattern. Lastly, we made our solution more robust by using object-oriented programming and the Visitor pattern.

All of these refactoring techniques allow for improved readability, maintainability, and testability of our code. Also, in our specific case, it allows the easy addition of new components.

This specific example might not be the exact case of use that you are dealing with, but if you see yourself struggling with a long chain of conditional statements try to apply the approaches explained here. It will be somehow similar to the problem you are facing.

This is the first article in a series about improving code step by step. If you would like me to take a look at specific examples you have, leave them in the comment section and I will try to create a tutorial like this one for the most interesting ones.

See you soon!


๐Ÿ‘‹ Hello, I'm Alberto, Software Developer at doWhile, Competitive Programmer, Teacher, and Fitness Enthusiast.

๐Ÿงก If you liked this article, consider sharing it.

๐Ÿ”— All links | Twitter | LinkedIn

Did you find this article valuable?

Support Alberto Gonzalez by becoming a sponsor. Any amount is appreciated!

ย