Most any programming language has a way to express nothing, whether it's NULL
, null
, nil
, or even None
. But, what is nothing? Can nothing be something? More importantly should nothing ever be something?
Let's talk APIs
Having a value for nothing could be really useful when that's exactly what your code has to provide to its caller, but there are cases where it's difficult to define what that nothing actually means. Let's explore the case of a REST API endpoint that retrieves data, like GET /api/v1/apple/{id}
.
If the caller asks for a specific apple by ID, and that apple exists, then the apple should be returned. Simple enough! Of course there are cases where this is not so simple, like if the data source is temporarily unavailable or if there's a network outage. Since the caller did what they were supposed to do and submitted a valid request for something that exists, these situations should result in a 5xx error code (for more on error codes, check out this post).
Expressing nothing at all
What about if the specific apple does not exist? Since the resource in the context of the API request is an apple, and that apple cannot be found, a 404 is the most natural and descriptive response code. What about the response body? There are a few options.
- We could return an empty response to further reinforce that nothing was found. However, this could be rather ambiguous. Is the 404 returned because the apple wasn't found, or is there a bug in the API such that the request isn't actually handled? Returning a response body clearly disambiguates between the two scenarios.
- We could return null! Since there's nothing more nothing that nothing, this seems to make sense. However, this means we're treading into the danger zone of nothing actually meaning something, in this case that no apples with that ID were found. The difference is subtle, but enough to be problematic.
Returning null for anything other than the strict value of nothing with no further context sets the precedent in our API that null can mean nothing, except in certain circumstances in which it actually means something very specific. This makes null a rather ambiguous value, and certainly not the nothing it once was.
Nothing = Nothing
We've all been exposed to the reflexive property of real numbers. Zero will never equal 1 because it can only equal itself. Why then should null equal anything but exactly nothing? I would argue that giving such a value meaning in a specific context degrades its utility and specificity as a value in any context due to the uncertainty of its use. It's the sort of subtle ambiguity that is just as easy to dismiss as it is to be ensnared by. There are several language design decisions that have been influenced by this concept.
- The addition of
Optional
in Java - The
Option
enum withNone
andSome
in Rust - Idiomatic protobuf enums are expected to have a 0-value constant to clearly disambiguate from an actual value
- Numeric primary keys usually start with 1 instead of 0, since 0 is usually the default, unset value of an integer.
- Variables in most programming languages have a defined default value or "zero value" in the case of Go, which is the default value of the variable after declaration (C/C++ being notable exceptions... 'cause C/C++, that's why).
What about other values?
What about the case of the ID. Does this idea not extend to the ID 1234? Must 1234 now only refer to the specific ID value by this logic?
That's a great question! To answer it we need to better understand what we mean by "nothing".
Nothing - however it's expressed in the language of choice - is the total absence of value or context.
By this definition, nothing other than "nothing" can possibly satisfy it. This makes it much more like a special constant than a real value, unlike 1234 which has a value and context.
Bringing it together
Looking at the API scenario above, it would be much less ambiguous and clear to return a response body that specifically states that the apple could not be found. Given the definition above, this is clearly better because there's no attempted interpretation of something that is inherently devoid of meaning.
When to use nothing
This doesn't mean that nothing has no purpose in programming languages. Let's consider the case of another endpoint in our API, PATCH /api/v1/apple/{id}
. The intended use of the patch endpoint is to update a specific apple's attributes.
Let's say that an apple resource looks like this.
{
"id": 1234,
"tastiness": 10,
"variety": "Golden Delicious",
"freshness": 10
}
And that we want to update the freshness to 5 because it has sat in the fruit bowl for a while. One way to express this unambiguously through the patch endpoint is like this.
{
"id": 1234,
"tastiness": null,
"variety": null,
"freshness": 5
}
This means that tastiness
and variety
should remain unchanged, while the freshness
attribute should be changed to 5. This isn't the most succinct way to express such a change, but it has several advantages:
- The patch endpoint allows expressing changes (not the new state of the entire resource). Setting the two unchanging fields to
null
clearly indicates no value, which means there should be no change. - Listing the unchanging fields helps us to validate the request against the known shape of an apple resource and ensure that the request was really meant for this endpoint.
- Specifying the new value for
freshness
clearly expresses how the attribute should be changed. - Specifying the ID is another validation measure. The resource ID cannot usually be changed, so the same ID as the resource must be given for the request to be valid.
Conclusion
Hopefully this helps you to think about how you're using "nothing" in your code, and to start the conversation about how it could be better utilized. Sometimes, not using it is the best choice. :)
Comments