In programming, there are patterns everywhere: patterns of building code, patterns for organising components, even patterns for how to test things. A whole industry is looking for patterns to solve all their problems.
But amongst them all, there’s one pattern I whole-heartedly love, it’s the Early Exit. In this short and sweet post, I want to declare my undying love to the Early Exit, sharing my excitement.
Functions routinely need to check some edge case isn’t hit, usually with more edge cases than actual routine happy-path code lines.
The most obvious way to write this is to have if
for the case that is good,
and else
the cases that aren’t.
def change_blogpost(post, user, content_change):
if user.is_admin():
return change_content(post, content_change, who=user.id)
else:
raise NotAuthorizedError("User is not admin")
Now, once this specific “if” is sorted, the code for when everything goes
according to plan is inside an if
, so it is shifted “inwards” by one layer of
indentation.
Of course, with more things to check, this gets more elaborate, an issue called various names like if-else ladders, or conditional hell.
def change_blogpost(post, user, content_change):
if user.is_admin():
if post.published:
# The main point of this function is here:
return change_content(post, content_change, who=user.id)
else:
raise ContentNotPublishedError("Draft article cannot be changed")
else:
raise NotAuthorizedError("User is not admin")
The entire structure becomes unwieldy, as we hide the very core of the function in a scaffolding that isn’t easily scannable from afar.
Early exit is a practice that favours getting the wrong case out of the way early, exiting as fast as possible from the function, returning or erroring as needed, in order to progressively weed out the edge cases, and allow us to focus on the happy path.
Because the bad case has already returned, the further we are in the function, the more things we can assert about the current state of the world.
def change_blogpost(post, user, content_change):
if not user.is_admin():
raise NotAuthorizedError("User is not admin")
# We KNOW user is admin if we reach here
return change_content(post, content_change, who=user.id)
def change_blogpost(post, user, content_change):
if not user.is_admin():
raise NotAuthorizedError("User is not admin")
# We KNOW user is admin if we reach here
if not post.published:
raise ContentNotPublishedError("Draft article cannot be changed")
# User is admin + post published already:
return change_content(post, content_change, who=user.id)
Note that the resulting code avoids multiple levels of indentation, dealing with one thing at a time, ending up with a shorter solution that reads more like a sentence.
This allows developers to focus on the specific case being dealt with on the specific line being worked on, instead of what combination of if/else ladder got us to be on this particular line.
That’s all for early exit: I haven’t come across any situation that wasn’t improved by flipping an if/else into an early exit, which makes it a bit of an underrated superpower.
I hope I’ve communicated the pleasure of flipping complex conditionals around, and hope you get as much mileage as I do out of this silly little trick.
Small insights like these remind me how much fun it is to play with computer programs.