Do not null-check lists
tl;dr
Lists should never be allowed to be null, this is a code smell:
if(myList != null)
{
foreach(var item in myList)
{
// ...
}
}
Null checking lists
Let's say you are working on Class B. An error occurs where a list turns out to be null. The list is given to you from Class A.
After some digging, you discover that the list originates from a web request:
The correct thing to do would be to make sure that HouseController cannot pass a null-valued list. Either, an empty list should be created in HouseController, or HouseController should deny the web request.
The fail-fast principle
The fail-fast principle states that it is better for a program to crash early, if it has encountered an erroneous state.
Imagine that class A generates a variable that has some kind of error. If the error is unrecoverable, according to the fail-fast principle, it would be best if it were A's responsibility to throw an exception, or crash.
Let us also imagine the absolute opposite. Class A generates an erroneous value, that value is persisted in a NoSQL datastore. Three months later you get weird bug reports from users. Good luck hunting down the source of the bug.
A program crashes with a NullPointerException only when you try to access a member of the null valued variable. Allowing unchecked nulls in the code violates the fail fast principle.
Can variable x be null...?
Looking at any code (that you did not write yourself 5 minutes ago), you always have to wonder: should I null check this? Can this variable be null? This is stealing focus from what you should be thinking: how can I achieve my clients' goals?
The point above is even worse for interfaces:
public interface IHouseRegister {
House GetHouseByPostalCode(string code);
}
var house = houseRegister.GetHouseByPostalCode("AAA"); // AAA does not exist
Will it return null? An empty object? Will it crash? Will other implementations of IHouseRegister behave the same way?
Functional approaches
In Haskell, every operation that might fail is wrapped in another object. We can use this approach too, to make the code more clear:
public interface IHouseRegister {
Maybe<House> GetHouseByPostalCode(string code);
}
var maybeHouse = houseRegister.GetHouseByPostalCode("AAA");
if(maybeHouse.HasValue) {
var house = maybeHouse.Value;
// Do stuff...
}
public struct Maybe<T> where T : class
{
public T Value { get; private set; }
public bool HasValue { get; private set; }
public Maybe(T value)
{
Value = value;
HasValue = (value != null);
}
}
Nullable reference types
If you are lucky enough to get to start a brand new dotnet core project: congratulations. By putting <Nullable>enable</Nullable> in your project file, the compiler will automatically disallow any variables from being null. That is, unless you specify their types with a ? at the end.
public interface IHouseRegister {
// A function that, per definition, can return null.
House? GetHouseByPostalCode(string code);
}
Lists
There exists one type of objects which does not need to suffer from the null madness: lists. If you think about it, a Maybe could be seen as a list with 0-1 elements. Let's look at looping:
var list = new List<string>();
var maybe = new Maybe<string>();
foreach(var s in list) {
Log(s);
}
if(maybe.HasValue) {
Log(maybe.Value);
}
Now imagine, during development the list is passed from somewhere else; and you get a NullPointerException on the foreach loop. Your reflex might be to just add a null check, but that would be wrong.
if(myList != null) // This belongs with the code initializing myList, not here
{
foreach(var item in myList)
{
// ...
}
}
Lists should never be allowed to be null. There is no purpose or point in it. Instead of adding a null check directly in the code, you should put in the work and track the source of the list. It is clearly doing its job poorly.
The only place where a null-check would be appropriate is if you recieve the list from an external source. However, your first priority should then be to create an instance of a list anyway, so that the rest of your program can trust lists to always be initialized.