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
andOption
constructorsHowever, the
Some
constructor doesn’t check it’s input for nullity, which can cause some tricky bugsThe best way to avoid these bugs is to use the
Option
constructor instead of theSome
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!