Writing Clean Predicates with Java 8
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.