We Need to Stop Using null: Here’s Why
By: Arindam Roy
Tags: JavaScript, Software Development, Software Engineering, Java, Programming

Photo by Ben Hershey on Unsplash

In general, while doing code reviews, I have developed a strong notion of why the use of null (or nil , NULL , nullptr etc depending on your programming language) generally causes more problems than it solves.

The fundamental problem of null is that it is trying to represent the fact that it is not a value while being assigned as a value. This fundamental flaw then snowballs and manifests into problems that we see in everyday production code.

Here, I have made an attempt to document the various types of issues I commonly find with using null.

Poor language Decisions

Certain languages have not handled edge-cases of null type variables in a consistent manner. For example, in Java, you have primitive and reference types of variables.

Primitive variables do not allow null initialization. They throw a compile-time error.

int i = null; //this throws a compile error error: incompatible types: <null> cannot be converted to int

int i= null;

But an instance of the class Integer can accept a null initialization.

Integer j = null; //perfectly fine

Now, in Java, when you assign a reference type to a primitive type, it silently does a type conversion.

Integer p = 100;

int q = p; //works fine

If you try the same with an Integer instance initialized to null , you will now get a runtime exception.

Integer i= null;

int j = i; //throws a runtime exception

The above, when executed, throws an exception:

Exception in thread "main" java.lang.NullPointerException

at Main.main(HelloWorld.java:5)

This kind of inconsistency can be extremely confusing, and lead to difficult to track bugs being introduced to the codebase.

Tracking Nulls Leads to Sloppy Code

The problem with things allowed to be null is that you need to check for their null case every time. For example, in Java, an empty check for a string always looks like this:

String s = "hello";

if(s == null || s.length() == 0) {

//do stuff

}

This is, in fact, so prevalent that almost all production code either has a bunch of utility classes to handle these edge cases or used a library like StringUtils to handle these cases. The native language has no support for it.

In fact, C# has added a isNullOrEmpty function natively to handle this extremely common scenario.

Multiple, Often Confusing Definitions

In weakly typed languages, things can get even more confusing. For instance, in Javascript, since there are not types, we need a way to explicitly identify whether a property of an object is null , or if it simply does not exist.

To handle this, Javascript now has two types of nulls.

undefined — property exists but has no value. null — property exists and is assigned a null value.

This again is extremely confusing. For instance, I am allowed to explicitly set the value of a variable as undefined , which contradicts the above definition.

And now for the biggest problem that I see every day.

Type Subversion

Null is an exception to type-definitions.

Type definitions are a great advantage of strongly typed languages. For instance, let’s take the method trim() on the class String . Now, if I try to call trim() on any instance, the language (in this scenario — Java) will check if the type of the instance if String . If not, it will provide a compile-time error.

String x = " Hello ";

x = x.trim();

System.out.println(x);

Above code works perfectly. It provides a trimmed string. But if we try to do it on a different object, let’s say an instance of Integer , we get a compile-time error.

Integer x = 5;

x = x.trim();

This generates an error:

error: cannot find symbol

x = x.trim();

^

symbol: method trim()

location: variable x of type Integer

These compile-time errors let developers write high-quality production code. Null breaks this rule because any reference can be null. Calling methods on references generate NullPointerException . These often go unchecked even in code-reviews and then come out during rum-time requiring extensive debugging.

String s = null;

s = s.trim(); //throws NullPointerException