Why Using Null is So Bad
Why is Null a Problem?
"I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years." Tony Hoare
Object references in code don't tell the developer that a reference is null. The compiler, your best tool for code integrity, doesn't have a way of knowing when a reference is null or not. Both the person writing the code and the software to verify the code can't handle this problem. This inevitably causes these programming errors to be pushed off until runtime as NullPointerExceptions. It's not uncommon for these errors to occur after the code has been deployed to production and it fails for the customer. Nulls are dangerous, and there isn't anything to ensure they are handled properly.
Can't I Just Use The Safe Navigation Operator?
The Safe Navigation Operator is simply syntactic sugar that aimed to avoid NullPointerExceptions. It is not a solution for null. It simply helps cover up the fact that null is there and makes writing code around nulls easier and cleaner.
// This is sudo code
final var accountNumber = account?.getAccountNumber();
This isn't any different than writing the following code:
final String accountNumber;
if (account == null) {
accountNumber = null;
} else {
accountNumber = account.getAccountNumber();
}
Some languages do however have help with the compiler to help the accuracy of this operator. This generally isn't perfect and still is only tackling the issue of NullPointerException avoidance. This is only part of the issue with nulls.
The Developer Psychology of Null
Let's say that when you walk into a room in your house, you may or may not get sprayed in the face with water. However, if you turn on the light before you enter a room, it's guaranteed that you won't be sprayed in the face with water. How long will it take until you turn on the light before entering a room, whether you need to or not just to avoid getting sprayed in the face? This is exactly what happens with nulls. How many times do developers have to get bitten by nulls before they take a brute force approach and start null checking everything? What new problems are they going to introduce with this approach, and what will they suppress that wasn't intended?
Developers who work in a code base that uses nulls will never fully understand the data they are working with. They will be too focused on avoiding NullPointerExceptions and won't be focused on understanding the data. Take the following method signature:
public User getUser()
What is the developer going to think when they call this method? When they call this method, are they going to write the following code?
final User user = getUser();
if (user != null) {
// do something
}
What if getUser() comes back as null and someone forgets to handle it? This is a common and easy mistake. What if this method will never return a null User? How many pointless null checks does this introduce in the system due to the paranoia of null references? With pointless null checks, you now have code falsely documenting "In our business domain, getUser() can be null because other developers are null checking it" instead of, "Why is getUser() returning null?" This is a problem if it is null. Who broke the code? How could you ever understand the data when you are too fixated on handling null?
Nulls and False Positives
Nulls have the potential of introducing a type of false positive. Since developers don't want code throwing NullPointerExceptions, they are more likely to write code that is null protected. This can be a bad mindset, and they'll suppress code doing so. For example:
public void send() {
if (emailAddress != null) {
sendEmail(emailAddress);
}
}
If emailAddress is null, nothing will happen even though the developer called send(). Time is now required to figure out why send() isn't working. You could argue that this code is really suppressing an error and is no different than the following code.
public void send() {
try {
sendEmail(emailAddress);
} catch (final NullPointerException e) {
}
}
The symptom is a NullPointerException, but what is the cause? The solution may seem to be null checking emailAddress, but maybe the cause is it being called from the wrong layer. Maybe something wasn't loaded correctly, or there is a bug in the user validation. There are a lot of possibilities, depending on context. It would be better to write the code above as:
public void send() {
if (emailAddress == null) {
throw new RuntimeException("[Insert descriptive explanation and potential fixes]")
} else {
sendEmail(emailAddress);
}
}
This helps prevent false positives and saves the developer and/or user time by informing them of the problem.
Conclusion
Don't use nulls whenever you can avoid it. They add significant complexity to the code, encourage bad practices, and prevent your team from fully understanding the code base.