Null reference is commonly used to express the absence of value. Most programming languages allow the use of them without explicit syntax, and that cause problems. Let's take a look at the following java method:
User findUser(String name);
What happens when a user with the given name can't be found? Returning null represents the concept of an absent value in probably the simplest way, but there is a specific risk: the interface doesn't reveal the possibility of a null reference. Why is that a big deal? Null references need handling, otherwise NullPointerException may occur at runtime, a very common error.
Generally not all the values are nullable in a system. Indicating where exactly handling is useful to reduce both risks and unnecessary effort. Meta code or documentation helps, but it's not ideal: it's possible to miss and lacks compile safety.
An interesting alternative to address the issue is using the type system: the option type. Java 8 has introduced Optional for instance. "A container object which may or may not contain a non-null value."
Optional<User> findUser(String name);This interface communicates clearly that a user may not be present in the result and the client is forced to take that into account:
// findUser("jonhdoe").address(); compile error!Handling the absent value:
Optional<User> user = findUser("johndoe"); if(user.isPresent()) { user.get().address(); }Note that this example is intentionally kept somewhat similar to how null references are often handled. A more idiomatic way of doing the same:
findUser("johndoe").ifPresent(user -> user.address());
It's interesting to consider the effects of the pattern in the wider context of a system. With consistent use of Optional, it is possible to establish a powerful convention of avoiding the use of null references altogether. It transforms an interface from:
interface User { String name(); Address address(); BankAccount account(); }to:
interface User { String name(); Address address(); Optional<BankAccount> account(); }Given the convention the client can safely assume a user may not have a bank account and that it always has a name and address. Such practice facilitates changes also: if address becomes optional at some point in the future all client code will be forced to conform.
The Optional type is a nice fit for functional programming, it's api simplifies implementation in many scenarios:
Optional<String> foo = Optional.ofNullable(some3rdPartyNullableVal); String fooWithDefauult = foo.orElse("No value.. take this default one."); String guaranteedFoo = foo.orElseThrow(() -> new RuntimeException("I really can't work without a value though")); if(foo.equals(otherOptional)) { ... } Optional<Integer> fooInt = foo.map(Integer::valueOf);
Conclusion
Avoiding implicit nulls can have nice effects on a codebase, making it a much safer place to be. Disadvantages of overusing Optional? Optional is a container object, so the heap consumption needs to be considered in extreme cases. 3rd party libraries may also force the use of some null references.
An example of the use of optional: the java 8's Stream API
No comments:
Post a Comment