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.
Problem definition
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:
1 | package me.loki2302.spring; |
When you run the build, you want it to fail with a clear message saying that having a reference to PersonRepository
in HelloController
is a bad idea.
Solution
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 beCodeModel
initialization 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 ofDesignViolation
objects for every file we validate.
SpringAppDesignCheck
– a custom Checkstyle check
Let’s first take a look at SpringAppDesignCheck
and its 2 methods: beginProcessing()
and processFiltered()
.
The 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 CodeModel
:
1 | public class SpringAppDesignCheck extends AbstractFileSetCheck { |
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):
1 |
|
CodeModel
– a code analyzer built around QDox
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.
1 | public class CodeModel { |
The algorithm is quite straightforward:
- For every controller class
- Get all fields of type
JpaRepository
- And for every such field record the error
How do we find all controller classes? We just look for all classes annotated with @RestController
:
1 | private List<JavaClass> getControllerClasses(File file) { |
How do we find all class fields of type JpaRepository
? We just look for all fields of type JpaRepository
:
1 | private List<JavaField> getJpaRepositoryFields(JavaClass javaClass) { |
Thanks to QDox’s intuitive API, the most hardest part of our solution, code analysis, looks very straightforward.
Demo
Now, if we enable our check in checkstyle.xml
:
1 | <?xml version="1.0" encoding="UTF-8" ?> |
The build will fail with this error:
1 | [ant:checkstyle] [ERROR] /home/loki2302/checkstyle-experiment/ |
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;
Conclusion
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.