DEV Community

Cover image for Reduce null checks in your code
Taha Shashtari
Taha Shashtari

Posted on • Originally published at tahazsh.com

Reduce null checks in your code

Imagine you are building a CMS (content-management system) for publishing articles. And in that CMS, you want to build your own analytics tool for the published posts—to see how many page views an article got, for example.

You want the authors using your CMS to be able to see how an article is doing through the analytics page for that article.

Let's say you are storing these analytics in their own table in the database. So to get the analytics data for a post, you have to fetch these analytics separately based on the post id.

So you would write something like this:

function fetchAnalyticsForPostId(postId) {
  // code to fetch the analytics data for postId
  return new PostAnalytics(analyticsData)
}

function displayAnalytics(analyticsObject) {
  console.log(`
    Page views: ${analyticsObject.pageViews},
    Users: ${analyticsObject.users},
    New Users: ${analyticsObject.newUsers}
  `)
}

const analyticsObject = fetchAnalyticsForPostId(5)

displayAnalytics(analyticsObject)
Enter fullscreen mode Exit fullscreen mode

This code would work as along as the fetched analytics exists in the database. If it doesn't (draft articles, for example), then I have to check for null everywhere it's used.

function displayAnalytics(analyticsObject) {
  console.log(`
    Page views: ${analyticsObject === null ? 0 : analyticsObject.pageViews},
    Users: ${analyticsObject === null ? 0 : analyticsObject.users},
    New Users: ${analyticsObject === null ? 0 : analyticsObject.newUsers}
  `)
}
Enter fullscreen mode Exit fullscreen mode

It's not a big deal if I'm doing these null checks only in this function. But in real-world cases, I will likely do these checks many times throughout my code base.

It would be much cleaner if I can just use the analyticsObject directly without checking for null each time I'm using it. Actually I can do this using the Null Object Pattern.

The idea of the Null Object Pattern is simple: it's a special version of an object that knows how to handle null cases.

In this example, it means I just need to define a new class with the same interface as PostAnalytics and return zeros from each getter—assuming I have three getters in it: pageViews, users, and newUsers.

Let's say that the PostAnalytics class looks like this:

class PostAnalytics {
  #analyticsData

  constructor(analyticsData) {
    this.#analyticsData = analyticsData
  }

  get pageViews() {
    return this.#analyticsData.pageViews
  }

  get users() {
    return this.#analyticsData.users
  }

  get newUsers() {
    return this.#analyticsData.newUsers
  }
}
Enter fullscreen mode Exit fullscreen mode

To create a null version for it, I would create this new class:

class NullPostAnalytics {
  get pageViews() {
    return 0
  }

  get users() {
    return 0
  }

  get newUsers() {
    return 0
  }
}
Enter fullscreen mode Exit fullscreen mode

So its interface (in other words, its public methods) should be exactly the same as the real one, PostAnalytics, but it should return empty values instead of real values. In this example, I'm returning 0 because they are numbers and that's what I want for null analytics to be. But if they are strings, I might return empty strings or whatever the logic should be.

Now the last step is to return this object instead of the real one in the creation step.

function fetchAnalyticsForPostId(postId) {
  // code to fetch the analytics data for postId
  if (!analyticsData) {
    return new NullPostAnalytics()
  }
  return new PostAnalytics(analyticsData)
}
Enter fullscreen mode Exit fullscreen mode

Notice how I have the creation logic of the analytics object in that function. In real-world apps, I would move that logic into a factory function for the post analytics object.

Now after this change, I can remove all the null checks for the analyticsObject from my code.

Top comments (6)

Collapse
 
ant_f_dev profile image
Anthony Fung

Nice. I've seen this pattern before, but haven't really used it. Would you say there are any advantages of using the Null Object Pattern over code like the following?

class PostAnalytics {
  pageViews = 0;
  users = 0;
  newUsers = 0;

  constructor(analyticsData) {
    if (!analyticsData) {
      return;
    }

    this.pageViews = analyticsData.pageViews;
    this.users = analyticsData.users;
    this.newUsers = analyticsData.newUsers;
  }  
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
tahazsh profile image
Taha Shashtari

Thanks, Anthony!

If it was only doing what you're showing here, I would say there's no main difference. However, in most cases a class would also contain methods to do something. With the null object pattern, you can easily handle each one in the null case without introducing any conditionals.

Another reason I prefer the Null class over the above implementation is that I can clearly see what all the code in the class is for (no need to see any checks like if (!analyticsData)), and in this case it's for the null case—which would most likely contain more logic and code as I add more features to it. In other words, your code will be more cohesive because all null related code are put in a single, dedicated place.

Collapse
 
ant_f_dev profile image
Anthony Fung

Right. So by having a null-object, you end up with two implementations:

  • one with all 'default' behaviours only (i.e. the null-object)
  • one with behaviours for populated data

In that way, there is no need for guard statements or conditionals when implementing them; the conditional occurs when choosing which implementation to return.

Thread Thread
 
tahazsh profile image
Taha Shashtari

Exactly!

Collapse
 
karleb profile image
Karleb • Edited

This is nice.

Collapse
 
tahazsh profile image
Taha Shashtari

Thanks!