OdeToCode IC Logo

Think Twice Before Returning null

Wednesday, August 7, 2019

Let’s say you are working in the kitchen and preparing a five-course meal. A five-course meal is a considerable amount of work, but fortunately, you have an assistant who can help you with the mise en place. You’ve asked your assistant to cube some Russet potatoes, julienne some baby carrots, and dice some red onions. After 15 minutes of working, you check on your assistant’s progress and ...

Kitchen disaster

What a disaster! You have diced onions to cook with, but you’ve also got a mess to clean up.

This is how I feel when I use a method with the following inside:

return null

I’m seeing thousands of return null statements in recent code reviews. Yes, I know you’ll find my fingerprints on some of that code, but as my mom used to say: "live and learn".

Returning null Creates More Work

An ideal function, like an assistant cook, will encapsulate work and produce something useful. A function that returns a null reference achieves neither goal. Returning null is like throwing a time bomb into the software. Other code must a guard against null with if and else statements. These extra statements add more complexity to the software. The alternative is to allow null to crash the program with exceptions. Both scenarios are bad.

What Are the Alternatives?

Before throwing in a return null statement, or using LINQ operators like FirstOrDefault, consider some of these other options.

Throw an Exception

In some scenarios the right thing to do is to throw an exception. Throwing is the right approach when null represents an unrecoverable failure.

For example, imagine calling a helper method that gives you a database connection string. Imagine the method doesn’t find the connection string, so the method gives up and returns null. Your software doesn’t function without a database, so other developers write code that assumes a connection string is always in place. The code will try to establish connections using null connection strings. Eventually, some component is going to fail with a null reference exception or null argument exception and give us a stack trace that doesn’t point to the real problem.

Instead of returning null, the method should throw an exception with an explicit message like "Could not find a connection string named 'PatientDb'". Not only does the software fail fast, but the error message is easier to diagnose compared to the common null reference exception.

Throwing is not always the right option, however. Consider the case where a user is searching for a specific patient using an identifier. A method to execute the search has to consider the possibility that a user is searching with a bad or obsolete identifier, and not every search will find a patient. In this scenario it might be better to return null than throw an exception. But, there are other options, too.

Return a Default Value

Finding a sensible and safe default value works in some scenarios. For example, imagine a helper method to fetch the claims for a given user. What should the method do when no claims are found? Instead of returning null and forcing every caller to guard against a null collection of claims, consider returning an empty collection of claims.

private static readonly IReadOnlyDictionary<string, string> EmptyClaims = new Dictionary<string, string>();

public IReadOnlyDictionary<string, string> FindClaims(int userId)
{
    /*
     * Find claims from somewhere, or ...
     */


     // when no claims are found . . .
     return EmptyClaims;
}

If callers only use ContainsKey or TryGetValue to make authorization decisions, the empty claims collection works well. There are no claims in the empty collection, so the software will never authorize the user. Even better, the code doesn’t need to guard against a null reference.

Think of all the complexity you've removed from the rest of the software! Future generations who work on the code will praise you and build ornate shrines in your honor.

Return a Null Object

The null object pattern has been around for a long time because the pattern is successful at reducing complexity and making software easier to read and write. Imagine a method that needs to return the current user in the system, but the method can’t identify the user. Perhaps the method can’t identify the user because the user is anonymous, and not being able to find the user is an expected condition. We shouldn’t handle an expected condition using exceptions.

Another option is to use null to represent an unknown user, but returning null is lazy. In case you missed the opening paragraphs, returning null is like telling your callers "I’m not only giving up, but I’m going to stick you with a dangerous value, so handle with care, and good luck!". Future generations who work on the code will curse you and burn you in effigy.

The null object pattern solves the problem by returning a real object reference, and the real object contains some safe defaults. We don’t want an unidentified user to gain access to admin functionality, or somehow slip through permission checks, so we create a version of the user that will fail all permission checks and not display a name, but at the same time no code needs to guard against null and introduce if else checks everywhere.

There are a number of different approaches you can use to design null objects, but remember the goal is to build an object with safe defaults. Here’s an example. Let’s start with a User type.

public class User
{
    public User(string name, IReadOnlyDictionary<string, string> claims)
    {
        Name = name;
        Claims = claims;
    }

    public string Name { get; protected set; }
    public IReadOnlyDictionary<string, string> Claims { get; protected set; }
    public bool IsAdmin { get; }
}

Now we can create an instance of User that contains safe defaults, like an empty claims dictionary so the IsAdmin helper (which presumably looks into Claims, will return false). One way to define the class is to use inheritance, like so:

public class NullUser : User
{
    public NullUser() : base(string.Empty, EmptyClaims)
    {

    }

    private static readonly IReadOnlyDictionary<string, string> EmptyClaims = new Dictionary<string, string>();
}

Now we can write a method that always return a safe value.

private static readonly NullUser nullUser = new NullUser();

 User GetCurrentUser()
 {
     // if user not found :
     return nullUser;
 }

If we start thinking about alternatives to null today, we'll be better set to handle the future.

The Future Is Undoubtedly Going to use Non-nullable Reference Types

There is a new feature coming with version 8 of the C# compiler that will help us avoid nulls and all the problems they bring. The feature is non-nullable reference types. I expect most teams to aggressively adopt C# 8 after the release later this year, at least for new code. You can find many examples of the new feature in Microsoft docs and blogs, but the idea is to tell the C# compiler if a null value is allowed, or not, in each variable. The C# compiler can aggressively check code to make sure non-nullable reference types do not receive a null value. Using these types should give us simpler code, and force us to think before adding a return null.

Summary

Ten years ago, Tony Hoare apologized for inventing null, calling the null reference a billion dollar mistake. Let's see if we can avoid using his mistake!