DevOps Zone is brought to you in partnership with:

I'm a programmer. Or at least, I intend to be a programmer but it's taking an awfully long time. I'm starting to wonder whether being a programmer is more about the journey towards being a programmer than being a programmer. Edmund is a DZone MVB and is not an employee of DZone and has posted 34 posts at DZone. You can read more from them at their website. View Full User Profile

Is Your Code Structured Like This?

03.01.2013
| 10172 views |
  • submit to reddit

JUnit's evolution.

JUnit's evolving structure.

JUnit is a masterpiece.

As Martin Fowler tells us, "JUnit was born on a flight from Zurich to the 1997 OOPSLA in Atlanta. Kent (Beck) was flying with Erich Gamma, and what else were two geeks to do on a long flight but program? The first version of JUnit was built there, pair programmed, and done test first."

It is rather difficult to think of any other piece of Java software that has made a greater contribution to Java software quality.

Kent Beck has done an equally noble service to our field by releasing the JUnit source code as it was written, thereby bequeathing us a permanent historical overview of its development.

Let us avail of this gesture. Let us take a rather superficial journey through JUnit's evolution by studying, not the enormous value it has brought or the lines of code which realised that value, but its package-structure.

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 dependency tuples (that is, transitive dependencies) in which it partakes: the more red, the more dependency tuples.

We shall examine those releases most readily-available, from versions 3.7 up to 4.11.

Are we sitting comfortably? Then we'll begin.

The early phase.

JUnit 3.7.

Figure 1: JUnit version 3.7.

Figure 1 shows version 3.7 and perhaps one aspect seems most striking.

The diagram is lovely.

We can see at a glance, for example, that awtui depends only on runner and framework. A confusable lot, programmers value such clarity, drawing from it a warm sense of no-overtimeness.

We should not, of course, read too much into a package-diagram. We do not know, for example, whether the runner package holds five hundred or five million lines of code. Deeper investigation will show that no monsters lie hidden under beds here but we shall confine ourselves mostly to structure for the moment.

Structure being a set of elements and their dependencies, most would conclude that JUnit version 3.7 is a well-structured work. Let us move on to version 3.8

JUnit 3.8.

Figure 2: JUnit version 3.8.

In version 3.8 the ui package has packed its bags but otherwise the structure is unchanged. If anything it appears even more aesthetically-pleasing than before; some might consider figure 2 a thing of beauty. Uglier constructions certainly besmirch half the tee-shirts that amble by you daily.

So far so good.

JUnit 4.0.

Figure 3: JUnit version 4.0 (version 3.9 was not in the repository).

Something has happened in version 4.0.

The diagram is still relatively clean-looking despite the number of packages having almost doubled from six to eleven. A nagging doubt has surfaced, however. In some qualitative way the elegance of version 3.8 has, if not been lost, at least suffered some tarnishing.

Curved lines have appeared indicating our first dependencies going up the page. In itself, this is insignificant; Spoiklin's algorithm produces such curves as artifacts rather than damning moral judgments.

A curved line only raises concern where it forms the infamous, "Bow," pattern of mutually-dependent elements and such, alas, is the case here. Mutually-dependent packages invite suspicion. Mutually-dependent packages suggest exposure to one another's ripple-effect changes and this troubles programmers.

Figure 3 reveals three such bows: runner <-> framework, runner <-> runners and framework <-> notification.

Still, the package-structure looks manageable and concerns are allayed with release 4.1 whose structure is identical with that of version 4.0. Let us move on to version 4.2.

JUnit 4.2.

Figure 4: JUnit version 4.2.

Version 4.2 is also similar to 4.0; a single new package is added, internal. The bows that arrived with version 4.0 have failed to metastasize, structural degradation appears arrested.

Version 4.3, however, is growling around the corner.

The middle phase.

JUnit 4.3.

Figure 5: JUnit version 4.3.

Version 4.3 represents by far the largest structural change in the history studied here. Beneath the surface, the number of functions leaps from 564 in version 4.2 to 1309 in version 4.4 (it will fall almost as dramatically in the next release), though the number of packages rises from eleven to just sixteen. (This non-commensurate rise in the number of packages may help explain the fall in configuration efficiency from 31% to just 18%.)

The introduction of the tests packages seems the most significant change yet the authors have performed this introduction quite successfully. A good test of a structure is the ease with which one can point to any element and identify the other elements it depends on directly and transitively. Though there are many, tests's dependencies are readily traceable.

Also, we note that the number of bows remains unchanged: the surge of functions has not triggered an increase in mutually-dependent packages.

JUnit 4.4.

Figure 6: JUnit version 4.4.

Figure 6 shows version 4.4 and tests leaves us before we really got to know it. The number of functions falls from 1309 to 853; the number of packages rises to 19.

Among the new packages to make their entrance are the hamcrest cluster (bottom right) which also unfortunately bring three more mutually-dependent packages to the party. Still, this cluster shies from the main group. We may have some difficulty counting the number of bows in that main group (has it increased by 1?) but a diligent programmer could still hope to identify all transitive dependencies from a randomly chosen target.

This is perhaps the last time that such is the case.

The late phase.

JUnit 4.5.

Figure 7: JUnit version 4.5.

A difficulty in tracing transitive dependencies characterizes the late phase of JUnit's package-structure.

Figure 7 shows version 4.5. 150 functions more than version 4.4, the system boasts a configuration efficiency risen from 29% to 34%. Its twenty-six packages, however, have become embroiled.

For the first time, a programmer introduced to this system might pause before accepting the assignment. No doubt the drawing-algorithm generating the diagram must take some blame: a better algorithm might do a better job. No algorithm, however, can veil the brash rise in inter-connectedness on display.

hamcrest, previously to one side, has gravitated towards the big players, tugged by new dependencies from both runner and internal. It will remain center-stage until its sudden elimination in version 4.11.

How many mutually-dependent packages do we have? Who can say? If we change a package, can we easily predict which clients will be affected? Can say with any certainty which clients will not be affected?

Such questions dog all later revisions so let us skip forward to the last: all intermediates are visible in the animated graphic at the top.

JUnit 4.11.

Figure 8: JUnit version 4.11.

By version 4.11, transitive dependencies have proliferated seemingly unchecked. We are far from the short dependency-chains and few cyclic-dependencies of good structure.

And this ultimately is the point.

A software's user-experience realization is just another term for what that software does. This user-experience realization and its structure are, however, practically orthogonal. Perhaps hundreds of thousands of programmers use JUnit daily. It is a phenomenally well-tested piece of software and it works beautifully. Yet this tells us nothing about its structure.

Structure is not about execution: it's about development cost. The greater the inter-connectedness of a structure, the more ripple-effects can occur during updates and the more costly those updates can grow.

You cannot test good structure into a design. JUnit exemplifies this: it was written test-first but its structure has nevertheless degraded significantly with time.

Testing - at least unit- and acceptance-testing as we generally know them - is about user-experience realization. We write unit tests such as, "Verify that bank account balance does not fall below zero." We do not write unit tests such as, "Verify that package A has no dependencies on package B, C, D or Z." Yet the latter is precisely the type of consideration we must make when we create structure.

There is another way.

It doesn't have to be like this.

We can build package-structures that scale well with program size.

There are many ways to do this, but one way is to practice radial encapsulation. Figure 9 shows the evolution of a radially-encapsulated program that is far bigger than JUnit yet has throughout its history retained a structural clarity that JUnit seems to have abandoned.

Spoiklin Soice through the ages

Figure 9: A radially-encapsulated program (end configuration efficiency=69%).

Summary.

Programmers should be forced to wear their systems' package-structures on their tee-shirts.

Practice radial encapsulation.

JUnit is a masterpiece.

Published at DZone with permission of Edmund Kirwan, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Geoffrey De Smet replied on Fri, 2013/03/01 - 3:05am

Is there a maven plugin which builds these diagrams and a jenkins plugin which picks up that resulting diagram and shows it?

Without that integration - 99% of the developers will never generate/see those diagrams. With that integration, we might be able to convince half of the developers to at least look at those diagrams once in a while. Just like they look at the findbugs reports on jenkins.

Geoffrey De Smet replied on Fri, 2013/03/01 - 3:39am

How do you tell spoiklin to group on package? Just tried it and it shows methods instead of packages.

Is Spoiklin open source? Apache licensed? Hosted on GitHub?


Fred Janon replied on Fri, 2013/03/01 - 4:58am

Excellent article and find, thanks. I wish there was a tool like Spoiklin for Javascript, or for any language actually.

Carlos Chacin replied on Fri, 2013/03/01 - 9:09am

Please, maven plugin?

Edmund Kirwan replied on Fri, 2013/03/01 - 3:29pm in response to: Geoffrey De Smet

Oops: thanks for pointing that out.The up-arrow icon goes from method-diagram to class-diagram, and then again up to package-diagram. I, of course, forgot to add a nice tooltip to that effect: I'll correct that.

The maven-plugin's in the making though it's not open-source yet or licensed or Githubbed. I'll look into all that admin.

Thanks again for pointing out the tooltip omission.

Geoffrey De Smet replied on Fri, 2013/03/01 - 3:35pm in response to: Edmund Kirwan

Nice work :) Good to hear a maven plugin is in the works.

For some inspiration, take a look at this findbugs report:
  https://hudson.jboss.org/hudson/view/Drools%20jBPM/job/drools-planner/2231/findbugsResult/
What happens is:
  • the findbugs-maven-plugin generates the findbugs report during the process build
  • the findbugs-jenkins-plugin takes those generated reports and shows them in the jenkins overview

So the findbugs results are just 1 click away for developers.

Christian Schli... replied on Sun, 2013/03/17 - 2:47am

Excellent article!

For analyzing package dependencies and detect circular/mutual dependencies, I recommend JDepend as an alternative. There is even a maven plugin which creates a report for a Maven site.  You can find a sample report here (shameless self plug): http://truezip.java.net/truezip-file/jdepend-report.html

The output of the report is not a fancy graph, but it provides you with a lot of details for analysis, in particular if the code base happens to have a circular/mutual dependency.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.