Exemple Java Predicates – Avoid nested if statements

  • Modifié il y à2 semaines 
  • Temps de lecture :5Minutes

Context

A Predicate can be used to simplify complex nested if statements in Java. It embraces functional programming, making your code more readable and maintainable by encouraging you to define conditions with meaningful names rather than relying on a sequence of technical expressions.

A good practice is to use one predicate for one condition, and to combine them with other predicates to achieve your business goal.

At first, it may seem that writing multiple small predicates takes more time than writing a single big one. But let me share a real enterprise experience. The developer works on multiple Java components. Each time the client asks for a feature or a bugfix, a small portion of code is added to the codebase. Over time, the developer notices he’s rewriting code that already exists, with just a small condition that differs. So the the code is duplicated by 80% and only 20% is the actual feature requested.

He can’t reuse the existing code because verifying that it does exactly what’s needed would take too much time, time not allocated in the feature estimation. Moreover, he is young and doesn’t like the old-style syntax of the current code.

The estimation didn’t account for using existing code or doing refactoring. So he decides to write the feature from scratch in a better, more modern way.

Even sonar or IA tools, at the time of writing this article, aren’t capable of detecting this kind of duplication. And so on, generation after generation, the same mistakes are repeated, due to a lack of discipline, and good practices.

Senior developers keep saying that refactoring is necessary, but asking the client for a dedicated ticket is risky, because refactoring costs time, and time costs money. The client doesn’t see immediate value in the refactoring, so he’s less inclined to approve that extra cost, just to improve developer satisfaction 😅.

Bad Practices Example: Without Predicate Chaining 😪

image - Exemple Java Predicates - Avoid nested if statements
public boolean isValidPerson(Person person, String nickname, String nicknamePrefix, String streetAddress) {
    if (person != null) {
        if (person.getNicknames() != null) {
            boolean hasExactNickname = person.getNicknames().stream()
                .anyMatch(n -> n.equalsIgnoreCase(nickname));
            boolean hasNicknamePrefix = person.getNicknames().stream()
                .anyMatch(n -> n.startsWith(nicknamePrefix));

            if (hasExactNickname && hasNicknamePrefix) {
                Address address = person.getAddress();
                if (address != null && streetAddress.equalsIgnoreCase(address.getStreet())) {
                    return true;
                }
            }
        }
    }
    return false;
}

Example Java Predicates and Functions

Predicates :

public class AddressPredicates {

    public static Predicate<Address> hasStreetCode(@NonNull String streetCode) {
        return address -> nonNullAddress().and(equalsIgnoreCaseStreetCode(streetCode)).test(address);
    }


    public static Predicate<Address> hasStreet(@NonNull String street) {
        return address -> nonNullAddress().and(equalsIgnoreCaseStreet(street)).test(address);
    }


    public static Predicate<Address> equalsIgnoreCaseStreet(@NonNull String street) {
        return address -> street.equalsIgnoreCase(address.getStreet());
    }

    public static Predicate<Address> equalsIgnoreCaseStreetCode(@NonNull String streetCode) {
        return address -> streetCode.equalsIgnoreCase(address.getStreetCode());
    }

    public static Predicate<Address> nonNullAddress() {
        return Objects::nonNull;
    }
}
public class PersonPredicates {

    public static Predicate<Person> hasNicknameAndHasNicknamePrefixAndAsStreetAddress(@NonNull String nickname,
                                                                                      @NonNull String nicknamePrefix,
                                                                                      @NonNull String streetAddress) {
        
        return person -> hasNonNullPersonAndHasNickname(nickname)
                .and(hasNonNullPersonAndHasNicknameStartWith(nicknamePrefix))
                .and(hasNonNullPersonAndHasStreetAddress(streetAddress))
                .test(person);
    }

    public static Predicate<Person> hasNonNullPersonAndHasNicknameStartWith(@NonNull String nicknamePrefix) {
        return person -> nonNullPerson().and(hasNicknameStartWith(nicknamePrefix)).test(person);
    }

    public static Predicate<Person> hasNonNullPersonAndHasNickname(@NonNull String nickname) {
        return person -> nonNullPerson().and(equalsIgnoreCaseNickname(nickname)).test(person);
    }

    public static Predicate<Person> hasNonNullPersonAndHasStreetAddress(@NonNull String streetAddress) {
        return person -> nonNullPerson().and(hasStreetAddress(streetAddress)).test(person);
    }

    public static Predicate<Person> hasNonNullPersonAndHasStreetCodeAddress(@NonNull String streetCodeAddress) {
        return person -> nonNullPerson().and(hasStreetCodeAddress(streetCodeAddress)).test(person);
    }

    public static Predicate<Person> equalsIgnoreCaseNickname(@NonNull String nickname) {
        return person -> person.getNicknames().stream().anyMatch(equalsIgnoreCaseString(nickname));
    }

    public static Predicate<Person> hasNicknameStartWith(@NonNull String nicknamePrefix) {
        return person -> person.getNicknames().stream().anyMatch(startWithString(nicknamePrefix));
    }


    public static Predicate<Person> hasStreetAddress(@NonNull String streetAddress) {
        return person -> AddressPredicates.hasStreet(streetAddress).test(person.getAddress());
    }

    public static Predicate<Person> hasStreetCodeAddress(@NonNull String streetCodeAddress) {
        return person -> AddressPredicates.hasStreetCode(streetCodeAddress).test(person.getAddress());
    }

    public static Predicate<Person> nonNullPerson() {
        return Objects::nonNull;
    }
}
public class StringPredicates {

    public static Predicate<String> equalsIgnoreCaseString(@NonNull String value) {
        return value::equalsIgnoreCase;
    }

    public static Predicate<String> startWithString(@NonNull String value) {
        return str -> str.startsWith(value);
    }
}

DTO :

public class Address {
    private String streetCode;
    private String street;
}
public class Person {
    private String firstname;
    private String lastname;
    private Address address;
    private List<String> nicknames;
}

Unit Test :

public class AddressPredicatesUnitTest {

    @Test
    public void AddressPredicates_hasStreet_returnTrue(){
        // GIVEN
        var address = Address.builder().street("Ferdinand").build();
        // WHEN
        var result = AddressPredicates.hasStreet("Ferdinand").test(address);
        // THEN
        assertTrue(result);
    }
    @Test
    public void AddressPredicates_hasStreetCode_returnTrue(){
        // GIVEN
        var address = Address.builder().streetCode("22").build();
        // WHEN
        var result = AddressPredicates.hasStreetCode("22").test(address);
        // THEN
        assertTrue(result);
    }
}
public class PersonPredicatesUnitTest {

    @Test
    public void PersonPredicates_hasNicknameAndHasNicknamePrefixAndAsStreetAddress_returnTrue() {
        // GIVEN
        var nicknames = List.of("el patron", "pablo");
        var address = Address.builder().street("forest").build();
        var person = Person.builder().nicknames(nicknames).address(address).build();
        // WHEN
        var result = PersonPredicates.hasNicknameAndHasNicknamePrefixAndAsStreetAddress("pablo", "el", "forest").test(person);
        // THEN
        assertTrue(result);
    }

    @Test
    public void PersonPredicates_hasNonNullPersonAndHasNicknameStartWith_returnTrue() {
        // GIVEN
        var person = Person.builder().nicknames(List.of("el patron", "pablo1")).build();
        // WHEN
        var result = PersonPredicates.hasNonNullPersonAndHasNicknameStartWith("el").test(person);
        // THEN
        assertTrue(result);
    }

    @Test
    public void PersonPredicates_equalsIgnoreCaseNickname_returnTrue() {
        // GIVEN
        var person = Person.builder().nicknames(List.of("el patron", "pablo1")).build();
        // WHEN
        var result = PersonPredicates.equalsIgnoreCaseNickname("pablo1").test(person);
        // THEN
        assertTrue(result);
    }

    @Test
    public void PersonPredicates_hasNonNullPersonAndHasNickname_returnTrue() {
        // GIVEN
        var person = Person.builder().nicknames(List.of("el patron", "pablo")).build();
        // WHEN
        var result = PersonPredicates.hasNonNullPersonAndHasNickname("pablo").test(person);
        // THEN
        assertTrue(result);
    }

    @Test
    public void PersonPredicates_hasNonNullPersonAndHasStreetCodeAddress_returnTrue() {
        // GIVEN
        var address = Address.builder().streetCode("222").build();
        var person = Person.builder().address(address).build();
        // WHEN
        var result = PersonPredicates.hasNonNullPersonAndHasStreetCodeAddress("222").test(person);
        // THEN
        assertTrue(result);
    }

    @Test
    public void PersonPredicates_hasNonNullPersonAndHasStreetAddress_returnTrue() {
        // GIVEN
        var address = Address.builder().street("Ferdinand2").build();
        var person = Person.builder().address(address).build();
        // WHEN
        var result = PersonPredicates.hasNonNullPersonAndHasStreetAddress("Ferdinand2").test(person);
        // THEN
        assertTrue(result);
    }


    @Test
    public void PersonPredicates_hasStreetAddress_returnTrue() {
        // GIVEN
        var address = Address.builder().street("Ferdinand").build();
        var person = Person.builder().address(address).build();
        // WHEN
        var result = PersonPredicates.hasStreetAddress("Ferdinand").test(person);
        // THEN
        assertTrue(result);
    }

    @Test
    public void PersonPredicates_hasStreetCodeAddress_returnTrue() {
        // GIVEN
        var address = Address.builder().streetCode("45").build();
        var person = Person.builder().address(address).build();
        // WHEN
        var result = PersonPredicates.hasStreetCodeAddress("45").test(person);
        // THEN
        assertTrue(result);
    }
}

Ressources

Github sources :

github.com/Kevded/demo-java-predicates

Laisser un commentaire