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!


Comments
Gravatar Alexandre Konioukhov Wednesday, August 7, 2019
We have Option type in F#
Gravatar Sean G. Wright Wednesday, August 7, 2019
I really like your various recommendations for avoiding nulls. It's easy to say "don't use them", but not really helpful - giving a selection of solutions for various use-cases definitely is helpful. The Maybe/Option type pattern is also something that could be used here. Vladimir Khorikov gives a nice C# implementation in his blog post https://enterprisecraftsmanship.com/2015/03/20/functional-c-handling-failures-input-errors/. If you want to use the Null Object pattern, discoverability can be tricky. You can treat the "Null" object as a singleton, make it static on the class so it's easy to find, return, and compare for equality. In the above example, User would have a NullUser static property that is the value that should always be returned in place of 'null', and any callers of methods that return NullUser could do an equality comparison between the result and the NullUser property.
Gravatar Ed Wednesday, August 7, 2019
I saw an interesting pattern for handling this recently where the method accepts two delegates passed in - one for when there is a value, and one for when there is not, forcing the consumer to implement both forcing compile time checking. http://www.codingwithsam.com/2019/06/19/c-the-good-parts/ (under Avoid null – use domain modelling) It sure is a lot of code to write in addition, and perhaps fits into the 'overkill' category. An interesting approach none the less.
Gravatar Charles Barest Wednesday, August 7, 2019
Excellent thought experiment and well presented Scott! I agree with Sean that Khorikov’s article is a good extension of this article.
Gravatar Anthony Johnston Wednesday, August 7, 2019
My rule of thumb is If you return null, prefix the method with “Try” If not throw GetStuff() // will throw TryGetStuff() //May return null Also Null return can be handled with ?? var stuff = TryGetStuff() ?? Stuff.Default
Gravatar Graham Thursday, August 8, 2019
I like this discussion and some of the suggestions, but I'm not entirely convinced that returning a NullUser object is any more helpful than returning null itself. The caller will still have to do some sort of check on the result; you're just changing the test from "== null" to "is NullUser". It's only really helpful if you're expecting the result to be used without any checks being performed on it first.
Sio Thursday, August 8, 2019
Good article. What about a `Get(id)` function on a repository? The record not existing in the repository is a valid scenario. I've always found returning null when not found to work fine. The only alternative I can think of is wrapping it in another type with some meta data showing if a record was found. You still end up with a null property on that type at the end of it though to guard against.
Gravatar Brian Thursday, August 8, 2019
Absolutely nothing wrong in returning a null! Its a very valid, quick and easy way of testing for an invalid result. If you need to know why a function failed (not in database etc) then use a different way of passing pack the output and return appropriate code indicating the problem or the other way round. Just make sure its documented!!!!! Exceptions should only be used in 'exceptional' circumstances (its in the name!) for example when its not possible to guarantee a result without an issue such as a divide by zero or if there is no other way to handle the error.
Gravatar lopa Thursday, August 8, 2019
Worst advice I've seen for a long time. testing for null return should be automatic, it's lazy and a potential bug not to do so. Example, I'm printing an invoice for an order, I cll a routine to see if there was a quotation before the order was placed (to add references to that in the output say invoice). if no quote document I return a null, a "default value" makes no sense, an exception, are you kidding? I want that qiotation if it exists in a few places, wherever I print a reference from it I'll check for null. (if I had a "default value" I also don;t want to print the reference headers). "An ideal function, like an assistant cook, will encapsulate work and produce something useful." sometimes, in the above case if their is a quote there is NOTHING useful to return except a big red flag, most obvious a null, that there is nothing. a default value is even more useless because I still have to test, an exception is 100% the wrong thing to do ever. Returning null is the most obvious choice, returning a default the worst possible idea, "I have something [but it's useless]" exception this exception that, AKA "ON ERROR GOTO" in disguise, worst idea ever, bar none... Here's a much more practical suggestion for real world programmers: AVOID EXCEPTIONS UNLESS YOU WANT EVERYTHING TO STOP.
Gravatar Jesse de Wit Thursday, August 8, 2019
Another good way to handle this is with Try. I find the syntax for this very clear and eliminates those future errors : if (!TryGetThing(out Thing thing)) // do something about it!
Gravatar scott Thursday, August 8, 2019
@Graham: The idea is to avoid testing for null or specific types. If you have to violate Liskov's Substitution principle, something went wrong with the approach.
Gravatar scott Thursday, August 8, 2019
Graham: I think it depends on what you are returning, I addressed that issue in the patient search section.
Gravatar scott Thursday, August 8, 2019
@Brian: I didn't say "never use null", but in some conditions I believe there are better alternatives.
Gravatar scott Thursday, August 8, 2019
@lopa: I am not telling anyone to stop using null unless they are using a language and runtime that prevents nulls. Your generalizations do not add to the conversation. "Avoid exceptions unless you want everything to stop", for example, was already a point I made using a specific context in the post.
Gravatar Sean Kearon Thursday, August 8, 2019
Following on from Alexandre's and Sean's comments above, there are a couple of useful Option implementations for C# around which may be of interest: https://www.nuget.org/packages/Option/ https://github.com/nlkl/Optional
Gravatar George Thursday, August 8, 2019
Use a ValueTuple2 return type. The first item is a Result: public bool Success { get; set; } public Int32 StatusCode { get; set; } public string Message { get; set; } public Exception Exception { get; set; } The second is the data you want to return. You only ever look at the data if result.Success is true. Plus you can return specific statuses, a message that can be logged/displayed if you want, and if an exception occurred you have the details of that also.
Gravatar Mike Thursday, August 8, 2019
Returning a null object is frequently harder to test for than a true null. Sometimes a null object is the correct approach, other times it's not. Once again, you need to specify the function's interface and not care about the internal workings of the function. @Graham, While you're correct that returning null is a violation of the Liskov Substitution Principle, Barbara Liskov had no problems returning null when it was the appropriate solution. I know because she taught me OO programming with the CLU programming language. She was also my thesis advisor.
Gravatar Ken Thursday, August 8, 2019
If you are having problems with people returning null, then you are your own problem. All you have to do, all any of us have ever had to do was check to see if a nullable type was null before trying to do anything with it. It is that simple.
Gravatar Scott Thursday, August 8, 2019
@Mike: Some languages avoid the concept of null altogether.
Gravatar Ken Thursday, August 8, 2019
@Ken: If we consider null values as the only solution that works, yes. If you have the imagination to think beyond null, there are alternatives.
Gravatar EdCarden Thursday, August 8, 2019
We have a similar problem in the database/SQL world where too often less experienced developers use NULL when they shouldn't. Its incredibly frustrating and can lead to very hard to find logic bugs. Because there are legitimate reasons for allowing NULL we can;t just lock down its use.
Gravatar David Thursday, August 8, 2019
Being a C/C++ programmer, one alternative might be to return a boolean, i.e. success/error condition (we tend to use loggers and log our errors and abnormal conditions) while using [in,out] reference arguments.
Gravatar André Cardoso Thursday, August 8, 2019
Like others said, I think the best alternative is the Maybe/Option. Some great explanations (with understandable/explained FP jargon): https://blog.ploeh.dk/2018/03/26/the-maybe-functor/ https://blog.ploeh.dk/2019/02/04/how-to-get-the-value-out-of-the-monad/ https://blog.ploeh.dk/2019/07/15/tester-doer-isomorphisms/
Gravatar Jonathan Thursday, August 8, 2019
Maybe/Options don't really avoid nulls, they just dress it up (kind of like Null objects) and force the user to actually check that the return value is usable. Speaking of which, I really can't see using Null Objects in most cases. They're the wrong approach for data objects since maintaining a hierarchy of data classes is silly; better to just have a single class and change the data to fit the circumstances. The best place for them is if you already have a couple subclasses to handle different behaviors and you never, ever check to see what actual type you just pulled from your collection of base type objects. Maybe I'm just lacking imagination, but while I can imagine having Null objects in collections, I can't imagine an situation where you'd ever want a single object that might be a Null Object. I see the User example above, but that looks like it would be better modeled as a single data class where "null" is just the absence of a name and an empty claims dictionary.
Gravatar David H Sherwood Friday, August 9, 2019
Returning null is better than NullUser. Suppose we have a function that modifies a user. If we pass null to this function and the the programming did not check for null, the appt will STOP - throw a null exception. This is a good thing. it says that the function needs to be fixed. The programmer may not know how to fix it. She may have to talk to her bose to find out what it is supposed to happen when no user is passed. On the other hand, if we pass NullUser the app will NOT STOP and will update NullUser causing catastrophic problems down the road. Null reference exceptions can be your friend.
Gravatar scott Friday, August 9, 2019
@David: That's why NullUser is immutable. Also, passing as an argument is a different context.
Gravatar Ernesto Monday, September 9, 2019
Very helpful post, it's something I started seriously looking into after taking a class in Coursera, "Programming Languages A" which teaches mostly in SML. There's also a course by Zoran Horvat in Pruralsight which focuses on this very topic. It definitely has its merit, and when correctly implemented there does not need to be a check for a specific type by the client as the NullObject implements the functionality expected with 'default' values.
Comments are closed.