Many programmers consider source code dependencies either circular or non-circular, with circular dependencies representing The Greatest Imaginable Evil (which of course they do) and non-circular dependencies representing the acceptable if drab face of source code structure. This second representation is not quite true. The digital gods do not create all non-circular dependencies equal.
Figure 1 shows six methods arranged in two independent transitive dependencies, the method chains: a()→ b()→ c() and d()→ e()→ f(). Straight lines show dependencies down the page and curved lines (of which there are none, yet) show dependencies up the page.
Figure 1: Two transitive dependencies minding their own business.
As soon as two transitive dependencies dangle close to one another in the real world, however, dependencies between the chains start popping up. In figure 2, for example, method e() has taken a shine to method c().
Figure 2: A dependency between transitive dependencies.
So far, so good. Not a circular dependency in sight. Now, consider the transitive dependency on the left forms a second dependency on its counterpart, with f() sprouting a dependency towards b().
Figure 3: A curve-ball.
Suddenly, something looks wrong. It seems as though the transitive dependency on the left has developed an unhealthy interest in the one on the right. The two interconnecting dependencies cross one another, evoking the braiding or intertwining of ropes. Rich Hickey famously channeled his inner Jane Austin to resurrect an archaic verb describing just such an intertwining: "To complect." In his honour, we shall call the above type of dependency a, "Complectation" (we cannot use, "Complection," as this means, "Appearance of the skin, especially of the face" - though, oddly, complectations tend to give programs a sickly complection).
Nor is this merely an aesthetic point. Complectations admit mathematical definition and objective structural analysis. The reduction of ripple effect motivates all great structure and programmers can measure susceptibility to ripple effect by counting the number of methods on which each method depends; the higher this value, the worse the structure. This is the, "Impact set," of the program. Labeling each method thus, we can re-draw figure 3 as follows (ultimately d(), for example, depends on 4 other methods):
Figure 4: The impact set of all elements.
Figure shows an impact set of 12. If, however, the program could be re-written slightly shallower, lifting the invocation of f() from e() to d(), then figure 5, which eliminates the complectation entirely, would obtain.
Figure 5: A depth reduction.
Figure 5 has an impact set of 10, a 17% reduction compared to figure 4 by moving just one method dependency.
This, of course, is a toy example, and often the programmer requires precisely what figure 4 shows. But at least complectations raise the question and weeding out unnecessary complectations can improve structure. Below, for example, are two methods (reduced for presentation purposes), createGraphicsContext() and colourBackground(), with the former calling the latter:
private Graphics2D createGraphicsContext() { Graphics2D graphics2D = canvas.getBufferedImage().createGraphics(); colourBackground(graphics2D); graphics2D.setColor(options.getColour(ColourTag.FOREGROUND)); return graphics2D; } private void colourBackground(Graphics2D graphics2D) { BufferedImage bufferedImage = canvas.getBufferedImage(); Color background = options.getColour(ColourTag.BACKGROUND); graphics2D.setColor(background); graphics2D.fillRect(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight()); }
Figure 6 portrays the methods as they appear in the wild.
Figure 6: A real-world complectation.
The complectation here arises from both createGraphicsContext() and colourBackground() calling getBufferedImage(), when it is trivial to re-write the methods such that createGraphicsContext() calls getBufferedImage() and passes the returned object to colourBackground() (thus reducing colourBackground()'s expose to getBufferedImage()):
private Graphics2D createGraphicsContext() { BufferedImage bufferedImage = canvas.getBufferedImage(); Graphics2D graphics2D = bufferedImage.createGraphics(); colourBackground(graphics2D, bufferedImage); graphics2D.setColor(options.getColour(ColourTag.FOREGROUND)); return graphics2D; } private void colourBackground(Graphics2D graphics2D, BufferedImage bufferedImage) { Color background = options.getColour(ColourTag.BACKGROUND); graphics2D.setColor(background); graphics2D.fillRect(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight()); }
Thus yielding the ever-so slightly improved figure 7.
Figure 7: A complectation vanished.
Complectations are not software development's biggest problem.
They are a minor nuisance, a minor opportunity for source code improvement.
But they show the interesting nuggets to be found whilst panning the freezing waters of program structure.