Why You Should Avoid the Else Operator
Table of Contents
In the previous article, we talked about the switch/case operator. I think that is more or less clear. A less obvious operator is else. Perhaps in some cases it is justified, but more often than not, the best solution is to avoid using this operator.
The Problem #
There is no problem with the operator itself. The problem is in its usage: often we have a mass of nested conditions and branching, which degrades code readability and spawns errors when writing tests. Surely everyone has encountered code like this:
def check_violation(car):
if car.type != 'military' and speed > 120:
driver = get_driver(car)
if driver.is_officials():
skip_car()
else:
fine()
else:
skip_car(car)
In this example, there are only 2 levels of nesting, but there could be many more. And this is not the only problem: our method does too many things. It checks the car type, checks who the driver is, checks their license, etc. One can come to the understanding that multiple conditions always mean bloated methods that violate the Single Responsibility Principle. Furthermore, this approach spawns potential places where the code can be expanded, and it won’t necessarily be you; perhaps your colleague will add another else.
The Solution #
How can we avoid using else in most cases? Actually, it’s quite simple, and it certainly won’t take you much time. This technique is called Guard Clause or early return.
Let’s duplicate the logic of checking car stop conditions into separate methods:
def should_stop(car, speed):
driver = get_driver(car)
return speed > 120 and car.type != 'military' and not driver.is_officials()
def check_car(car):
if should_stop(car):
fine()
return
skip_car(car)
Of course, in the real world, we would move the traffic violation check into a separate abstraction, but in this case, I decided to omit this moment for simplicity.
I want to note that in the check_car function, we did not use else. Instead, we used return from the first condition. This approach rids us of one more potential branch and code nesting.
However, in some situations, this can lead to code duplication for cleanup. For example, if after the main logic completes we want to close a database connection, we would have to write the closing code before every return.
Note: If you have a need to duplicate resource closing code, it is worth looking towards context managers (the
withoperator in Python) ortry/finallyblocks, as well as decorators that will take this routine upon themselves.
I also recommend using inverse logic (Guard Clauses). For example, code like this:
def handle_response(response):
if response.status_code == 200:
process_data(response.data)
I would propose replacing it with this:
def handle_response(response):
if response.status_code != 200:
return
process_data(response.data)
In this case, we get rid of branching when processing the main logic and can write code linearly, without extra indentation.