Friday, January 25, 2008

Java Packages and Dependency Management

I used to think Java's packages were a great thing. They solved the problem of naming conflicts and enforce a consistent system of organizing source code on disk for everything written in Java. That view came from working on some C/C++ codebases where boundaries of name-spaces were haphazard or nonexistant and the conceptual organization of the source bore no relation to the location in the file system of the various modules that made up the program. Tracing an execution path through the code was next to impossible and the build process a nightmare.

I had a friend, an experienced C++ developer who said he always felt straight-jacketed by Java packages. He didn't want to be forced to organize his files in the same way he organized his packages. I never understood why anyone would want the freedom to create the type of disaster I had seem before. But there's a part of the picture I was missing. Packages also imply something about dependencies.

Good guidelines on packaging are remarkably hard to come by. But, at minimum, a well designed package structure will minimize dependencies between packages and avoid circular dependencies. I've come to follow other rules of thumb about inter-package dependencies. Everyone can depend on a util package. But a util package should have no dependencies inside the project. Domain model classes should be defined abstractly and be free of outside dependencies.

Inversion of control is when code at a lower level of abstraction depends on code at a higher level of abstraction. The domain model is defined at a high level, with little or no implementation detail. In fact, much of my domain models are just interfaces. Lower level packages do the grunt work with specializations of the domain model artifacts.

Recently, I worked on a project with a Swing UI, which I hadn't done in years. I created a ui package to hold all the Swing related kerfuffle. I started noticing that I had a lot of actions. These little 20 line subclasses of AbstractAction define the event handlers used by controls throughout the UI and seem to multiply like rabbits. Mixed in with the rest of the UI classes, classes that formed the windows, panels, dialogs, and other on-screen widgetry of my app, the actions were hard to find and just cluttered up the place. I wanted, for strictly organization reasons, to move them out of the way.

Of course, the actions depend on the UI widgets they invoke and the UI widgets depend on the actions. So, if I put my actions in a separate package, I'd have a big ugly circular dependency staring me right in the face. I go sick of looking at the action classes, so I did it anyway. Where dependencies and name-spaces are concerned, they're all in one conceptual unit. But on disk, and therefore necessarily in the package hierarchy, they're in two different places.

Since becoming more aware of the role dependencies play in packaging, I've notice more little conflicts of interest like this. I still prefer the enforced file system structure of packages to total chaos, but I now recognize my friend's point.

It would be an interesting extension to the language to be able to explicitly declare that package X was allowed to depend on packages Y, Z, and util. Any other dependency could generate a compiler warning. There are static code analyzers that will do just that. (JDepends for example.) It would be even better if the IDE was aware of such things and could issue a warning as you added a suspicious dependency.

1 comment:

  1. Condensed version: There are at least two purposes for which you might try and use packages. One is to organize code. The other is as a boundary for dependencies. Those two purposes don't always point in the same direction.

    ReplyDelete