Today on my current project I had to spend some time creating a client JAR file for use by downstream systems. I needed to keep the JAR as small as possible, with (more importantly) no external JAR dependencies.
There are tools out there for dependency analysis, such as IBM’s Structural Analysis for Java (aka Smallworlds), Compuware’s Pasta or the open source JDepend. As nice as these tools are, they are for analysis and the gathering of metrics. As I’ve mentioned before, unless ‘good metrics’ are in some way enforced by the build process, it becomes very easy for even automatically gathered metrics to be ignored.
JDepend can be run via Ant, but just like Emma or Findbugs the information gathered is used for reporting purposes – it is not capable of failing a build because someone has introduced an invalid dependency between packages.
Japan is a tool which comes with an Ant plugin that will fail a build if your code violates the allowed package dependencies. For example, our downstream systems only need to use classes in the
client package – I don’t want to have to include any other code in the client JAR file. So in a Japan config file I place the following code:
Now if any of the classes in
client include any other packages in my codebase, the build will fail. It’s important to note that Japan requires that you define all your dependencies at the package depth you define (the
package-depth="4<a href="http://www.c2.com/cgi/wiki?AcyclicDependenciesPrinciple" <2>> means that all source code in com.company.app.xxx and below will be checked) – so for example if our
gui package depended on
client, I’d have to add the line:
By defining these configurations you can quickly discover circular dependencies) – for example if to get the build passing you find yourself defining something like this:
You know something is up. The other thing I like about Japan is the fact that because I can now enforce sensible package dependencies, I feel better about spending some time cleaning our packages up, safe in the knowledge that we won’t backslide (assuming no-one goes and sticks everything in one giant package of course). There was one little problem I had though – I had to disable transitive dependency checking as it caused a stack overflow error, but I think once we remove our existing circular dependencies that should sort itself out. I still of course like to have tools like Pasta to help me define acceptable inter-package dependencies, but I feel much happier having Japan in the build just in case I start getting sloppy :-).
6 Responses to “Enforcing acceptable package dependencies in your build”
I really do not intend this to be a naive question, so kindly hear me out. Are circular dependencies really bad in Java? Don’t they happen all the time? They’re all over the JDK itself (util depends on io which depends on io, to take but one arbitrary example). I would think with the runtime binding that—apart from architectural concerns, which, I grant you, are often quite valid—circular dependencies just aren’t that big a deal, technically.
Using the Java API as a model of good programming is probably not good idea – the API developers have admitted on many occasions that mistakes were made, but due to maintaining backwards compatibility have been unable to fix them.
Circular dependencies are a problem for _any_ language – Java is no exception (for more on package coupling and OO, see the “Pricnciples of Object Oriented Design”:http://www.c2.com/cgi/wiki?PrinciplesOfObjectOrientedDesign. If A depends on B, and B depends on A, if you change A you can potentially change B, which in turn can force a change on A. More importantly, A and B are now inextricably linked – the larger the project, the more this becomes a problem – as changes cannot be isolated to a single package the chance for effects rippling out to affect others increases (for a much better description see pages 6 onwards of the “Granularity”:http://www.objectmentor.com/resources/articles/granularity.pdf article linked to from the C2 wiki page on “Acyclic Dependencies”:http://www.c2.com/cgi/wiki?AcyclicDependenciesPrinciple)
Thanks for the response. You raise good points. It’s interesting—I’m aware of the architectural problems in general (if A depends on B and B depends on A, changes to one often necessitate changes to the other), but where A and B are packages and not classes I don’t necessarily see this as being as big a deal. Often it really means that the classes that cause the interdependency should simply be in the same package, or that the two packages should be collapsed, since you’re going to have to deploy them with each other anyhow. I’ll check out the c2 links. Thanks again.
I’d certainly agree that some circular dependencies can show up classes that should be in the same package – but equally they can show you places where you need to decouple things to achive high cohesion within a package (that is the package is strongly focused with common responsibilities) and low coupling. Again, there is an informative discussion of cohesion and coupling on the “c2 wiki(c2Wiki – Coupling and cohesion)”:http://www.c2.com/cgi/wiki?CouplingAndCohesion.
Another way that we’ve been tying JDepend into our build is to use it programatically as a unit test which will fail our build process. Although there was probably a little bit more work involved in setting up failures (and formatting the info well), we still managed to get it going with not too much hassle.
Thats good to know – thanks Pat. That also implies that rolling an Ant plugin capable of failing the build shouldn’t be too much bother.