Keep your dependencies clean
Manage the dependencies of a project is important and becomes more crucial when a project grows bigger. If this is not taken care of, a big ball of mud will be the result or a spaghetti monster that cannot fly.
One specific kind of dependencies are the ones between Java packages. If you don’t have a look at it while coding, it’s most natural that there will be circles in the dependency graph. Classes from package A use classes from package B which use classes from C which use A.
This structure makes the code harder to change and hinders modularity. Imagine A contains business logic and B are some utility classes. As B is useful also for other projects, you want to separate it into its own module. But this is impossible without breaking up the cycle because you don’t want any business logic inside the utility project.
There are great tools to detect such cycles, such as structure 101, JDepend, FindBugs or Classycle. But the problem is, somebody has to use them regularly and take some measures as soon as cycles are detected. In my experience, this does not work. Nobody takes enough time for such a job and when it’s not done regularly, the tangle will quickly become too dense to solve and the tools are only used to measure the misery.
That’s why I started using JDepend as part of the build process. When the package structure is broken, the build fails. This is immediate feedback. This works.
Unfortunately, JDepend is not an active project anymore. But it’s available on GitHub, so is made a fork to correct a bug, support Java 8 and add some API niceties. It’s available in the maven central repository.
A possible usage of it looks like this:
public class DependencyTest {
private static JDepend depend;
@BeforeClass
public static void init() throws IOException {
depend.addDirectory("target/classes");
depend.analyze();
}
@Test
public void noCircularDependencies() throws IOException {
assertThat(depend, hasNoCycles());
}
@Test
public void dependencies() throws IOException {
DependencyConstraint constraint = new DependencyConstraint();
final JavaPackage
base = constraint.addPackage(""),
util = constraint.addPackage("util"),
core = constraint.addPackage("core");
base.dependsUpon(util, core);
core.dependsUpon(util);
assertThat(depend, matches(constraint));
}
@Test
public void maxDistance() throws IOException {
assertThat(depend, hasMaxDistance("", .5));
}
}
It checks that the code has no package cycles, follows the defined dependency structure and has a good balance between abstractness and instability.
I’m using it in some of my private projects. It makes me think about how a project should be structured and then ensures keeping it in the defined way. I should introduce it in my day job.