Embracing the Messiness in Search of Epic Solutions

Better Preconditions: v0.1.0

Posted

in

DEPENDENCY

<dependency>
  <groupid>com.github.choonchernlim</groupid>
  <artifactid>better-preconditions</artifactid>
  <version>0.1.0</version>
</dependency>

Introduction

The goal of Better Preconditions is to provide a set of Java APIs that allows developers to create succinct, yet readable and testable preconditions.

Why Write Preconditions?

Let’s assume we have the following code:-

public void save(final String name, final LocalDate birthDate) {
    dao.save(new Entity(name.toUpperCase(), birthDate));
}

Although this example is simple and trivial, every developer that looks at this code will interpret this API differently. For example:-

  • Can name be blank?
  • What if we pass in a null value for name?
  • Can birth date be null?
  • Can birth date be after today’s date?

The truth of the matter is we cannot create an API that handles every possible scenario. Otherwise, we will never get our products out the door.

Thus, it is better to safeguard our API with a set of preconditions to ensure our fellow developers (or even you) know what this API needs before it performs the real work. Further, it provides a living documentation that would never go stale. Here’s an example of what the preconditions might look like:-

public void save(final String name, final LocalDate birthDate) {
    // precondition 1: name cannot be blank
    // precondition 2: birth date cannot be null
    // precondition 3: birth date cannot be after today's date

    dao.save(new Entity(name.toUpperCase(), birthDate));
}

What’s wrong with Guava Preconditions?

The biggest advantage of using Guava Preconditions is its flexibility. The biggest disadvantage of it is also its flexibility. Here’s an example written with Guava Preconditions:-

public void save(final String name, final LocalDate birthDate) {
    checkArgument(!nullToEmpty(name).trim().isEmpty(), "Name cannot be blank");
    checkNotNull(birthDate, "Birth date cannot be null");
    checkArgument(!birthDate.isAfter(LocalDate.now()), "Birth date cannot be after today's date");

    dao.save(new Entity(name.toUpperCase(), birthDate));
}

A couple of observations:-

  • Too verbose, which makes the code very messy. If we start inserting the raw values into the error messages, it becomes even messier.
  • We use checkArgument(..) and checkNotNull(..) most of the time. When an exception is thrown, we either get IllegalArgumentException or NullPointerException. This makes the API very confusing to unit test if there are many preconditions.

Introducing the power of Better Preconditions

Let’s rewrite the example above with Better Preconditions:-

public void save(final String name, final LocalDate birthDate) {
    expect(name, "Name")
            .not().toBeBlank()
            .check();

    expect(birthDate, "Birth Date")
            .not().toBeNull()
            .not().toBeAfter(LocalDate.now(), "Today's Date")
            .check();

    dao.save(new Entity(name.toUpperCase(), birthDate));
}

A couple of observations:-

  • Short and succinct.
  • Each thrown exception provides very useful error message for debugging purpose. For example, if birth date is after today’s date, the error message would be Birth Date [ 2015-02-01 ] must not be after Today’s Date [ 2015-01-01 ]
  • When one of the preconditions fails, a specific exception is thrown. For example:-
    • blank name throws StringBlankPreconditionExceptionnull birth date throws ObjectNullPreconditionExceptionbirth date after today’s date throws JodaTimeAfterPreconditionException

    This makes it simpler to unit test because we can easily catch specific exception without any doubts.

Conclusion

Lastly, play around with it. This post barely scratches the surface of what Better Preconditions can do for you. If it works for you, great. If it doesn’t work for you and you are still interested to use it, create an issue at my GitHub page so that I can fix it. Due to my current real world workload, I’m using the 80/20 rule… the provided APIs should solve 80% of the problem.

To learn more about Better Preconditions, visit https://github.com/choonchernlim/better-preconditions

Comments

Leave a Reply