Appendix: Don't Null

CSC-430

Phillip Wright

Billion-Dollar Mistake

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. […] I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement.

Billion-Dollar Mistake II

In reality, 1 billion dollars is a huge understatement of the financial damage caused by null references.

Most developers spend a comically large amount of time chasing null pointers.

Billion-Dollar Mistake III

…but you don’t have to!

This one secret will save you time and money–more than the cost of this course!

What’s The Problem?

Many will tell you that null pointers aren’t a problem if you’re a good programmer.

final User user = getUser(10);
final String name;

if(user !=null){
  name = user.getName();
}else{
  name = "Not Found";
}

The Problems

Problem: Every method could, possibly, return a null value, so to be safe you need to check the result of every method.

The “good programmers” don’t do this, though. They “know” which methods need to be tested…which is why they, too, get null pointer exceptions.

The Problems II

Problem: If you don’t want to test every method, you need to read the documentation for methods to determine which ones can return null.

The “good programmers” don’t do this either…which is why they, too, get null pointer exceptions.

The Problems III

Problem: If you do read the documentation, it is imperative that the documentation is correct and complete.

The “good programmers” don’t write and update documentation sufficiently, though…which is why they, too, get null pointer exceptions.

The Good Programmers

Of course, the “good programmers” always have excuses for why their null pointer problems are not their fault and continue to have null pointer problems.

The Dumb Programmer

Meanwhile, the “incompetent” programmer acknowledges his or her inability to cope with complex systems and asks the compiler for help.

public Optional<User> getUser(int id){
  if(userExists(id)){
    return Optional.of(new User(id));
  }else{
    return Optional.empty();
  }
}

The Dumb Programmer II

Now, this won’t even compile!

final User user = getUser();

The compiler complains, because the return value is not a User. But we need an User

The Dumb Programmer III

final Optional<User> maybeUser = getUser(10);
final String name;
if(maybeUser.isPresent()){
  name = maybeUser.get().getName();
}else{
  name = "Not Found";
}

Did we just do the same thing as the good programmer?

The Difference

Sort of. There is one immediate difference, though: Now we are forced by the compiler to address the possibility of no value being returned.

This is already a big gain with only slightly more code needed than the good programmer’s approach.

The Difference II

If your team is on board with never returning null values (which is significantly easier than avoiding null values returned from methods unexpectedly), then you can use Optional values to eliminate null pointer exceptions from your code.

Java Is Not Great

Java, unfortunately, does not prevent you from returning a null value even if the return type is an Optional value.

You should have used Haskell.

Bonus

Another bonus to this approach is that the return type itself documents the fact that the method may not return a value. Even better, if this documentation changes, the compiler will force you to rethink your use of the method!

What About Get()

Of course, you could just assume that the method returns a value and write:

final String name = getUser(10).get().getName();

Which isn’t any better than

final String name = getUser(10).getName()

Using the old code.

Psychology

The big difference, though, is psychology. If you know the meaning of an Optional return type, and you decide that, even though this method is guaranteed to sometimes not return a value, you are going to roll the dice, then there is literally no programming language feature that can help you, because you willfully do bad things.

Psychology II

On the other hand, the developer who doesn’t check for a null is often just being careless. There is no red flag being waived in his or her face about the certain danger.

So, Optional return types flip the responsibility around.

Psychology III

With null values, you have to be responsible enough to make sure you don’t screw up.

With Optional values, you are almost forced to write good, safe code and have to male a conscious decision to do the bad thing.

(Some even argue that get() should not exist in the Optional class)

The Problems IV

Problem: Why are we returning a default value of “Not Found”? If the User does not exist, then why should the name exist? We could just return a null, but that’s bad!

Instead, we should just return an Optional name!

A Mess

final Optional<User> maybeUser = getUser(10);
if(maybeUser.isPresent()){
  final User u = maybeUser.get();
  return Optional.of(u.getName());
}else{
  return Optional.empty();
}

Am I Trolling?

At this point you should be ready to revolt and assume I am trolling you.

Are you really, supposed to litter Optional wrappers throughout every line of code?

Improvement

No! Higher order functions will rescue us and make life beautiful.

The Optional class defines a method called map which accepts a function as its argument.

Improvement II

More specifically, for the type Optional of type T, it accepts a function that takes an object of type T and returns some other type of object.

Improvement III

Using this, we can write the following instead:

return getUser(10).map(u->u.getName());

If the Optional contains a value, the function is applied to it, resulting in an Optional containing an String. Otherwise, the whole thing is just an empty Optional.

The Dumb Programmer Wins

Now, we have obtained safety and simpler code. Good programmers and their null checks have been thoroughly defeated at this point.

Going Further

What if the getName() method itself returns an *Optional*

final Optional<String> result = 
  getUser(10).map(u->u.getName());

This doesn’t compile. Why?

Improvement IV

The Optional type also provides a method flatMap that takes a function which returns an Optional value and “flattens” the Optional layers so that there are no nested Optionals

Awesome

Now, we can write safe code in the face of missing values quite simply:

return getUser(10)
         .flatMap(u -> u.getName())
         .map(n -> n.toUpperCase())
         .filter(n -> n.equals("PHILLIP"))
         .map(n->"User: " + n)

Improvement V

We can do a little cleanup using method references

return getUser(10)
         .flatMap(User::getName)
         .map(String::toUpperCase)
         .filter(n -> n.equals("PHILLIP"))
         .map(n->"User: " + n)