Developers care about the code they write. They build tools that enforce spaces instead of tabs, forbid 1-letter identifiers and ensure that every class and method has Javadoc comments. One example of such a tool is Checkstyle.
But usually, it’s not code style violations that makes code hard to read and maintain. More often, it is higher level code organization (software design) – all the decisions made about classes, their responsibilities, connections between them, etc.
Tools like Checkstyle surprisingly won’t help you to avoid making your view layer go directly to the database. In this post I want to show how to implement this kind of design constraint by building a custom Checkstyle check.
Let’s consider a naive example – a Spring web application where you have controllers, services and repositories. Let’s pretend that, as the architect, you believe that controllers should only talk to services, and services should talk to repositories. You consider controllers talking directly to repositories a bad design and want to entirely forbid this:
When you run the build, you want it to fail with a clear message saying that having a reference to
HelloController is a bad idea.
Checkstyle’s code analysis capabilities are quite low-level. When you build your own check, Checkstyle gives you an AST and it’s up to you to understand what it describes. While AST is one of the central concepts in compiler design, it only describes the words code consists of, not the code’s semantics.
We are interested in semantics. We don’t want to operate on AST level and instead of looking at the code, we want to look at what this code describes. QDox is a great library that reads Java code and builds a model of all packages, classes, methods and links between them. It doesn’t analyze method bodies, though (if it’s a showstopper, take a look at Spoon – a more powerful alternative to QDox).
To solve the problem, we’ll make Checkstyle and QDox work together:
- We’ll create a
SpringAppDesignCheck– a custom Checkstyle check that will connect our code analysis with Checkstyle. Its only goal is going to be
CodeModelinitialization and querying.
- We’ll create a
CodeModel– a service that uses QDox models to understand the code structure. Its only goal is going to be to provide a collection of
DesignViolationobjects for every file we validate.
Let’s first take a look at
SpringAppDesignCheck and its 2 methods:
beginProcessing() method gets called once per build. This method is a good place to perform initialization. In our case, we’ll construct the
JavaProjectBuilder and the
What we do is, we just load the entire codebase from the very beginning.
The second method,
processFiltered(), gets called once per source file and is supposed to emit errors (if any) for this specific file. In our case, we ask
codeModel object to give us all the error descriptors and then just format and log them (logging is how Checkstyle expects you to report the errors):
Now, let’s take a look at
CodeModel. Its only responsibility is to provide a collection of
DesignViolation objects for a given source code file. Every
DesignViolation is a descriptor of which controller class has a reference to which repository class in what field.
The algorithm is quite straightforward:
- For every controller class
- Get all fields of type
- And for every such field record the error
How do we find all controller classes? We just look for all classes annotated with
How do we find all class fields of type
JpaRepository? We just look for all fields of type
Thanks to QDox’s intuitive API, the most hardest part of our solution, code analysis, looks very straightforward.
Now, if we enable our check in
The build will fail with this error:
If you jump to the top of the page, you’ll see that
HelloController.java line 10 is exactly where the violation is:
private PersonRepository personRepository;
Software exists in time, so maintainability is one of the defining factors of software quality. Clear and logical design is a key to good maintainability. While it’s a responsibility of every developer to keep the system balanced as it evolves, some of the design validations are easy to automate and minimize the risk of human factor.
There’s a self-sufficient demo project in this GitHub repository – make sure to take a look.