Lim Yoong Kang

I don't like null checking

Imagine you were using a JavaScript library, let’s call it AwesomeLibrary.

AwesomeLibrary provides you an API that lets you get the most popular Book in a bookstore.

In other words, it provides this function:

/**
* Given a bookstore's name, return the most popular `Book`
* @param {string} bookstore Name of bookstore
* @returns {Book} The most popular book in the bookstore
*/
const getMostPopularBook = bookstore => {
    let books = database.getBooks(bookstore);
    let booksByPopularity = Book.sortByPopularity(books);
    return booksByPopularity[0];
}

This is relatively typical code. It receives a string which represents the name of the bookstore, and returns a single object of the Book class.

Here’s what the Book class looks like (truncated for brevity):

class Book {
    constructor(title, author, year, popularity) { ... }

    getMetadata() { ... }

    purchase(budget) { ... }

    static sortByPopularity(books) { ... }
}

So, this allows you to write something like this:

import { getMostPopularBook } from 'awesome-library';

let bookStores = ['bookstore1', 'bookstore2', 'another bookstore'];

// The most popular books in each store
let popularBooks = bookStores.map(getMostPopularBook);

// The metadata for each popular book including title, author, year, etc.
let popularBookMetadata = popularBooks.map(book => book.getMetadata());

Now, fastidious readers may have noticed that the library doesn’t handle some edge cases.

Let’s pretend for some reason one of the stores does not have any books.

So the author of the library makes a patch, and in some stroke of madness decides to change the above function to this:

/**
* Given a bookstore's name, return the most popular `Book`
* @param {string} bookstore Name of bookstore
* @returns {Book|Carburetor} The most popular book in the bookstore, or a Carburetor
*/
const getMostPopularBook = bookstore => {
    let books = database.getBooks(bookstore);
    // Look here:
    if (books.length === 0) return new Carburetor();

    let booksByPopularity = Book.sortByPopularity(books);
    return booksByPopularity[0];
}

The function can now return either a Book or a Carburetor.

Although the function indicates you should expect a Book, it can sometimes return an object of a totally unrelated class called Carburetor.

Does this seem ridiculous to you?

I hope it does!

So, why wouldn’t the below piece of code seem equally ridiculous?

/**
* Given a bookstore's name, return the most popular `Book`
* @param {string} bookstore Name of bookstore
* @returns {Book|null} The most popular book in the bookstore, or null
*/
const getMostPopularBook = bookstore => {
    let books = database.getBooks(bookstore);
    // Look here:
    if (books.length === 0) return null;

    let booksByPopularity = Book.sortByPopularity(books);
    return booksByPopularity[0];
}

Remember, null is an object!

If it is absurd to return an object of class Carburetor from a function called getMostPopularBook, why is it acceptable to return null?

By the way, null is an object, just like an instance of Carburetor.

You can verify by this in your browser’s dev console by typing typeof null. You’ll see "object".

Null is an object!

NULL.

IS.

AN.

OBJECT.

What this does to my code

Let’s face it, we’ve all written code like that. We’ve all also used code like that.

I don’t like it at all, because what it does is it forces me, a user of the library to do things like this:

import { getMostPopularBook } from 'awesome-library';

let bookStores = ['bookstore1', 'bookstore2', 'another bookstore'];

let popularBooks = bookStores.map(getMostPopularBook);

// See the extra filter
let popularBookMetadata = popularBooks.filter(book => book !== null).map(book => book.getMetadata());

Sometimes, that also comes in this form, which is even worse (slightly exaggerated):

import { getMostPopularBook } from 'awesome-library';

let bookStores = ['bookstore1', 'bookstore2', 'another bookstore'];
let popularBooks = bookStores.map(getMostPopularBook);
let budget = new Budget(200.00);

for (let book in popularBooks) {
    // null check here
    if (book) {
        let metadata = book.getMetadata(); // can return null

        // another null check because `Book` throws an exception if
        // the remaining budget is insufficient
        if (metadata && metadata.price <= budget.remaining) book.purchase(budget);
    }
    else console.log('we ran out of money');
}

It’s just… ugly.

I have to guard against null at all times, because I can’t rely on things like getMostPopularBook, to… you know, return a Book.

I don’t like writing code that way.

What I’d prefer

Where possible, I’d prefer an object with the same interface is returned, instead of null.

The library could return, for example, a NullBook:

class NullBook extends Book {
    getMetadata() { return { price: 0 } }

    purchase(budget) { /* does nothing */ }
}

Then I could call all the methods without having to do all these null checks.

Unfortunately, I don’t think this is possible all the time.

And since it’s rare to see this in JavaScript (or Python for that matter), it can also be argued that this isn’t “idiomatic”.

Sigh. I’ll just go back to null checking.

What about undefined?

@#$#%%($%)!!!!

Some resources

Lots of people have talked about this before, with some proposed solutions (like the one I described). Check out these resources: