Null checks aren't fun, but even worse are the ever ambiguous run-time NullReferenceExceptions we might otherwise receive.
Take the following code:
public class Family
{
public List<string> Names;
}
// consumer creating a family
var family = new Family();
family.Names = new[] {"John", "Jane"}.ToList();
// consumer adding a name
family.Names = family.Names ?? new List<string>();
family.Names.Add("Baby");
// consumer searching names
var searchName = "Bob";
var hasSomeoneNamed = family.Names != null && family.Names.Contains(searchName);
// consumer
comment delineates a separate example of using the Family
type&&
operator.Invariant - "never changing"
A few simple changes can make things much simpler for consumers. If we require a list of names upon creation of a family, we can do the following:
public class Family
{
public readonly List<string> Names;
public Family(IEnumerable<string> names)
{
Names = names.ToList();
}
}
readonly
, which means it can't be changed after creation. readonly
alone is a great start.Look at the impact on consumers:
// consumer creating a family
var names = new[] { "John", "Jane" };
var family = new Family(names);
// consumer adding a name
family.Names.Add("Baby");
// consumer searching names
var searchName = "Bob";
var hasSomeoneNamed = family.Names.Contains(searchName);
I'd much rather maintain this code!
We could also encapsulate the Names list:
public class Family
{
protected readonly List<string> Names;
public Family(IEnumerable<string> names)
{
Names = names.ToList();
}
public void AddName(string name)
{
Names.Add(name);
}
public bool HasSomeoneNamed(string searchName)
{
return Names.Contains(searchName);
}
}
Now our consumers don't even have to be aware of the fact that Names exists let alone that it might be null:
// consumer creating a family
var names = new[] { "John", "Jane" };
var family = new Family(names);
// consumer adding a name
family.AddName("Baby");
// consumer searching names
var searchName = "Bob";
var hasSomeoneNamed = family.HasSomeoneNamed(searchName);
However, I usually don't go this far:
I prefer the invariant only approach, giving consumers the guarantee that Names won't be null and letting them take it from there.
readonly
can cause a lot of friction in serialization, if it does, try an auto property with a public getter and protected/private setter, but be aware that deserializers may leave this null.Null check insanity is often a sign of design smell, invariants are a great first step in the direction of creating a solid contract between producers and consumers of a type.
It may seem like work to enforce invariants, but the dividends in maintainability and readability are worth it. Think how often you stumble upon null checks, or the lack thereof. Understanding these patterns will make them second nature.