A simple utility for writing guards using Hamcrest Matchers.
A lot of Java guards look like these:
if (someValue == null) { throw new IllegalArgumentException("same value was null"); } if (StringUtils.isEmpty(someString)) { throw new IllegalArgumentException("some string was empty") } if (someStringArray == null || someStringArray.length != 5) { throw new IllegalStateException("some string array was less than 5 elements`"); }
Hamcrest Guards lets you achive the very same functionality with these:
requireThat(someValue, is(notNullValue())); requireThat(someString, is(not(emptyString()))); requireThat(someStringArray, hasArraySize(5), elseThrow(IllegalStateException.class));
There are several advantages to this approach:
- The condition guarded against and the error message are specified once in the same code (e.g. is(notNullValue())). In the first approach the two are separate and may diverge over time and cause a lot of confusion (e.g. sameStringArray length check is changed to 4 but error message remains 5).
- Consistent and informative error messages assist in diagnosing problems faster.
- Ease of writing guards encourages the developer to actually use them.
- Declarative guards are more readable than those performed procedurally.
- Smaller amounts of readable code means fewer places to make mistakes and a lower chance of making them.
If you haven’t heard of Hamcrest Matchers before but recognize them from somewhere it’s probably because they’ve been packaged with JUnit since version 4.0:
assertThat(newArrayList("A", "B"), hasSize(2));
// Exception detail messages are very informative. requireThat("red data", is(matchedBy("(gold|green) data"))); // -> java.lang.IllegalArgumentException: // Expected value to be a string matching <(gold|green) data> but was "red data". // You can provide value names that are included in the exception detail message. requireThat(null, "essential widget" is(notNullValue())); // -> java.lang.IllegalArgumentException: Expected essential widget to not be null but was null. // You can specify any RuntimeException to be thrown; by default IllegalArgumentException is used. requireThat("", "employee first name", is(not(emptyString())), elseThrow(IllegalStateException.class)); // -> java.lang.IllegalStateException: Expected employee first name to not be a blank string but was "".
Download the source via the “Download Source” near the top of the page. Hamcrest Guards is developed using Maven, but you’re free to use it however you wish. Since much of the syntactic sugar is made possible via static imports you’ll probably want to add com.hamcrest.Matchers, com.walkertexascoder.hamcrestguards.Guard and com.walkertexascoder.hamcrestguards.matchers.HamcrestMatchers to whatever your IDE requires to locate them automatically(e.g. Contest Assist – Favorites in Eclipse).
There are a lot of Hamcrest Matchers already at your disposal that deal with collection contents, object properties, XPaths, and so on. For an overview see org.hamcrest.Matchers.
It is also very easy to add domain-specific matchers that can add a lot of expressibility and reduce the LOC of your code base. Look in the com.walkertexascoder.hamcrestguards.matchers package for examples.
- A lot of Hamcrest Matcher’s descriptions are really awkward when used in this context, so it’d be nice to do some work on them. Instead of attempting to duplicate the classes I’m taking the approach of modifying the description String after it is generated.
- It’d be very nice to excise the part of the stack trace after the point where the guard exists, it’s just noise the client won’t care about.
- Add an elegant way for arbitrary detail to be added to the exception detail message.