Friday, September 27, 2019

Map or any other option?

I must admit that sometimes we can go too far with using Optionals as silver bullet. Let's take a look at a simple mapper class, defined like this:
class Mapper {
  Optional<Dto> map(Optional<Entity> input) {
    //... implementation
  } 
}
At first passing an Optional instance looks like a good idea - we want to be safe from null values, we express our defensive intent in types. What can go wrong? Let's now take a look at how this mapper may be used.
Entity entity = //...
Dto dto = mapper.map(Optional.of(entity)).get();
Well, typical code involving the mapper is packing the input entity into an instance of Optional, just to get to output value. Even worse could look usage for a collection.
List ents = //...
List<dto> dtos = ents.stream()
  .map(Optional::of)
  .map( x -> mapper.map(x) )
  .map(Optional::get)
  .collect(toList());
Instead of using type system and compiler to our benefit, we lie about our input value - which we actually know that is never null. Not only we do not use the knowledge we already have, but put extra burden of reading through the wrapping on reader's eyes. I do not mention creating extra instances of Optional, and making common use of calling .get() - which is not best practice. I do not want to get familiar to that.
What I also do not like in the code is forcing the user of our mapper to provide object wrapped into the very implementation of Optional. Actually mapper should not care which implementation of Optional I use. There are more than the standard one, and they may have some properties that are more useful for me in the context of my implementation. Last thing, we may observe here is that the API actually mimics what the Optionals are expected to provide - the map method itself.
What we need is just:
Dto map(Entity in) {
//....
}
That simple.
Implementation of very mapping is what should be provided directly by mapper, without requirement for a wrapped value. We should require that value will be non-null. Optionality should be handled in client code somewhere else, we will not tolerate null values anyhow, right? Such mapper can be also easily used when applied to streams or collections. We save machine's memory from wrapping non-null values into Optionals and our precious eyes from reading the code that would cause the latter.