Of Validating Inputs in Java and Designing for Extension

Posted by & filed under , .

iStock_000008980370XSmallI read recently an article on DZone titled “Defensive Programming: Why You Shouldn’t Check Input Parameters for Validity” (by the way, you can find the article here: https://dzone.com/articles/defensive-programming-via-validating-decorators) and it made me think — and write this post 🙂

For those of you who don’t have the patience to read the full article, the author is talking about “layering” pretty much validation on top of business logic: you start with a class DefaultReport which implements the business logic — in this case, exporting a file. The implementation of it is bare-bones (for no better term) and lacks any data validation, such that given a null parameter will throw NullPointerException, given a wrong file will throw all sorts of other exceptions. Then through aggregation, an instance of this class is wrapped inside another class which validates first the input (e.g. check for nulls etc) and if the validation passes then it delegates to the original DefaultReport class. Both DefaultReport and the wrapper classes both implement the same interface Report which defines the contract: exporting of a file. Of course, this approach allows for infinite customization of the validation as one can wrap the DefaultReport in a class which checks for null which then gets wrapped in a class which checks if the file exists (so we don’t overwrite existing files) which then checks for instance that the destination folder exists and so on. Each one of these wrapper classes implements the Report interface and their implementation of export() calls first their validation and then delegates the call to the wrapped instance’s export() method (which in turn does validation then delegates etc.)

That is an elegant approach and I agree with separating the checking and validation of the input from the actual code. However, the personal annoyance I have with this approach is the fact that each time a new validation is introduced you have to write a lot of boilerplate code: implement the Report interface, implement the export() method and have it such that it first calls your validation then it delegates to the aggregated instance’s export() and finally implement your method to perform the validation.

Instead, I prefer a different approach which uses extension rather than aggregation. To exemplify, I’ve put together this repo on GitHub: https://github.com/liviutudor/CheckInputParameters

We start with our base class Report — this offers the implementation of our export() all bare-bones with no data validation / parameter checking built in. However, it does offer a hook (validateFile) which gets executed before our export logic.

public class Report {
    public final void export(File file) throws ReportException {
        validate(file);
        System.out.println("File " + file.getAbsolutePath() + " exported.");
    }
 
    public void validate(File f) throws ReportException {
        // default implementation left blank
    }
}

The default implementation in the Report class is left blank intentionally so it doesn’t do anything in the base class. However, subclasses can override this method, perform validation and throw an instance of ReportException. And to make sure that no one messes up with our internals, we make the export method final — this way the subclasses can only operate at the validate() “hook” level.

Let’s consider the first test we want to perform: we want to ensure the File instance passed in is not null. So we implement ValidateNotNullReport and we override the validateFile to check for null — and in case of a null instance throw ReportNullFileException, which is a subclass of ReportException. As you can see, the implementation is very simple and we only write the bit concerning validation:

public class ValidateNonNullReport extends Report {
    @Override
    public void validate(File f) throws ReportException {
        if (f == null) throw new ReportNullFileException();
    }
}

Now this is all good and from now on if we want to check for null we simply use an instance of ValidateNonNullReport instead of Report. But what do we do if we want to check that our file is not null AND also meets another criteria? Let’s say that we only want to accept zip files for instance.

This is where “design for extension” comes in: rather than simply implementing the check we also add another “hook” at the end of validate — checkAfterNull. Same as before this allows subclasses to implement their own validation and throw an instance of ReportException. Same as before we make our validate() method final so subclasses only get access to override our hook method:

public class ValidateNonNullReport extends Report {
    @Override
    public final void validate(File f) throws ReportException {
        if (f == null) throw new ReportNullFileException();
        checkAfterNull(f);
    }
 
    public void checkAfterNull(File f) throws ReportException {
    }
}

Now having changed our ValidateNonNullReport class all we have to do is have another class extend it and implement the check for zip files:

public class OnlyZipFilesReport extends ValidateNonNullReport {
    @Override
    public final void checkAfterNull(File f) throws ReportException {
        if (!f.getName().endsWith(".zip")) throw new InvalidFileTypeReportException(f);
        checkAfterZip(f);
    }
 
    public void checkAfterZip(File f) throws ReportException {
    }
}

You notice the same pattern again: provide an empty hook at the end after we perform our checks, and make the checkAfterNull method final. This way we can layer another check on top of the zip file check via the checkAfterZip “hook”. Even more, we can have each one of these checks can throw their own exception — as we did with InvalidFileTypeReportException, so one can implement a better exception handling at the higher level than simply bail out. (For instance if it’s not a zip file but perhaps it’s a directory maybe zip the whole directory and feed the result back into the export etc.)

Incidentally, this approach is actually flagged by one of the Checkstyle modules called “DesignForExtension” — and maybe its personal preference but I find myself taking this approach instead of the DZone one most of the time.

If you missed the link at the beginning of the post, here’s the Github repo with all the code mentioned above: https://github.com/liviutudor/CheckInputParameters

Leave a Reply

Your email address will not be published.