Expedia Group Tech — Software

Avoid Optional or Nullable Attributes with This Simple Trick

Simply take them out in this way

Image showing wooden tiles from a word game, with the tiles arranged to spell “Allow for Error”.
Photo by Brett Jordan on Pexels

TLDR, summary

Optional, nullable attributes needlessly introduce state to your objects and needlessly makes your code more bug-prone and more complex. By default, optional, nullable attributes can and should be avoided. A very simple and idiomatic way to avoid them is to just take them out.

Longer version

How many times have you come across classes like this?

Note, overuse of String and Int for everything is a problem all of its own, “primitive obsession”, but that’s a different topic altogether — the topic of this post is strictly about how to compose classes)

class User {
String userId;
String username;
String bestFriend;
String favoriteBand;
}

where userId and username are the essential attributes that constitute the core thing the class represents, and bestFriend and favoriteBandare optional, nullable attributes, and don’t really have anything to do with what constitutes a user.

You’ve probably come across this more times than you can count, even in fresh code, right? Probably most classes in our codebases contain a mix of non-nullable and nullable attributes. (or at least, a very significant share do)

Why it’s bad

Because any given object can now be in the state of

  1. having all attributes
  2. all but bestFriend
  3. all but favoriteBand
  4. just userId and username.

That’s 4 (!) states! Vs just the 1 possible state if it always had all attributes.

Note how only 2 optional attributes added 4 states — the range of states scales exponentially the more optional attributes there are.

Aside from the mere conceptual mental complexity, now, very real errors will result from doing things like

listOfFriends.add(someUser.getBestFriend()); //java.lang.UnsupportedOperationException

So now, every part of the code that deals with these objects will have to take the state into account and deal with the possibly absent values, eg:

if (someUser.getBestFriend() != null) {
listOfFriends.add(someUser.getBestFriend());
} else { //do nothing }

Worse, the compiler won’t warn about the problem, it will just result in runtime errors in production if not dealt with.

Also, conceptually, when you for example want the best friend of someone, you want the best friend object, not the originating user (let alone all details about that user) who considers that person their best friend!

The solution

Optional, nullable attributes can very simply and idiomatically be completely eliminated by just moving them out:

class User {
String userId;
String username;
}

and turned into their own thing:

class BestFriend { 
String originUserId;
String bestFriendUserId;
}

class BestFriendService {
Optional<User> findBestFriendOf(String userId);
}
class FavoriteBand {
String userId;
String bandId; //or String bandName or whatever
}

class FavoriteBandService {
Optional<User> findBestFriendOf(String userId);
}

Note that this isn’t some “clever” solution, and it doesn’t rely on annotations, Optional, Kotlin nullable attributes etc. — we’re literally just moving the concept of a best friend out of the user class to be their own thing — which it is! It doesn’t belong on the user object!

Common arguments

These are just some of the most common ones I see, but I’m sure more could be thought of.

Leaving the attributes on the User and using Kotlin Types? / Optional / @NotNull and @Nullable solves it

No, it doesn’t. All they do is make the compiler warn and prevent things like:

listOfFriends.add(someUser.getBestFriend()); //java.lang.UnsupportedOperationException

which is great! But the fundamental problem of state and complexity (both conceptual and in concrete code) is still there (that is, when working with the object we still have to do if/map or whatever), and the class remains poorly designed, with attributes that don’t really have anything to do with what constitutes a user.

Code review will catch any resulting bugs

Maybe! By all means, you could carefully code review to make sure there are no bugs resulting. But… why not just code in a way that completely avoids the issue, greatly simplifying the code review, so you won’t have to (hopefully) catch them in a code review?

What about making the attributes non-optional with default values?

Sometimes, this is an option (pun intended). But who are you going to set as bestFriend, and what band are you going to set as favoriteBand? It’s not always possible to set a default — many times, defaults are just set to shoddy non-values like “NA” or “unknown” or whatever just to please the compiler, when, really, that’s a strong smell that something isn’t right and there’s an underlying issue that should be fixed instead — the classes should be redesigned.

Sometimes attributes really are optional, nullable / sometimes optional, nullable attributes are useful, practical etc

Sure! Despite what it may seem, I’m actually not making some fundamentalist argument that absolutely no class must ever contain optional, nullable attributes. I’m simply saying, rather than by default casually and liberally adding optional, nullable attributes to classes, instead, by default, try to keep classes free of optional, nullable attributes as much as possible, and only introduce them when truly appropriate.

What if I always need to load users including their best friend and favorite band

In that case, by all means, maybe splitting things up like this is pointless. But even then, it could still be beneficial to keep things separate, because it is good class design. Consider the fictional example below. It’s based on some similar code that was actually being used in production.

Note, the attributes can be null

public class User {
private int firstAttribute;
private String secondAttribute;
private Byte thirdAttribute;
private Byte fourthAttribute = 0;
private String fifthAttribute = 0;
private int sixthAttribute;
// etcetera
}

Granted, this may seem like a contrived and trivial example, but it serves to demonstrate how things can get out of hand if good class design is not kept in mind from the outset.

Other considerations

Benefits the database too

Since class design often maps closely to underlying persistence, the proposed solution will have the same added benefit to your persistence layer, whether using relational database schema or objects stored as JSON. For example instead of your tables looking like this:

mysql> select * from user;
| user_id | username | best_friend_user_id | favorite_band |
| xxx | foo | null | null |
| yyy | bar | zzz | null |
| zzz | smurf | yyy | The Smurfs |

They will look like this:

mysql> select * from user;
| user_id | username |
| xxx | foo |
| yyy | bar |
| zzz | smurf |

and

mysql> select * from best_friends;
| first_user | second_user |
| xxx | xxx |
...
mysql> select * from favorite_bands;
| user_id | favorite_band |
| xxx | The Smurfs |
...

Benefits infrastructure and performance too

Often, you’ll find that optional, nullable attributes are fundamentally different in ways that relate to their:

  • nature — they often turn out to constitute relationships between things rather than things themselves, which is true for both bestFriend and favoriteBand.
  • “load profile”— while for example a user object userId and username only need to be loaded very rarely, and changed even more rarely, bestFriend and favoriteBand might change more often and have different caching and time to live. We don’t want to needlessly add traffic to the user table, reload and store the whole user into caches and so on when really we just want to update bestFriend and favoriteBand!

So splitting things up helps with that too, especially when operating at scale as we do at Expedia Group™️.

Learn more about technology at Expedia Group

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store