The Flat Success Path: Clean Python Code Made Simple
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
andtry/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:
- The nested
if/else
statements obscure the main purpose - it's unclear which path represents the core functionality. - 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.