Clean Code and Java 8

I write more and more Java 8 code. But within the last week I wondered if streams and lambdas may lead to code that nobody understands (like generics back in the days). But I’ll give you a few examples and my opinion about it.

Messy Code because of Streams and Lambdas?

I wrote some code like this:

items.stream()
    .filter(item -> mapper.hasMappingFor(item))
    .map(item -> mapper.map(item))
    .findFirst();

Wouldn’t it be more readable for other developers seeing the code for the first time, if I write it like that?

for(Item item : items) {
    if (mapper.hasMappingFor(item)) return mapper.map(item);
}

Or does it just feel that way because I see these for-loops every day? In general, I really like fluent APIs and for me the stream-solution seems to be very elegant. Are developers complaining about Java 8 code because they are just old-fashioned? Or is it really harder to understand and maybe also harder to maintain?

Elegance of Streams and Lambdas

Last week I attend at one of our internal coding events and we worked on the yahtzee kata. I had to leave early but because I had a lot of ideas, I worked on that kata on the train, focusing on Java 8 features and check if the code feels clean. The problem I was solving: get all possible yahtzee categories for a given roll. I solved that with an enumeration of categories, each defining an predicated against a given roll is tested.

public Set<Category> possibleCategories() {
    return Stream.of(Category.values())
        .filter(category -> category.isPossibleCategoryFor(dice))
        .collect(Collectors.toSet());
}

And here the definition of the single categories and the check if a dice matches the category:

enum Category {
    ONES(hasAtLeastOne(ONE)),
    TWOS(hasAtLeastOne(TWO)),
    THREES(hasAtLeastOne(THREE)),
    FOURS(hasAtLeastOne(FOUR)),
    FIVES(hasAtLeastOne(FIVE)),
    SIXES(hasAtLeastOne(SIX)),
    THREE_OF_A_KIND(atLeastOneDieOccursAtLeastXTimes(3)),
    FOUR_OF_A_KIND(atLeastOneDieOccursAtLeastXTimes(4)),
    FULL_HOUSE(atLeastOneDieOccursAtLeastXTimes(3)
                    .and(anotherDieOccursAtLeastYTimes(2))),
    SMALL_STRAIGHT(dicesInARow(4)),
    LARGE_STRAIGHT(dicesInARow(5)),
    YAHTZEE(allDiceAreEqual()),
    CHANCE(matchesForEveryDice());

    private Predicate<List<Dice>> rule;

    private Category(Predicate<List<Dice>> rule) {
        this.rule = rule;
    }
    
    public boolean isPossibleCategoryFor(Dice dice) {
        return rule.test(dice);
    }
    ...
}

Of course you could implement something like this with earlier Java versions as well. Only the method implementations are different.

Here I struggled again, when I wrote the method creating possible straights:

private static Set<Set<Dice>> possibleStraights(int numberOfDice) {
    return IntStream.range(0, Dice.values().length - numberOfDice)
        .mapToObj(startDice -> IntStream.range(startDice, startDice + numberOfDice)
            .mapToObj(die -> Dice.values()[die]).collect(Collectors.toSet()))
        .collect(Collectors.toSet());
}

I fear everyone of you thought “WTF?” at the first sight. Although I chose an self-explanatory name (at least I hope so), it’s not clear, what the code does. You may hope, that it really creates a set of possible straights. But after I extract a method it seems more readable again.

private static Set<Set<Dice>> possibleStraights(int numberOfDice) {
    return IntStream.range(0, Dice.values().length - numberOfDice)
        .mapToObj(startDice -> createStraightStartingAt(startDice, numberOfDice))
        .collect(Collectors.toSet());
}

private static Set<Dice> createStraightStartingAt(int startDice, int numberOfDice) {
    return IntStream.range(startDice, startDice + numberOfDice)
        .mapToObj(die -> Dice.values()[die]).collect(Collectors.toSet());
}

You can find the code on GitHub.

Conclusion

I really think my feeling that the Java 8 code is less readable and maintainable, is just because these Java syntax is quite new. (And maybe I don’t trust other developers, that they are familiar with these new features.)

I am quite sure, that the code is as easy to read and maintain as the “old-fashioned” java style. And in a few month it will be completely common. But we should always think twice, if streams and lambdas are the best possible solution for our problem. Just because we have a hammer, not everything is a nail.

But what do you think? Do you think the code is less cleaner because of the Java 8 syntax?

One Response to “Clean Code and Java 8”

  1. Hi, Nina! Thank you for this interesting post. Actually I love the resulted code. I attended a lecture once, where the lecturer talked about similarities between writing code and writing poetry. The point was that in kinds of writing we tend to write the minimum to express our intention. I think your code does it exactly.

Leave a Reply

Your email address will not be published. Required fields are marked *