Return Early Pattern

Leonel Menaia Dev
The Startup
Published in
6 min readAug 5, 2020

--

When I learned about programming, the basic thought process that I had when creating a function was to validate the corresponding requirements until reaching the result.

What can be observed in this approach?

  • The nonlinear flow of the code is hard to follow because of the nested conditions.
  • It is difficult to figure out the corresponding “else” for each “ifwhich makes the error handling confusing to read, especially when the “if” block is big.
  • It is required to follow the flow of the code, navigating through nested “ifs”, to find the expected positive result.
  • For the sake of this example, an exception is being thrown on the “else”. If the “else” didn’t terminate the execution, it would execute the rest of the code. This can lead to unnecessary errors.

It also includes a couple of anti-patterns:

  • Else is considered smelly. When the condition is complicated, the “else” is twice, because the reader has to invert it; When the “if “ block is big, it is easy to forget the condition; Nested “If” and “else” confuse the reader.
  • Arrow anti-pattern is when the code starts becoming shaped like an arrow because of nested conditions and loops.

Return Early

Let’s attempt to refactor the code with a different mindset.

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 (by returning or throwing an exception) when conditions are not met.

This is accomplished by reverting the “if” conditions, doing the necessary error handling, and returning or throwing an adequate exception, finishing the execution of the function.

There are a few things that are observable here:

  • The code only has one level of indentation. It is possible to read it linearly.
  • The expected positive result is quickly findable at the end of the function.
  • Using this thought process, there is a bigger focus on finding the errors first and safely implementing business logic later, which avoids unnecessary bugs.
  • The fail-fast mindset used is similar to Test-Driven Development, which makes the code easier to test.
  • The function ends immediately on errors, avoiding the possibility of more code being executed without intention.

Design Patterns

While using the “return early” mindset the succeeding design patterns are followed.

Fail Fast

Jim Shore and Martin Fowler created the concept of Fail Fast in 2004. This concept is the basis for the “return early” rule. While failing fast, the code is more robust because of the initial focus in finding the conditions where the code execution can terminate. With this approach, bugs are easier to find and fix.

Guard Clause

A guard clause is simply a check (the inverted “if”) that immediately exits the function, either with a “return” statement or an exception. Using guard clauses, the possible error cases are identified and there is the respective handling by returning or throwing an adequate exception.

Happy Path

The happy path for a function would be where none of the validation rules raise an error, thus letting execution continue successfully to the end, generating a positive response.

Using the “return early” approach, the code is read linearly, thus exposing the happy path. Using this pattern, it is not needed to lose time following a code flow to get to the goal. It is possible to use muscle memory to find it.

Bouncer Pattern

The bouncer pattern is a method to validate certain conditions by either returning or throwing an exception. It is particularly useful when the validation code is complex and can be used in multiple scenarios. It complements the “return early” pattern.

Disadvantages

While the “return early” approach has positive points, it also has some fair criticisms which will now be brought to light.

Functions should only have one exit point

This coding rule dates back to Dijkstra’s structured programming. This notion of Single Entry, Single Exit (SESE) comes from languages with explicit resource management, like C and Assembly.

In languages where resources are not or should not be managed manually, there is little to no value in adhering to the old convention. SESE often makes code more complex. It is a dinosaur that (except for C) does not fit well into most of today’s languages. Instead of helping the understandability of code, it hinders it.

Resource Cleaning

High-level languages like Java and C# have garbage collection, but sometimes it is still needed to manage some resources manually. Thankfully, newer languages have the following concepts:

  • “Try, catch, and finally” statements enable the use of a resource, catching any possible exceptions and then releasing the resource in the final block, making sure that there are no memory leaks.
  • The “using“ statement allows the use of a resource inside a block and automatically disposes of it after its usage, even if the termination is caused prematurely.

These concepts allow the use of the “return early” rule while also disposing of the resources used when terminating the execution of the function.

Logging and Debugging

One argument is that one single “return” is easier to debug since it is only needed to add one breakpoint to catch all exits from a function, and easier to log because it only requires a log at the end. This is not necessarily true.

Using the “return early” approach it is possible to throw exceptions right way. Debugging is much easier if the reason why the code is failing is obvious. A log can also be added before each termination to better inform the developer. If it is required to log all exits, in case of multiple “return” statements, it is possible to log after getting the value from the respective function.

Multiple exit points affect readability

200 lines of code function with various “return” statements sprinkled randomly over it is not a great programming style and it is not readable. But such a function wouldn’t be easy to understand without those returns either. The bouncer pattern and the extract method pattern should be used to keep the size of the function within reasonable limits.

Code style is subjective

A design pattern is a general repeatable solution to a commonly occurring problem in software design. These are conventions that developers found over time that help to ease their work and should be used in the appropriate cases. However, some aspects of programming are subjective. Let’s take a look at the following example:

  • On the first approach, there is more written code and complexity compared to the second approach. However, the code is prepared, using the “return early” mindset, for future improvements to the function. However, this way of thinking infringes on the KISS and YAGNI rules. Keep It Simple Stupid and You Aren’t Gonna Need It. It is easy to change the code to the “return early” pattern if it is needed in the future.
  • The second approach is much more simple and readable. It goes straight to point and it would be my personal choice in this case.

Nevertheless, it is understandable why someone would use the first approach. In this case, arguing over the “correct way” to do it is losing important time.

Conclusion

The “return early” pattern is an excellent way to prevent functions from becoming confusing. However, this doesn’t mean that it can be applied every time. Occasionally, during the complex business logic, it is inevitable to have some nested “ifs”, even with the option of extracting the code to other functions.

The better approach is to align with the respective team, to share knowledge with them, to decide which patterns to use in each case, making sure that everyone has a similar mindset when programming. Developers spend more time reading code than writing it so it is essential to provide a positive experience for everyone.

--

--

Leonel Menaia Dev
The Startup

Android Developer at Critical Techworks / BMW Group