This is a small example for anyone writing maven plugins and feels the need for increased testability at the unit test level.

On tools used when compiling artifacts

Artifacts can be runnable programs, documentation, libraries etc. There are many things that fall into this category; compilers, project management, analysis etc. These tools tend to be most conservative when it comes to compatibility with other tools. These requirements have consequences on the behaviors they exhibit and what patterns they follow. To emphasize productivity is is important to manage complexity and enable reuse. The paradigm convention over configuration combats complexity and makes reuse easier.

Error handling in maven plugins

Plugins extend the AbstractMojo and implements the execute method. Whenever a plugin wants to signal a ‘failed build’ the contract states to throw a MojoFailureException. When an unexpected problem occurs the plugin is required to throw a MojoExecutionException. Guide to plugin development.

Maven as a project management tool deals to a large extent with side effects. Essentially maven takes a set of instructions and then produces a side effect. Checked exceptions are a good fit in this domain.

Functional principles

In functional programming pure functions are a key part. They have a plethora of advantages on several levels. One of them being testability. Say a caller wants to verify a set of inputs. A function that always produces a return value is preferable over a function that does not. A method that throws a checked exception may return a value, or not. Imagine you ask someone a question that you feel is important. Then imagine someone answering you, smiling, “Yes. Or no.”. An efficient way to annoy your co workers.

Now for the example

It will not be possible to write pure functions here. Instead we can use a property from the pure functions. The property to always produce a return value. This value can be propagated or checked immediately. Unit tests will verify it immediately. When propagated it will be used to create a checked exception. This is what we need to communicate with the layer above us.

The usual pattern:

void methodThatWillBeCalledByExecute() throws MojoExecutionException {
    // Do work and throw on error
}

With the above method an error will be propagated and the build will fail. All good but hard to test in a unit test.

Using Google guava or Java 8 it is possible to use the option monad or the option type. Below the Optional from Java 8 is used:

Optional<IOException> methodThatWillBeCalledByExecute() {
    try (file.createNewFile()) {
      // write
    } catch (IOException e) {
      return Optional.of(e);
    }
    return Optional.absent();
}

The above method is now a little easier to write a small test for. In the calling code, in this example the execute method, we can now use

public void execute() throws MojoExecutionException {
  Optional<IOException> result = methodThatWillBeCalledByExecute();
    if (result.isPresent()) {
      throw new MojoExecutionException(result.get());
    }
}

Note that the use of isPresent is considered a code smell and in most cases getOrElse is prefered. Compare:

if (!optionVar.isPresent()) {
  return DEFAULT_CONSTANT;
}
return optionVar.get();

with the more concise

optionVar.getOrElse(DEFAULT_CONSTANT);

In the first alternative you risk making a mistake in the conditional. There are also more lines which hurts readability.



Published

29 November 2015

Category

programming

Tags