Deeply nested *if…else *statements are difficult to reason about and tricky to debug. The logic becomes tangled and you’re unsure if you’re adequately checking all cases you have in mind. When your code surpasses the second or third *elif *statement it’s time to refactor. There’s enough code smell at this point to warrant rethinking your entire approach.

One method I’ve come to prefer for control flow with dense business logic is routing statements in python dictionaries. You can build your dictionary like a switch, and you can quickly route to the logic you want to use.

## Our toy problem and it’s resolution

Let’s say we have the following function and apologies for the silly example ¯\_(ツ)_/¯

def perform_mathematical_operation(operation, a, b): """ Args: operation (string) a (int or float) b (int or float) Returns: (float) """ val = 0.00 if operation == "add": val = a + b elif operation == "subtract": val = a - b elif operation == "multiply": val = a * b else: val = a / b return val

#### What we have here is a set of cryptically dense if, elif, else statements.

Besides being a pile of spaghetti this function also makes decisions for us without our consent. Specifically, in this function all other operations besides addition, subtraction, and multiplication are **division**. This choice to use division is happening implicitly because the operation magic string could be anything. Adding other checks for division (that our denominator isn’t zero, for example) will require more dense logic.

#### Making the case for switching with dictionaries.

Instead of one singular dense function, we can break out the components into functions of their own.

def add_two_numbers(a, b): return a + b def subtract_two_numbers(a, b): return a - b def multiply_two_numbers(a, b): return a * b def divide_two_numbers(a, b): return a / b

#### Breaking out these individual functions gives us the added bonus of being able to easily cover each one in unit tests!

We will also need the switching logic, which I’ve placed inside this *operation_on_numbers *function.

def operation_on_numbers(operation, a, b): """ Args: operation (string) a (int or float) b (int or float) Returns: (int or float) """ switch = {'add': add_two_numbers, 'subtract': subtract_two_numbers, 'multiply': multiply_two_numbers, 'divide': divide_two_numbers } return switch[operation](a, b)

The function with the dictionary control flow will throw a KeyError if a new operation is provided that does not exist in the switch dictionary. At least we will *know *the cases where items are being divided now…because they will break this function.

The switch works by setting the values of the dictionary as our functions. The function itself is called on the return. Here’s how it looks in action:

## Leave a Reply

You must be logged in to post a comment.