May 23 2020

Name That Algorithm #3

Tony Hoare, the inventor of null, claimed later in his career that null was his “billion dollar mistake.” Languages like Rust and Haskell address the issue of null very directly through the forced handling of Some and None. As for others, it’s up to the programmer to check for null values.

If you find yourself manually handling null with say, JavaScript, then you may want to consider Uncle Bob’s advice. He preaches to never return null from a function. If null is returned, you will likely have a null check from the caller. Conversely, if null is never returned, then there is much less chance to find yourself null checking. Instead, it is better decision to throw an error.

Consider the following null check:

function getAuthor(post) {
  if (!post || !post.author || !post.author.name) {
    return null
  }
  return post.author.name
}

Now we are forced to handle the sad path in two branches:

  1. Returning a null name (in the snippet above)
  2. Check whether the name is available (from the caller side)

Some frameworks are pretty forgiving with null values such as React. Even so, I would advice against using null as a crutch. Instead, let’s throw these errors. Let’s TDD out our expected implementation.

const {expect} = require('chai')

describe('getAuthor', () => {
  it('throws if the author does not exist', () => {
    const badFn = () => getAuthor(null)
    expect(badFn).to.throw(/Author not found/)
  })

  it('returns the author', () => {
    const post = {author: {name: 'Jake Robers'}}
    const author = getAuthor(post)
    expect(author).to.equal('Jake Robers')
  })
})

The second test passes since the author name is already returning. Now we just need to refactor our null check and make it throw. Feel free to come up with your own solution too. I would love to hear your feedback and how you approach this issue. Here is how I did it:

const _ = require('lodash')
const assert = require('assert')

function getAuthor(post) {
  assert(_.get(post, 'author.name'), 'Author not found')
  return post.author.name
}

Assert isn’t just for tests! You can use it in implementation code as well. Also by using _.get, the ugly some && some.property pattern is abolished. But wait, there’s more. Keep reading to see how we can implicitly throw an error.

How might a caller want to use this function? Here is an extremely contrived example, which I apologize for. Imagine maybe the post comes from a database, use input, or some impure source. You can also imagine that instead of the return, we are triggering an effect, such as a database write, console output, or writing to a network stream.

function displayAuthor(post) {
  try {
    return getAuthor(post)
  } catch {
    return 'The Software Scrutiny Journal'
  }
}

Ok, now let’s get a little wacky. If we are just catching an error, why are we gracefully plucking the author name and running an assert? Can’t we rewrite getAuthor and just do this?

function getAuthor(post) {
  return post.author.name
}

This is the exact implementation as the first iteration with exception to the ugly null check. How simple. In summary, if any undefined/null value in the attribute chain, we implicitly throw. Also, instead of explicitly throwing an AssertionError, we raise a TypeError. In the case that more exceptions are introduced, we can still handle the individual errors by checking the error type with the name/message attributes.

Is post.author.name chaining good practice? I’m really not sure. After years and years of writing JavaScript, I tend to have an adverse reaction to the crude attribute selection of a.b.c. It also might not be evident to a peer that getAuthor is dangerous and throws. As a more novice JavaScript developer, I would not have realized that this would need to be caught. Even today, I might skim through a function like this, and forget to wrap the call in a try/catch clause.

However, I do like implicitly throwing errors – it’s less code to write (and read). If you do not like the final implementation, it is okay to fall back on the assert implementation :). I’ll leave this for you to decide. After all, it’s just one line of code difference.