Much Ado About Some(thing)

(I’m trying something new and saving code snippets in Scala Worksheets instead of Scastie. You can now run all my code samples locally by cloning the repo. You can still copy code snippets in to Scastie too.)

My name is Brad and I’m a software engineer at Axon. Something that’s interesting about engineering at Axon is that we write most of our backend services in Scala. I did not know Scala before starting here and had to pick it up along the way.

This post is a cautionary tale about a lesson that I recently learned the hard way, at 4 AM on December 26th, 2023.

‘Twas Was the Night After Christmas:

During my most recent on-call rotation, I got woken up in the wee hours of the morning by an alert for an elevated number of 500 Internal Server Error responses coming from one of our services. Upon checking the logs, I found a clump Null Pointer Exceptions. The logs had stack traces that all pointed to this line:

(Names have been changed to protect the innocent and the code has been changed to protect me from my non-disclosure agreement.)

This code queries a database for some business data, iterates over the results, and extracts the field called “expirationDate” to a value with the same name. We wrap the resulting value in an Option in case it’s NULL in the database record and then apply some transformation.

If you’re coming to Scala from Java or C#, this may seem like a pretty typical application hiccup, but it’s actually really strange behavior for a Scala application.

Let’s Talk About Options (Again)

As discussed in a previous post, the Scala language designers have some particularly strong opinions about null values. More specifically, they added them to the language as part of the price of interoperability with the larger Java ecosystem, but their usage is universally discouraged because they represent a Pandora’s Box that has been stuffed to the brim with Null Pointer Exceptions.

Rather than using null values to represent missing data, the language designers recommend a more nuanced approach using a type called Option. Option[A] is an abstract type that represents the potential presence of data. It is extended by two child types: Some[A], which represents the definite presence of the data, and None, which represents the absence:

A Tale of Two Constructors

If we look closely in the last example, we can see that there are two different methods for creating Some values: the Some constructor and the Option constructor.

Hmm, that seems a little strange though, right? Why would we need two constructors that do the same thing? Are there any differences between them?

In the overwhelming majority of cases, it turns out that there’s no difference at all:

In 99.999% of all cases, the Some and Option constructors will return the same output for any given input.

However, there is one case where the behavior of the two constructors differs significantly, and it just so happens that that case is the only one that matters:

If we wrap a value in the Option constructor, it will check if the input is null for us and convert it to None if so. However, if we pass null to the Some constructor, it will blindly wrap it in a Some with no one being the wiser.

This is a big problem.

By wrapping null in a Some, we’ve signaled that we have data to operate on when no such data exists. We’ve broken the core promise of the Option type, and in doing so, we’ve voided any warranty that came with that promise:

Fortunately, fixing this bug is usually pretty simple. Our code is already expecting an Optional value so the logic for handling the Some and None cases is usually already in place. We just need to correctly map null to None, and that’s as easy as changing Some to Option:

In general, defaulting to using the Option constructor instead of the Some constructor is a best practice that will help you avoid this pitfall in the future. I also recommend replacing existing uses of Some with Option as you come across them in your code too.

Let’s Wrap Up

In this post, we learned that

  • We can create Optional values with the Some and Option constructors

  • However, the Some constructor doesn’t check it’s input for nullity, which can cause some tricky bugs

  • The best way to avoid these bugs is to use the Option constructor instead of the Some constructor whenever you need to create an Option

Hopefully this post can help you avoid some 4 A.M. pages of your own.

Happy Scala-ing!

Previous
Previous

On Writing (Pull Requests) Well

Next
Next

Methods and Functions and Partials, Oh My!