Look, whatever you're doing: stop.
If you're at work, go home.
If you're at home, grab a beer from the fridge.
If you have dog, grab a beer for him, too.
Banish the kids to their rooms.
Send your husband out to get his hair done or your wife out to mend the car.
Send your neighbours to the cinema.
Because the girls and boys from Spring are about to give you the greatest package-structure masterclass of your entire sorry-assed life.
Note the time, because you'll want to look back at the moment when all your Java projects' package-structures were suddenly rendered ugly, embarrassing and obsolete.
You are about to become a better person.
What had become a gloomy search for structural excellence among the the most popular, open-source, Java programs slams into Spring, the, " ... most popular application development framework for enterprise Java." Spring being too large to study as a single entity, this analysis will concentrate on the jar-file of the core components. (And thanks to @etiennestuder for suggesting Spring as a candidate for analysis.)
As usual, 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-dependency transitive dependencies in which it partakes: the more red, the more transitive dependencies.
Seriously, hang on to something.
Figure 1: Spring core version 0.9.
It all starts so well but if this series has taught us anything it is that most applications start well. Figure 1 shows us the nine, well-structured packages of Spring's core jar-file housing around eight hundred functions. Pretty as a picture, it almost invites update so that we can effortlessly tip-toe along the ripple-effect paths.
So maybe a mutual dependency twangs between
beans but what's a single mutual dependency between friends?
Figure 2: Spring core version 1.0.
Version 1.0 contains more-or-less the same number of functions as 0.9 but the number of packages increases from nine to eleven with that big, bad
access barging into dominance, spraying dependencies far and wide.
Still, stark order remains; even the
beans mutual dependency has vanished.
Figure 3: Spring core version 1.1.
Figure 3 sees the number of functions rise to over a thousand and a third
support package making an entrance, all gracefully absorbed by that resilient structure.
io has developed a dependency on
propertyeditors is making eyes at
io, to note a few of the changes, but nothing untoward disturbs the peace.
Excellence of structure has now survived three significant upgrades; "Not a bad record for this vicinity." Fingers crossed that all goes well for whatever next mass influx of functionality that the Spring engineers have in store.
Figure 4: Spring core version 1.2.
The number of functions shrivels to 369. Some sort of Year-Zero-Event, then. That code review must have been the mother of all bloodbaths.
Not really much to say about this.
Figure 5: Spring core version 2.0.
Version 2.0 returns to business as usual with almost nine hundred functions dolloped into ten packages and producing a structure which looks even better than that of the first version, 0.9.
The achingly-pretty simplicity finds itself reflected in two key statistics.
Firstly, the structure is so wonderfully shallow with an average function transitive dependency length of just 3.0 (std. dev. 1.6). Depth ruins good structure, bulldozing all before it into the landfill of complication. Indeed, depth had been a minor concern in version 1.1 which had an average function transitive dependency length of almost ten - considered slightly high - but The Great Contraction of version 1.2 seems to corrected this trajectory: average transitive dependency length will never again rise above an impressively-low six.
Secondly, as a direct consequence of the shallowness the number of function transitive dependencies composing the core components hovers at just over one thousand. This figure will become a concern again - we'll see that later - but to have the number of transitive dependencies so close to the number of functions strikes as an extraordinary feat of software engineering, producing one of the least-coupled designs this series has witnessed.
Figure 7: Spring core version 2.5.
Version 2.5 maintains its structural composure even in the face of an extra nine packages and six hundred functions.
Tracing potential ripple-effect paths remains a breeze. Package-spotters - up with the dawn mists, woolly-hatted, thermos-flasked - scribble to record the excited arrival of the
asm package: it will disappear immediately into those dawn mists and pop back up in version 3.2.0. where it will play a prominent role in the transitive dependencies problem.
Figure 8: Spring core version 3.0.0.
Version 3.0.0 accretes another three hundred functions, bringing the total to eighteen hundred, and sheds two packages, resulting in an almost-perfect crystalline structure. That such a vision represents a real-world, working-for-a-living, get-your-hands-dirty system rather than an ideal toy created in a structure tutorial belies belief.
Beneath the surface, yet another key statistic enjoys improvement. The core components' configuration efficiency has to date languished at around twenty per-cent but this version sees it leap to thirty-four per-cent and it will rise slowly but steadily with each new upgrade.
Figure 9: Spring core version 3.0.5.
Version 3.0.5 brings with it an extra two packages and one hundred and fifty functions. The result? Continued, flawless structure.
Figure 10: Spring core version 3.1.0.
Version 3.1.0. adds yet another three hundred functions, bringing the total to almost two thousand three hundred.
An ever-so slight blemish has appeared in those curved lines but these are mostly dismissible as drawing artifacts rather than indicators of deeper degradation. That the system maintains its integrity cannot be doubted.
Figure 11: Spring core version 3.2.0 with 4521 functions.
Version 3.2.0 - last stop on our tour - represents by far the largest upgrade in the Spring core components' history displayed here: the number of functions virtually doubles to four thousand five hundred while the number of packages soars to thirty-two.
Yet how does this massive increase in functionality impact the structure? Have the invading hoards brought the system to its knees?
Not at all.
Figure 11 radiates confidence and control and complete structural mastery.
The Spring team has stood firm before the onslaught, battling the potentially-overwhelming functionality by simply expanding their structure to embrace it. The upshot is a configuration of packages that oozes decoupling and clarity-of-dependency. The aim of good structure is to aid update-cost prediction and figure 11 does precisely that.
Facing such a diagram we all too easily lose sight of just what an accomplishment it represents. The secret of success lies in making the difficult look easy and in this the Spring programmers have alas excelled themselves. Without immediate comparison some might think that figure 11 is somehow normal in the field of software engineering, or even common.
This would be a mistake.
Compare figure 11 to the Ant package-structure, for example, when it grew to a similar number of functions:
Figure 12: Ant at 4603 functions.
The Struts core components barely rose above three thousand functions yet recall that package-structure:
Figure 13: Struts core components at 3022 functions.
And JUnit never rose above thirteen hundred functions:
Figure 14: JUnit at 1295 functions.
Spring makes these look like crayon scrawls on padded walls.
But even Spring has its problems.
Though the package-structure thrived in the massive upgrade to version 3.2.0, a problem arose lower down: the number of function-level transitive dependencies leaped from below four thousand in 3.1.0 to twenty-six thousand in 3.2.0. At the same time the number of function-level circular dependencies shot from below three hundred to above five thousand.
That rise in circular dependencies suggests that the proliferation of transitive dependencies stems from some amplification effect; it would be a shame not to peek just a little closer to try to find evidence for such a claim.
And evidence there is. For when we look again we find that the function through which most transitive dependencies flow is
ClassReader.accept() in the
asm package, a function whose cravat of immediate dependencies is shown in figure 15 (in which circles represent functions rather than packages).
Figure 15: ClassReader.accept()
From the naming scheme the experienced programmer immediately recognizes this as a visitor pattern which may go a long way towards explaining the problem: for such a dense concentration of dependencies offers a perfect nesting-ground for amplification effects.
asm package, in which this function resides, is a sink: it has no dependencies on any other packages in the core components.
Both points suggest that the exorbitant number of transitive dependencies - which might otherwise spoil the value of so fine a package structure and make it, analytically, worse than both JUnit and Struts (see below) - may be due not to systemic dependency disarray but a mere amplification-effect, one mostly confined to a single package.
On the whole, then, we can applaud this as a good structural achievement.
Thank you, Spring.
Just thank you.
|Absolute potential couple efficiency %||38|
|100 - (% length of longest dependency that half the system is shorter than)||64|
|% Method transitive dependencies spanning 2 packages or fewer||60.9|
|((25 - (number of transitive method dependencies per method) / 25) as % of 25||77|
Spring 3.2.0's structural evaluation.