Java

Lesson 07

Streams vs readable loops

Use streams for clear transformations and loops when branching, validation, or naming intermediate steps makes the review easier.

Good Code

src/main/java/dev/review/orders/DiscountCalculator.java
public Money discountFor(List<OrderLine> lines) {
    Money discount = Money.zero();

    for (OrderLine line : lines) {
        if (!line.isDiscountable()) {
            continue;
        }

        Money lineDiscount = policy.discountFor(line);
        discount = discount.plus(lineDiscount);
    }

    return discount;
}

Bad Code

src/main/java/DiscountCalculator.java
public Money discountFor(List<OrderLine> lines) {
    AtomicReference<Money> discount = new AtomicReference<>(Money.zero());

    lines.stream()
        .filter(line -> line.isDiscountable())
        .peek(line -> audit.log(line.id()))
        .map(line -> policy.discountFor(line))
        .forEach(value -> discount.set(discount.get().plus(value)));

    return discount.get();
}

Review Notes

What to review

Good Code

The good version uses a loop because the operation has branching and a named accumulation step. Each review question has a place to attach.

Bad Code

The bad version hides mutation inside a stream pipeline and uses peek for side effects. The stream looks functional, but it is harder to reason about.

Takeaways

  • The best Java iteration shape is the one that makes intent and failure points easiest to review.