Prevent the Arrow Anti-Pattern Caused by Excessive Defensive Checks for Null List References

The Arrow Antipattern

You have most likely encountered the Arrow anti-pattern when maintaining legacy code. Perhaps you have even used a poorly designed Application Programming Interface (API) that required writing overly verbose and defensive code, particularly around null object references.  This anti-pattern is very easy to recognize, as it typically consists of several nested conditional checks. These nested conditional blocks quickly cause code to stretch across the screen in the shape of an arrow. The resulting code is difficult to read and understand, with the main logic hidden among the excess code and its function not immediately apparent to the reader.

POJO Example

Consider the following design for illustrative purposes:

import java.util.List;
 
public class Person {
    private List<Outfit> outfits;
    private String name;
 
    public Person() {
 
    }
 
    public Person(List<Outfit> outfits, String name) {
        this.outfits = outfits;
        this.name = name;
    }
 
    public List<Outfit> getOutfits() {
        return outfits;
    }
 
    public void setOutfits(List<Outfit> outfits) {
        this.outfits = outfits;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
}
import java.util.List;
 
public class Outfit {
    private List<Article> articles;
    private String name;
 
    public Outfit() {
 
    }
 
    public Outfit(List<Article> articles, String name) {
        this.articles = articles;
        this.name = name;
    }
 
    public List<Article> getArticles() {
        return articles;
    }
 
    public void setArticles(List<Article> articles) {
        this.articles = articles;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
}
import java.util.List;
 
public class Article {
    private List<Material> materials;
    private String name;
 
    Article() {
    }
 
    public Article(List<Material> materials, String name) {
        this.materials = materials;
        this.name = name;
    }
 
    public List<Material> getMaterials() {
        return materials;
    }
 
    public void setMaterials(List<Material> materials) {
        this.materials = materials;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
}
public class Material {
    private String name;
 
    public Material() {
    }
 
    public Material(String name) {
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
}

This is a fairly typical data structure of Plain Old Java Objects (POJOs) that each consist of a nested collection of other objects. In our example, a Person has a list of Outfit objects, of which each Outfit has a list of articles of clothing (Article), and each Article consists of different materials (Material).

Unfortunately this POJO design is such that the constructors and setter methods allow the internal collection object to be a null reference. This exposes us to the possibility of NullPointerException (NPE) errors at application runtime unless we defensively check for null every time we attempt to dereference the object. The method below is prone to NPE runtime exceptions if any of the List objects are null, and is already showing a bit of an arrow anti-pattern.

public void doSomething(List<Person> people) {
    for (Person person : people) {
        for (Outfit outfit : person.getOutfits()) {
            for (Article article : outfit.getArticles()) {
                for (Material material : article.getMaterials()) {
                    //do something
                }
            }
        }
    }
}

To protect against the runtime errors we might refactor our code to resemble something more closely like this:

public void doSomething(List<Person> people) {
    if (null != people) {
        for (Person person : people) {
            if (null != person) {
                for (Outfit outfit : person.getOutfits()) {
                    if (null != outfit) {
                        for (Article article : outfit.getArticles()) {
                            if (null != article) {
                                for (Material material : article.getMaterials()) {
                                    if (null != material) {
                                        // do something
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

Before adding defensive checking of the List objects for null references we were already well into the Arrow anti-pattern. Upon making the code safe from runtime exceptions caused by NullPointerException, the anti-pattern is now much more amplified.

Helper Utility Method

Fortunately, we can write a helper method using Java Generics and Collections.emptyList() to return an empty and immutable list if the list parameter is a null object reference.

/**
 * Helper method to return an empty list if provided one is null.
 *
 * @param list
 *            the list object to test for null
 * @return the provided list or an empty and immutable one if it was null
 */
private static <T> List<T> immutableEmptyListIfNull(List<T> list) {
    if (list == null) {
        return Collections.emptyList();
    }
    return list;
}

This utility method allows us to refactor our code back to a more tolerable amount of the arrow anti-pattern.

public void doSomething(List<Person> people) {
    for (Person person : immutableEmptyListIfNull(people)) {
        for (Outfit outfit : immutableEmptyListIfNull(person.getOutfits())) {
            for (Article article : immutableEmptyListIfNull(outfit.getArticles())) {
                for (Material material : immutableEmptyListIfNull(article.getMaterials())) {
                    // do something
                }
            }
        }
    }
}

If so inclined, the anti-pattern could potentially be further reduced by extracting the inner third and fourth loops out into their own method, but doing so does not really provide much benefit in this example, as the methods are already quite small.

public void doSomething(List<Person> people) {
    for (Person person : immutableEmptyListIfNull(people)) {
        for (Outfit outfit : immutableEmptyListIfNull(person.getOutfits())) {
            doSomethingElse(outfit);
        }
    }
}
 
public void doSomethingElse(Outfit outfit) {
    for (Article article : immutableEmptyListIfNull(outfit.getArticles())) {
        for (Material material : immutableEmptyListIfNull(article.getMaterials())) {
            // do something else
        }
    }
}

Summary

The Arrow anti-pattern results when a design requires excessive checks for null references on objects. To reduce the severity of the anti-pattern on nested collections, you can make use of a helper method to return Collections.emptyList() in the case of a null object.

Author: Aaron Ferguson

Share This Post On

Submit a Comment

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