Why should you return early?
When I started my journey as a computer programmer, I had written my code in various way. In most cases, I translated requirements into specific conditions and statements, but I hadn’t taken too much care of readability or quality of my code. “It works, that’s it” – and the code base grew and grew in time. Thousands of written methods later, I had discovered that a lot of these little pieces of logic could look better if I reverse some condition within them. Unconsciously, I’ve started following the rule which I had later called the “fail fast” rule, and subsequently “return early”.
I realized I hadn’t had the one preferred way to check conditions within the single method. Sometimes I let execution if the expression was true. Another time I had waited for the false result to show the error message before the main part of the code was executing. In my head, I discovered various styles of checking conditions.
Update 2020-03-14
Although this article is 2yo, it’s one of the most popular articles I’ve written on my blog. I decided to review it, correct mistakes and simplify the content by eliminating visible inaccuracies. The topic this article touches is timeless, so I hope you’ll enjoy the reading. I appreciate every feedback.
Two approaches to check requirements
The most basic style of checking if something meets the requirement is to put the expectation into the condition simply. There is one example. I want to send a message only if the variable $email
is the valid e-mail address and if the variable $message
is not an empty string. Otherwise, I want to throw an exception.
function sendEmail(string $email, string $message)
{
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
if ($message !== '') {
return Mailer::send($email, $message);
} else {
throw new InvalidArgumentException('Cannot send an empty message.');
}
} else {
throw new InvalidArgumentException('Email is not valid.');
}
}
The above snippet is one-to-one what I wrote before. Is it correct? Definitely. Is it readable? Not quite.
What is wrong with moving requirements literally to the code?
We can point two problems with sendMail
method:
- It has more than one level of indentation within the method body.
- You don’t know the success path without analyzing the flow of the method.
Since the first point concerns the “Clean Code” side, let me focus on the second one. The code mentioned above has a couple of branches and the return
statement isn’t visible – you have to look for it. I know that this example is short, however, try to imagine the more extended method with more arguments and conditions (or even probably more than one return statement). Have you tried? Some pieces of code are awful, but the small change could improve readability drastically. The only thing you have to do is to reverse conditions.
function sendEmail(string $email, string $message)
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Email is not valid.');
}
if ($message === '') {
throw new InvalidArgumentException('Cannot send an empty message.');
}
return Mailer::send($email, $message);
}
I admit that I also remove one else
after the second condition. I know that it’s subjective, but in my opinion, this code looks much cleaner and prettier than the previous version. And the only thing I did is reversing conditions within if statements.
What I reach by reversing conditions?
First, the max indentation level within method body is one, and we got rid of the nested condition. Thanks to these operations, the code is more readable, and it’s easier to understand. The expected result is at the end of the method, so it’s easy to find. The flow within this method is clear and visible.
Now you probably guess what I meant by calling this approach “fail fast”. Unfortunately, I figured out that Jim Shore, together with Martin Fowler, had defined “fail fast” before I created my very first “Hello World” app. The goal is almost the same, but I decided to change the name of my approach to “return early”. I also figured out that this is already defined pattern. However, I’m glad I had noticed it on my own! Because of this, I’ll continue using the other name, which, in my opinion, is more accurate for this article.
Now, let’s get a closer look at the “return early” concept.
“Return early” concept
You should know what is the “return early” rule. Nonetheless, I want to try to define this concept by myself
Return early is the way of writing functions or methods so that the expected positive result is returned at the end of the function and the rest of the code terminates the execution when conditions are not met.
I wrote the first version of the definition in 2017, but it was awkward and complicated, so I decided to make it simple. The old one sounds that:
“Conception of writing functions (methods) so that the expected positive result is returned at the end of the function and the rest of the code should cause as soon as possible the termination in case of divergence with the purpose of this function."
If you see a way to improve this definition, please let me know. I’m always open for discussion.
Follow the “happy path”
“The pot of gold is at the end of the rainbow”. Do you remember this story? Maybe did you find one? The expected positive result should be like a pot of gold – placed at the end of the function. Just look at the above sendEmail
example. Our goal was to send a message to the given e-mail address. The execution must follow the “success path” or, as the header says, the “happy path”. Happy, because this is the expected positive result and this is the reason why we call this function.
By placing the final operation at the end of the function, we always know what our “happy path” looks like and what is the expected positive result. No guessing, no analyzing – quick look should be enough to find out what is the goal.
Get rid of bad cases early
The “happy path” is the best result that we got from the function. But the execution might don’t go in the right way – and that’s ok. You can come back to our sendEmail
example. If someone provides an invalid e-mail address, then he’ll cause an expected bad case. We don’t want to pass an invalid e-mail address to the further execution. We should terminate the execution or simply return from function earlier.
The termination could be either the return or the exception. The choice is yours. You should use the way that is more convenient for you or what it would fit in a particular case.
Many people may feel awkward by using more than one return statement per single function. If the language gives you a possibility to explicitly define return value, I don’t see any reason why you shouldn’t use it, even more gladly when it gives you real advantage, e.g. better readability and maintainability.
The Bouncer Pattern
Bouncer Pattern is what I described in the previous section. It’s also known as “Assertions” or even “Guard clauses”. All mean the same and they prevent code execution in case of the invalid state. Take a look at this code snippet:
function matrixAdd(array $mA, array $mB)
{
if (!isMatrix($mA)) {
throw new InvalidArgumentException("First argument is not a valid matrix.");
}
if (!isMatrix($mB)) {
throw new InvalidArgumentException("Second argument is not a valid matrix.");
}
if (!hasSameSize($mA, $mB)) {
throw new InvalidArgumentException("Arrays have not equal size.");
}
return array_map(function ($cA, $cB) {
return array_map(function ($vA, $vB) {
return $vA + $vB;
}, $cA, $cB);
}, $mA, $mB);
}
The first part of the code is responsible for checking if an operation can proceed. The return statement contains the main logic of our function. In the ideal world, you wouldn’t need the first part of the code.
You may noticed that I use array functions instead of basic loops. I recommend this approach in another article.
Return early from the controller’s action
Controller’s action is a great candidate to use a “return early” approach. Actions often do much more things before they return a final response. Let’s consider below updatePostAction
method that comes from the PostController
.
/* PostController.php */
public function updatePostAction(Request $request, $postId)
{
$error = false;
if ($this->isGranded('POST_EDIT')) {
$post = $this->repository->get($postId);
if ($post) {
$form = $this->createPostUpdateForm();
$form->handleRequest($post, $request);
if ($form->isValid()) {
$this->manager->persist($post);
$this->manager->flush();
$message = "Post has been updated.";
} else {
$message = "Post validation error.";
$error = true;
}
} else {
$message = "Post doesn't exist.";
$error = true;
}
} else {
$message = "Insufficient permissions.";
$error = true;
}
$this->addFlash($message);
if ($error) {
$response = new Response("post.update", ['id' => $postId]);
} else {
$response = new RedirectResponse("post.index");
}
return $response;
}
Such a big piece of code here. It has one return statement and a lot of nested conditions. I don’t know what you feel looking at this code snippet, but I think that it should be a better way to do this. Another example takes advantage of the “return early” approach.
/* PostController.php */
public function updatePostAction(Request $request, $postId)
{
$failResponse = new Response("post.update", ['id' => $postId]);
if (!$this->isGranded('POST_EDIT')) {
$this->addFlash("Insufficient permissions.");
return $failResponse;
}
$post = $this->repository->get($postId);
if (!$post) {
$this->addFlash("Post doesn't exist.");
return $failResponse;
}
$form = $this->createPostUpdateForm();
$form->handleRequest($post, $request);
if (!$form->isValid()) {
$this->addFlash("Post validation error.");
return $failResponse;
}
$this->manager->persist($post);
$this->manager->flush();
return new RedirectResponse("post.index");
}
Now you can observe that this action has a clearly visible “happy path”. It’s not one-to-one as the previous examples, but it follows the same conception. If you expect that something may go wrong, check it and return early! Moreover, the one-level of indentation makes the code more readable, and, just note, I don’t need to use the else
keyword anywhere!
Return early from recursive functions
Also recursive function always should have a terminator that can looks like “return early” statement. This is a simple example:
function reverse($string, $acc = '')
{
if (!$string) {
return $acc;
}
return reverse(substr($string, 1), $string[0] . $acc);
}
Objections about “return early” conception
The main question is – what is the advantage of this concept? Why should I take care of this? Or when I shouldn’t?
Problem 1: Code style is subjective
We, as programmers, spend much more time reading code than writing. That’s common true. Therefore, we should write code which is easy to read and understand. I proposed to react on falsy state first, not only because it improves readability but also it ensures that the main gear within the function is always at its end (or really close). In other words, we see the expected success result as our “happy path”.
Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …[Therefore,] making it easy to read makes it easier to write.
– Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship
But the code style is subjective. Someone may feel better with single return statement or with multiple assignments instead of writing the same method call again and again (like addFlash
in one of above code snippets).
Problem 2: Sometimes “return early” is an exaggeration
I know a lot of cases where applying “return early” approach could harm the code or at least gives nothing. Think about all these setters where you have to assign value to the field only when the param is different than false
:
public function setUrl($url)
{
if (!$url) {
return;
}
$this->url = $url;
}
That’s not the best way to improve code’s readability. You should know where and when you can use techniques like ” return early “. This situation shows that sometimes it’s good to ignore rules.
public function setUrl($url)
{
if ($url) {
$this->url = $url;
}
}
Problem 3: It looks like break statement
Because function may have more than one return statement, we don’t accurately know where from the result comes. As long as you use “return early” to stop reaching an invalid state, you shouldn’t care about this. Of course, are you writing tests?
Problem 4: Sometimes it’s ok to use a variable instead of many returns
There are some structures where “return early” doesn’t change anything. Please look at these two examples of the same function. It comes from the “game of life” algorithm where function calculates the next state of the cell:
function nextState($currentState, $neighbours)
{
$nextState = 0;
if ($currentState === 0 && $neighbours === 3) {
$nextState = 1;
} elseif ($currentState === 1 && $neighbours >= 2 && $neighbours <= 3) {
$nextState = 1;
}
return $nextState;
}
The same function but using “return early” looks as below:
function nextState($currentState, $neighbours)
{
if ($currentState === 0 && $neighbours === 3) {
return 1;
}
if ($currentState === 1 && $neighbours >= 2 && $neighbours <= 3) {
return 1;
}
return 0;
}
So, as you see, there is no big advantage in this case.
Are you wondering what is a “happy path” in the above code snippet? That’s the exciting thing. The function’s name isn’t so meaningful, but its hidden goal is to “kill” a cell. Everything that keeps our cell alive goes to the invalid state. So, maybe do you have a better proposal for the name for this function?
Conclusions
The way how we write code comes from our customs. It’s hard to say that something is better than others when the outcome is the same. It’s very subjective like many more things in the programming.
“Return early” concept introduces some rules. Rules that you shouldn’t blindly follow. As you can see there are a lot of cases where this approach helps and otherwise, where it doesn’t change anything. It’s crucial to keep a discipline during writing code because the consistent code style is much more important than anything else when it comes to the cognitive load.
I use “return early” as often as I can. It means that I use it where I see the advantage of returning early from functions, e.g. because of invalid state or when the change improves readability. I’m used that success is always at the end of the function. The hidden mantra sounds:
Follow the success path (happy path) and react in case of errors.
Resources
- J. Shore, M. Folwer, “Fail Fast”, IEEE Software 2004
- Bouncer Pattern
- “Should I have only one return statement?”, Stack Overflow
Featured Photo by Franz Spitaler on Unsplash