MathJax

Thursday, March 30, 2017

Liskov Substitution Principle: Write This, Not That!

Let's start with
interface Fruit {}

class Apple implements Fruit {}
class Orange implements Fruit {}
Now we want to eat the fruit. But we can only eat Apples.
void eat(Fruit fruit) {
    if (!(fruit instanceof Apple)) {
        throw new IllegalArgumentException("I only like Apples!");
    }
    // Om nom nom....
}
So that's one way to only eat apples, but look! There's an if block, an instanceof check, and an unchecked exception. Plus now we need unit tests to make sure we reject these unsuitable Oranges. Bleh! Compare that to another way to eat only Apples:
void eat(Apple fruit) {
    // Om nom nom....
}
Now the type checker won't let us eat() anything but Apples, which is what we were trying to do anyway. The problem with the first code is that we're lying about which types of input are acceptable in our method signature, and therefore we have to play catch-up with more complicated code in the method definition. We can and should choose to eat Apples if it's true that those are the only Fruit that does it for us. Less to write. Less to test. Fewer opportunities for mischief.

Friday, March 24, 2017

YAGNI, meet YDNIA

Software crafts(wo)men avoid overbuilding for the present need. Usually, the intrinsic complexity of the problem at hand is exciting enough. Conjecture about how a client may want to generalize in the future, unless the client has that exact need in the present, only serves to clutter the built solution. The mantra is "You Aren't Going to Need It" (YAGNI).

Working software systems evolve over time. Sometimes the need for a feature disappears entirely. Maybe some code was in place to support a launch or a migration, and that work is complete. This is where we meet YAGNI's twin, YDNIA: You Don't Need It Anymore.

Clinging to the past can be as limiting as preparing for a future that will never be. Make your software live in the present.

Thursday, March 16, 2017

Java 8 Stream: stream().sorted.collect() throws NullPointerException

Here's what I ran into:

return map.keySet().stream()
    .sorted()
    .collect(Collectors.joining(":"));

was throwing NullPointerExceptions.

Why?

Turns out the original map actually had a value defined for the null key, as in
"a" -> "b"
null -> "c"

So the obvious first place to look is why on earth that silly null -> "c" mapping exists.

Another option, if this is safe for your use case, is to filter with java.util.Objects::nonNull:

return map.keySet().stream()
    .filter(Objects::nonNull)
    .sorted()
    .collect(Collectors.joining(":"));