Did you know that “null” is a concept that had to be invented? C.A.R. Hoare created it in 1965 as part of the Algol W language. I think adding “null” to programming languages was a giant step in the wrong direction. But you don’t have to take my word for it. C.A.R. Hoare agrees with me and calls it his Billion Dollar Mistake. Since “null” had to be invented and Algol W wasn’t the first programming language, that means that there must be programming languages without the concept of “null”.
But, how do they avoid this? For example, what if you get some data from a database and the row is not there? If a language doesn’t have “null”, how could it represent this state? The answer is with the Optional
type. In this article I’m going to explain in detail why you should prefer the Optional
type (AKA the Maybe type) over “null”. But before I explain this, I want to explain what I dislike about “null”.
“Null” is not that big of an issue if you read and write all the code you use. But as soon as you have to work with someone else’s code, it starts slowing you down in minor ways. For example, if my programming language allows “null” and I write this code var x = someLibrary.someMethod(y);, there is a lot of ambiguity. When “null” is a possibility, this single line makes me wonder 3 things:
- Is someLibrary “null”?
- Can I pass in “null” for y ?
- Is x “null” because someMethod can return “null”?
It is inconvenient (relative to the alternative I will explain below) to answer these questions. The only way I know how is to “read documentation”/”trial and error at run time”/”assume”/”guess”. There’s something unsatisfying about each of these options. If I make the wrong assumption, it may take a very long time for me to realize my mistake. I’ll only see the problem if I run this code (because the error only happens at run time), at that point I’ll have to recompile and redeploy to fix the issue.
Worst case scenario, these issues can take down a production environment. And if one line of code can give me 3 areas of concern, imagine how this adds up in projects that have thousands of lines of code. A minor inconvenience multiplied a thousand times can turn into a major liability. Wouldn’t it be better if there was a way to prevent these errors and catch them at compile time? Wouldn’t it be better if the code told me if Step 1, 2, or 3 was “yes” or “no”? Optional types give a means to do this. The Optional type works like this (this is Java code, but the concept applies to all languages):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
package com.sleepeasysoftware.optionalcookbook; import java.util.HashMap; import java.util.Map; import java.util.Optional; /** * Created by Daniel Kaplan on behalf of Sleep Easy Software. */ public class Usage { private Map<String, String> map = new HashMap<>(); public void creatingOptional() { Optional<String> empty = Optional.empty(); //this is an alternative to using "null" Optional<String> something = Optional.of("hi"); //this is an alternative to using "hi" } /** * This method will find something, or maybe it won't. Notice how the API of the code says it's using Optional. * This means you can tell, without even looking at this documentation, that you can pass in Optional.empty() or a * real value. It also implies that you can't pass in "null". * <p> * If we wrote a maybeFindSomething that used nulls instead, you would HAVE to take time to investigate if * you are allowed to pass in nulls or nulls are returned. Here's an alternative signature of maybeFindSomething * that takes nulls: * <code> * <pre> * public String maybeFindSomething(String key); * </pre> * </code> * Pretty typical, but the problem is you don't know if the return value can be null or the parameter you pass in * can be null without investigating further. * * @param maybeKey A key to use to look up data. This is an Optional so you are allowed to pass in Optional.empty() * @return If a value is found, this will return Optional.of(value), otherwise it will return Optional.empty() */ public Optional<String> maybeFindSomething(Optional<String> maybeKey) { if (maybeKey.isPresent()) { return Optional.ofNullable(map.get(maybeKey.get())); //if the map.get call returns "null", return Optional.empty(), otherwise, return Optional.of(map.get(key)) } else { return Optional.empty(); } } public void usingOptional() { /** * Now we're going to show an example of calling some code that returns Optional */ map.put("foo", "bar"); Optional<String> maybeFoo = maybeFindSomething(Optional.of("foo")); //we put "foo -> bar" in the map, so this will return a value if (maybeFoo.isPresent()) { System.out.println("I found a value for \"foo\": " + maybeFoo.get()); //this will print something } Optional<String> maybeBaz = maybeFindSomething(Optional.of("baz")); //we did not put "baz -> ..." in the map, so this will return an Optional.empty() if (maybeBaz.isPresent()) { System.out.println("I found a value for \"baz\": " + maybeBaz.get()); //we will not enter this if statement } //the maybeFindSomething API tells us, with self documenting code, that we can pass in an Optional. This means we are allowed to pass in Optional.empty() Optional<String> maybeEmpty = maybeFindSomething(Optional.empty()); //we did not put "baz -> ..." in the map, so this will return an Optional.empty() if (maybeEmpty.isPresent()) { System.out.println("I found a value for Optional.empty(): " + maybeEmpty.get()); //we will not enter this if statement } } public static void main(String[] args) { new Usage().usingOptional(); } } |
As you can see, one advantage to using Optional
is that the code becomes self documenting. You don’t have to guess whether code accepts or returns “null” because it tells you. This is a huge advantage in maintainability. Another advantage is that you have to write
optional.get() to get the value out of it. That means you can’t accidentally overlook that the value is missing.
I’m not going to pretend this is 100% win with no downside. As you can see with the
maybeFoo field above, our code has to deal with the possibility that it may be empty even though we know that it’s not. In other words, Optional
forces you to deal with missing values even if you know they’re there, but null lets you use missing values even when you shouldn’t. In my opinion, the way that Optional is self documenting and finds issues at compile time makes it a better choice than “null”.
By the way, you can find the source code of this example here: https://github.com/tieTYT/OptionalCookbook