avoid null

Why we should avoid null in software development

In software development, null is a term that is often used to represent a lack of value or an unknown state. It’s a concept that has been around for a long time and has been a source of confusion and bugs in software development. In this article, we will explore why we should avoid null in software development and what we can do instead.

What is null?

Null is a concept that represents the absence of a value or an unknown state. It is commonly used in programming languages to indicate that a variable has no value assigned to it. Null is often used to represent a missing or unknown value in databases, and it’s used in software development to indicate that a variable has no value assigned to it.

The problem with null

Let’s look at the following example.

We have a UserRepository interface (contract) that says it returns a User object based on an ID and we have a class that uses the repository.

public interface UserRepository {
    User findById(int id);
}
public void changeUserName(int id, String name) {
    User user = userRepository.findById(id);
    user.changeName(name);
    userRepository.update(user);
}

Now, imagine you are not familiar with the implementation of the UserRepositoryand you can only trust the interface (contract). What happens if another engineer who will implement this interface returns NULL when the user record is not found. Right, you’ll get NullPointerException.

How can we avoid null in this case?

As you understand, following the contract (interface) is very important especially when many engineers work on the same codebase. And what can we do in this situation?

Throw an exception

Let’s change the interface a bit.

public interface UserRepository {
    User findById(int id) throws RepositoryException;
}

Create a couple of exceptions that cover different scenarios.

public class RepositoryException extends Exception {}

public class UserNotFoundException extends RepositoryException {}

public class ConnectionException extends RepositoryException {}

And this is how we can work with exceptions.

public void changeUserName(int id, String name) {
    try {
        User user = userRepository.findById(id);
        user.changeName(name);
        userRepository.update(user);
    } catch (UserNotFoundException e) {
        // Log exception or throw another one
    } catch (ConnectionException e) {
      // ...
    }
}

Now, our contract has become even more stringent and it says how it can handle different situations.

Use null object pattern

In object-oriented computer programming, a null object is an object with no referenced value or with defined neutral (null) behavior.Wikipedia

Here’s what an implementation of the first version of the repository might look like:

public User findById(int id) {
    Record userRecord = database.select("users", "id = ?", id);
    if (record == null) {
        return User.createEmpty();
    }
    
    return new User(userRecord);
}

And this is how we can use it.

public void changeUserName(int id, String name) {
    User user = userRepository.findById(id);
    if(user.isEmpty()) {
        throw new UseCaseException("User not found");
    }
    
    user.changeName(name);
    userRepository.update(user);
}
public class User {
    private isEmpty = false;
    private String name;
    
    public void changeName(String name) {
        if(isEmpty) {
            throw new ModelException("You can change the name of the empty user");
        }
        
        this.name = name;
    }
}

The concept here is that even if the User object is empty, the repository will still give you back the object instead of null. However, it’s important to keep in mind that you should check whether the User object is empty or not before performing any actions on it, such as trying to change the name by using the changeName method. If you do forget to check and try to change the name of an empty User object, the method will throw an exception to let you know that you can’t make changes to an empty User.

What can null bring to your code

Null is a source of confusion and bugs in software development. One of the main problems with null is that it can cause NullPointerExceptions (NPEs) that are challenging to debug and can even result in crashes. Additionally, null is often ambiguous, and it’s not always clear what it represents in a particular context. As a result, code that relies heavily on null values can be difficult to understand and maintain.

Another issue with null is that it violates the principle of least astonishment, which states that software behavior should be predictable and intuitive. When a function or method returns null, it can be surprising and lead to unexpected results. Instead, we should strive to return sensible default values or throw meaningful exceptions when necessary.

Null can also lead to code that is difficult to test. When a function or method returns null, it can be challenging to write a test that verifies the function’s behavior. Tests that rely on null values can be brittle and difficult to maintain, making it challenging to ensure that the code is functioning correctly.

Conclusion

In conclusion, we should avoid using null in software development whenever possible. Null is a source of confusion and bugs in software development, and it violates the principle of least astonishment. Instead, we should strive to use alternative approaches, such as the Optional class or the Null Object pattern. These approaches provide a more explicit way to handle the absence of a value and make the code more readable, maintainable, and testable. By avoiding null, we can write more robust and reliable code that is easier to understand and maintain.

Extra materials:

Why null is bad

Null References: The Billion Dollar Mistake