The Flat Success Path: Clean Python Code Made Simple

Filipe Ximenes
December 18, 2024
For Pythonistas this could also be called "why flat is better than nested"

Clear, maintainable software follows a simple principle: every piece should have one clear path to success. This concept is fundamental yet often overlooked in software development.

The single success path principle encompasses two key ideas. First, each function should serve exactly one purpose. Think of it as giving directions - if you can't explain where you're going in a simple sentence, you're probably trying to visit too many places at once. When you find yourself struggling to name a function concisely, it's often a sign that it's doing too much.

Second, the happy path - the sequence of steps when everything works as intended - should be immediately obvious when reading the code. This means keeping your main logic path clear and uncluttered, pushing error handling and edge cases to the sides. The primary flow of your function should read like a story, with each line naturally leading to the next.

Let's talk about keeping your code flat:

A nested command is a block of code that is under a clause that visually moves the start of the code away from the left margin of the text editor (given that you are using good practices of indentation). if/else and try/catch are examples of it. Flat code is the opposite of nested code, it's code that is near to the left margin of the editor.

A success path varies in meaning across different parts of code. It might be a function's default behavior, the most probable outcome, or simply the path that directly achieves the code's main purpose. Consider a divide(x,y) function that takes user input. While its purpose is performing x / y, it must verify that y isn't 0 before calculating. Input validation is essential for the function to work correctly, but it isn't the primary purpose of divide. This highlights an important relationship: a truly flat success path is only possible when a function has a single purpose.

To illustrate this concept, let's examine a function that handles money transfers between users, returning true for successful transfers and false otherwise.

1def transfer_money(from_user, to_user, amount):
2    if amount > 0:
3        if from_user.balance >= amount:
4            from_user.balance = from_user.balance - amount
5            to_user.balance = to_user.balance + amount
6            notify_success(from_user, amount)
7            return True
8        else:
9            notify_insuficient_funds(from_user)
10            return False
11    else:
12        return False

This code is difficult to understand at a glance. Two main issues make it challenging to follow:

  1. The nested if/else statements obscure the main purpose - it's unclear which path represents the core functionality.
  2. The true/false return values scattered throughout make it impossible to determine success or failure conditions without reading and understanding the entire function.

Let's refactor this to make it clearer:

1def transfer_money(from_user, to_user, amount):
2    if amount <= 0:
3        return False
4
5    if from_user.balance < amount:
6        notify_insuficient_funds(from_user)
7        return False
8
9    from_user.balance = from_user.balance - amount
10    to_user.balance = to_user.balance + amount
11    notify_success(from_user, amount)
12    return True

Notice that despite looking much more apparent, the refactored code has the same cyclomatic complexity as the first. Also, measuring cyclomatic complexity is a precise mathematical concept that may indicate your code needs refactoring. Flatness, however, relates to its semantics and is more of a subjective evaluation.

The main change between the first and second pieces of code we showed is that if you read it ignoring anything that is nested, you will end up with the main flow of the program:

1def transfer_money(from_user, to_user, amount):
2    from_user.balance = from_user.balance - amount
3    to_user.balance = to_user.balance + amount
4    notify_success(from_user, amount)
5    return True

This represents the success path - the core functionality of our transfer function. When reading unfamiliar code, developers naturally focus first on the non-nested parts, which should represent the main flow. The nested sections typically handle special cases and error conditions.

Using guard clauses instead of if/else statements is one of the most effective ways to highlight the success path. When you find that you can't achieve this kind of flatness, it often indicates your code is handling too many responsibilities and should be split into separate functions.