What is problematic with returning null?
A common pattern, both in the code I am used to work with, and in parts of the .NET Framework, is to return null in methods if for some reason a valid return value is not available.
One example of this is the Find method of List<T>:
var persons = new List<Person>(); var bandit = persons.Find(p => p.Name == "Billy the Kid"); // Returns default(Person) which is Null (assuming Person is a reference type) if (bandit == null) { ... } else { ... }
So, why would you consider handling this, and similar cases, differently?
My first argument is, returning null makes code hard to use. Let me show you by example.
Assume that you write code that will call the following public method:
public Person GetPersonByName(string name) { ... }
Is there any way for the user to tell, by looking at the method signature, whether he needs to guard for the return value being null or not? No there is not. He will have to check the documentation, or the code (if available). Would it not be better if he could tell directly? You could achieve that by naming the method GetPersonByNameOrNullIfNotFound but that is not very desirable.
My second argument is, returning null forces the caller to pollute his code with multiple checks and if/else forks:
var dude = persons.GetPersonByName("Jesse"); if (dude == null) { log.Error("Could not find Jesse"); } else { var car = cars.FindByOwner(dude); if (car == null) { log.Error("Dude, Where's My Car?"); } else { ... } }
This makes the code much harder to read.
So what alternatives are there?
Alternative 1: The null object pattern
The Null Object Pattern (wikipedia link) says that instead of returning null you should return a valid object, but with empty methods and fields. That is, an instance of the class that just doesn’t do anything. For example:
public Person GetPersonByName(string name) { var id = _db.Find(name); if (id == 0) { return Person.Nobody; } return Person(id); }
Here the Person class implements a static property, Nobody, that returns the null object version of Person.
Advantages
There are a couple of advantages of using this pattern over returning null.
- Users do not need to add null checks in the calling code, making it simpler.
- The risk of NullReferenceException being thrown is eliminated.
Disadvantages
All alternative ways have some disadvantages, using the null object pattern may:
- Hide errors/bugs, since the program might appear to be running as expected
- Force the introduction of just a different type of error checking
The last point here is interesting. If you, when implementing this pattern, realize that need to check the returned value anyway. Then this pattern is not suitable for your situation and you should consider a different solution.
Alternative 2: Fail fast
If you analyze your code and come to the conclusion that the case where null is returned is an exceptional case and really indicates an error condition, you can choose to throw an exception instead of returning null. One example of this is the File class in the .NET framework. Calling File.Open with an invalid path throws an exception (different exceptions depending on the type of error, for example FileNotFoundException if the file does not exist). A system that fails directly when an error condition is detected is called a Fail-fast system (wikipedia link).
I have worked in a large project where this philosophy was applied. Actually we didn’t throw exceptions, we directly halted the entire system, dumped all memory, stack and logs and reported the error. The result was that once the system went live it was really robust (having multiple levels of testing, some that ran for days or weeks simulating real load, also helped a lot).
Advantages
- Makes errors visible
- Forces you to fix any errors early, leading to a more robust system once in production
- Reduces cost of fixing failures and bugs since it is cheaper to fix them early in the development process
Disadvantages
Failing fast might not be suitable in all situations. Assume for example that you are dependent on data from an external system, or user. If that system provides invalid data you do not want your system to fail. However, in situations where you are in control I recommend failing fast.
Alternative 3: Tester-Doer pattern
If you are depending on external systems and need to consider cases like your system being provided with corrupt data, shaky networks, missing files, database servers being overloaded, etc, throwing exceptions and halting the system won’t work for you. You could still throw exceptions and let the user add a try-catch clause to handle the exceptions, but if some scenarios are really error prone, throwing exceptions a lot, it might impact performance to the extend it is unacceptable (microsoft link). One way to approach this situation is to split the operation in two parts, one that checks if the resource is available and a second that gets the data. For example if you want to read a file but don’t know in advance that it is available you can do this:
if (File.Exists(path)) // Test if file exist { var content = File.ReadAllText(path); // And if it does, read it }
This idea can be expanded to test a lot of different preconditions and if they are fulfilled, do the operations.
Advantages
- Allows you to verify that the operation will probably succeed
- Removes the overhead of exception handling (exceptions are really bad for performance)
- The calling code can be made quite clear
Disadvantages
- Even though the test passes, the accessing method might fail. For example, in a multi threaded system the resource may have been deleted by another thread between the test and the accessing method.
- Requires the caller to remember to do both calls and not just call the accessing method.
Alternative 4: Try-Parse pattern
A different version of the Tester-Doer Pattern is the Try-Parse pattern. One example where this is used in the .NET framework is the int.TryParse method that tries to parse a string to an integer. It returns a boolean value that indicates whether the parsing succeeded or failed. The actual integer value is supplied by an out-parameter in the method call:
if (int.TryParse(aString, out var i)) { Console.WriteLine($"The value is {i}"); }
Advantages
- Same as tester-doer, with the addition that you only need one call, hence the thread safety issue is taken care of.
Disadvantages
- Obscure method signature where the return value is not the data you requested, instead an out variable is needed.
Summary
This post have hopefully provided you with some alternatives to returning null and some ideas on why and when it can be good to do so. As always, most important is that you try to make the code as clear and simple as possible. Now, code!