Tripping on Code

Early Returns

January 12, 2021

When a developer is building an application of any complexity, they will have to write many conditional statements. Depending on the complexity of the problem, this can lead to heavy nesting of code.

For Example:

public int? GetPersonAge(string firstName, string lastName) 
{
  int? result = null;
  if (!string.IsNullOrWhitespace(firstName) && !string.IsNullOrWhitespace(lastName)) 
  {
    var person = this.repository.GetPerson(firstName, lastName);
    if (person != null) 
    {
      result = this.CalculateAge(person.DateOfBirth);
    }
  } 
  else 
  {
    throw new ArgumentException($"nameof(firstName) && nameof(lastName) cannot be null");
  }
  
  return result;
}

The above function has a few flaws.

  1. Readability - The code above is not very explicit in its intention. You need to read the entire method to realize that it returns null if no person is found.
  2. The nesting - The actual logic of the method is captured in the call to invoke calculateAge. This is hidden in the deepest level of nesting.

Functional Programming

F# and other functional programming languages deal with this differently. In F#, an if statement is supposed to return a value. Take a look at the code below.

let greatest a b c =
	if a >= b && a >= c 
  	then a
  elif b >= a && b >= c 
    then b
  else 
    c

The code above might be hard to read if you are unfamiliar with F#. But it makes a lot of sense. Let's break it down.

greatest is a function that accepts three parameters, a, b and c, and returns the greatest of the three numbers. F# and many other functional programming languages do not have a return statement; it is assumed that all methods return something, and interestingly an if statement is no different. An if statement always returns a value. So in the code above, if a is greater than equal to b and c, it returns a.

This pattern makes a lot of sense. Let's try and apply this in C# and clean up the code.

The GetPersonAge function with early returns

public int? GetPersonAge(string firstName, string lastName) 
{
  if (string.isNullOrWhitespace(firstName) || string.IsNullOrWhitespace(lastName)) 
  {
    throw new ArgumentException($"nameof(firstName) && nameof(lastName) cannot be null");
  }
  
  var person = this.repository.GetPerson(firstName, lastName);
  if (person == null) 
  {
    return null;
  }
  
  return this.CalculateAge(person.DateOfBirth);
}

The function above is a lot cleaner. The first few lines of code deal with the exceptional cases. It. explicity states the cases when it throws an exception

When to avoid Early Returns

As with almost every rule, you should only use early returns when it adds to the code's maintainability and readability.

There are cases when early returns make the code harder to read, as described in the following cases.

  • Suppose you have a method that sets other properties or member variables in the class. In this case, adding early returns can introduce bugs and make the code harder to follow.
  • When you have large if statements and many branching paths, here, early returns can be a detriment to readability.
  • If you have logic after the if statement that needs to be executed, early returns are definitely not going to work. However, you might consider breaking out the conditional logic into its own method if it makes sense.