Apache Ant is a wildly successful build tool which, " ... supplies a number of built-in tasks allowing to compile, assemble, test and run Java applications."
The Ant project having made all it releases available to posterity, this post continues the search for great Java structure by examining the package-structure of the Ant, much like the previous post analyzed the excellent JUnit.
We shall look at each release's package-structure as a spoiklin diagram in which a circle will represent a package, straight lines will represent dependencies from packages drawn above to those below and curved lines will represent dependencies from packages drawn below to those above. The colour of a package will indicate the relative number package transitive dependencies in which it partakes: the more red, the more transitive dependencies.
Figure 1: Ant version 1.1.
Figure 1 shows the package-structure of Ant version 1.1.
It is a simple beast.
The diagram portrays a structure whose clarity belies its capability; even this youthful Ant boasted some powerful features. Concerns surface when we note that the configuration efficiency rests at twenty per-cent (and will remain so throughout the history here) indicating that some packages might be too large but we, for the moment, merely note this worry and move on.
Figure 2: Ant version 1.2.
Version 1.2 has its glad-rags on and is ready to party. (An enthusiasm for utter transformation typifies Ant's early career.)
The number of packages soars from four to thirteen, veiling a rise in the number of classes from eight-hundred to fifteen-hundred. Most notable, however, is the configuration of these newly-arrived packages: splayed out, seemingly not in the service of the main ant
, taskdefs
and types
packages but feeding off them.
In that these main, three packages seem to provide services to most others, this, like version 1.1, would appear to be a fine, flexible structure.
Figure 3: Ant version 1.3.
Hello, version 1.3! Another head-to-toe wardrobe-change sees Ant go all vertical.
The wave of packages that invaded version 1.2 have been repelled and in their place compilers
and util
help cement a pretty, crystalline structure. Some mutually-dependent, "Bows," have appeared but they do not spoil the ease with which dependencies might trace themselves as packages undergo updates.
Inter-package coupling therefore not being a concern, this structure enjoys promising prospects.
Figure 4: Ant version 1.4.
Version 1.4 sees taskdef
level-up as it sprouts mail
, rmic
, zip
and condition
, with optional
and listener
becoming the system's new entry-points and bringing the number of packages from eight in version 1.3 to thirteen.
Still, the diagram looks coherent and well-structured.
But you know what they say in programming. "One day you're in and the next day you're out."
Figure 5: Ant version 1.5.
Version 1.5, a massive upgrade, brings a thousand new functions in its wake and the results, structurally, are disastrous.
The heritage remains clear; we can easily identify in figure 5 the fundamental relationships established in version 1.4, but oh how the dependencies have thickened and grown. A great stiffening has taken place. Where ripple-effects were traceable in version 1.4, version 1.5 brings a disheartening waywardness.
This is unfortunate, too, because the structure might otherwise be sound. If those main red packages grazed (what we presume to be) the service-providing classes arrayed below, this structure would be fine. All those curving lines, however, imply that almost as many dependencies go up as down, opening the door to a gang of murderous cyclic dependencies.
And worse is to come.
Figure 6: Ant version 1.6.3.
Version 1.6 adds another thousand functions but this hardly matters. Ossified, the main three packages - ant
, taskdefs
and types
- lie embedded in dependencies offering no clear relation with their surrounds. Coupling, cackling, has won.
A package-structure perspective, this analysis could end here.
But there is something else to say about Ant lower down, on function-level.
Let us look at its circular transitive dependencies. Actually, before we do that, let us examine the function-level transitive dependencies of our previous analysis patient, JUnit. To be even more precise, let us look at the number of function-level circular transitive dependencies per function, normalizing our figures to allow comparison. Figure 7 shows JUnit's circular dependency density per release.
Figure 7: JUnit's function-level circular transitive dependency density.
The figure shows that JUnit's circular transitive dependency density remained low throughout its development; today, it has fewer than 0.1 circular dependencies per function. Contrast this with Ant's circular transitive dependency density in figure 8.
Figure 8: Ant's function-level circular transitive dependency density.
Today, Ant has seven circular dependencies for each of its functions: over 35,000 circular dependencies in total.
Yikes.
Ant is a great, popular and easy-to-use build environment for Java (and many other languages besides). Its contribution to the Java community is as appreciated as it is priceless.
This, however, was a structural analysis of Ant and we must regrettably conclude that our search for an excellent Java structure continues.
If we were keep score, we would say that, so far, JUnit is our top contender and Ant is a distant number two.
Factor | Score |
Absolute potential couple efficiency % | 21 |
100 - (% length of longest dependency that half the system is shorter than) | 60 |
% Method transitive dependencies spanning 2 packages or fewer | 12.6 |
((25 - (number of transitive method dependencies per method) / 25) as % of 25 | 0 |
Average | 24% |
Ant's structural evaluation.