Over a million developers have joined DZone.

Writing Clean Predicates with Java 8

· Java Zone

Navigate the Maze of the End-User Experience and pick up this APM Essential guide, brought to you in partnership with CA Technologies

IN-LINE PREDICATES CAN CREATE A MAINTENANCE NIGHTMARE.

Writing in-line lambda expressions and using the stream interfaces to perform common operations on collections can be awesome. Assume the following example:

List<Person> getAdultMales (List<Person> persons) {
  return persons.stream().filter(p -> 
    p.getAge() > ADULT &&
    P.getSex() == SexEnum.MALE 
  ).collect(Collectors.<Person>toList());
}

That’s fun! But things like this also lead to software that is costly to maintain. At least in an enterprise application, where most of your code handles business logic, your development team will grow the tenancy to write the same similar set of predicate rules again and again. That is not what you want on your project.  It breaks three important principles for growing maintainable and stable enterprise applications:

  • DRY (don’t repeat yourself): writing code more than once is not a good fit for a lazy developer ;)It also makes your software more difficult to maintain because it becomes harder to make your business logic consistent
  • Readability: following clean-code best practices, 80% of writing code is reading the code that already exists. Having complicated lambda expressions is still a bit hard to read compared to a simple one-line statement.
  • Testability: your business logic needs to be well-tested. It is adviced to unit-test your complex predicates. And that is just much easier to do when you separate your business predicate from your operational code.

And from a personal point of view… that method still contains too much boilerplate code…

IMPORTS TO THE RESCUE!

Fortunately, we have a very good suggestion in the world of unit testing on how we could improve on this.

Imagine the following example:

import static somepackage.PersonPredicate;
...
List<Person> getAdultMales (List<Person> persons) {
  return persons.stream().filter(
    isAdultMale()
  ).collect(Collectors.<Person>toList());
}

What we did here was:

  • create a PersonPredicate class
  • define a “factory” method that creates the lambda predicate for us
  • statically import the factory method into our old class

This is how such a predicate class could look like, located next to your Person domain entity:

public PersonPredicate {
  public static Predicate<Person> isAdultMale() {
    return p -> 
      p.getAge() > ADULT &&
      p.getSex() == SexEnum.MALE; 
 }
}

Wait… why don’t we just create a “isMaleAdult” boolean function on the Person class itself like we would do in domain driven development? I agreed, that is also an option… but as time goes on and your software project becomes bigger and loaded with functionality and data… you will again break your clean code principles:

  • the class becomes bloated with all kind of function and conditions
  • your class and tests become huge, more difficult to handle and change (*)

(*) and yes… even if you do your best to separate your concerns and use composition patterns

ADDING SOME DEFAULTS…

Working with domain objects, we can imagine that some operations (such as filter) are often executed on domain entities. Taking that into account, it would make sense to let our entities implement some interface that offers us some default methods.

For example:

public interface DomainOperations<T> {
  default List<T> filter(Predicate<T> predicate) {
    return persons.stream().filter( predicate )
      .collect(Collectors.<Person>toList());
 }
}

When our Person entity implements this interface, we can clean-up our code even more:

List<Person> getAdultMales (List<Person> persons) {
  return persons.filter( isAdultMale() );
}

And there we go…

CONCLUSION

Moving your predicates to a Predicate helper class offers some good advantages in the long run:

  • Predicate classes are easy to test and change
  • Your domain objects remain clean and focussed on representing your domain, not your business logic
  • You optimize the re-usability of your code and, in the end, reduce your maintenance
  • You seperate your business from operational concerns

REFERENCES

Clean Code: A Handbook of Agile Software Craftsmanship [Robert C. Martin]

Practical Unit Testing with JUnit and Mockito [Tomek Kaczanowski]

State of the Collections [http://cr.openjdk.java.net/~briangoetz/lambda/collections-overview.html]

NOTES

The code above is served as an example to illustrate the principles I wanted to discuss. However, I did not proof-run this code yet (it’s still on my todo list). Some modifications may be needed for your project.

Thrive in the application economy with an APM model that is strategic. Be E.P.I.C. with CA APM.  Brought to you in partnership with CA Technologies.

Topics:

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}