Amplification revisited.


Ashdown ABM-300 Bass Amplifier

Accidental interconnectedness.

A previous post discussed the problem of amplification but failed to present a solution. So, how do we tackle this bad boy?

Amplification concerns transitive dependencies and the phenomenon by which adding just a single new dependency between (say) the functions of a program can generate vastly more than one new transitive dependency.

Recall from that earlier post that figure 1 below contains five transitive dependencies:

Image generated by Spoiklin Soice

Figure 1: System of five transitive dependencies.

If we add a new function, b(), which calls c(), then we end up with ten transitive dependencies (see figure 2):

Image generated by Spoiklin Soice

Figure 2: System of ten transitive dependencies.

That is: one new dependency = five new transitive dependencies.

Granted, this particular example would hardly stop a programmer on the way home from the pub looking for a fight but the principle cracking its knuckles in the background might give pause for thought.

The reason being, of course, Mister Ockham's shaving apparatus.

Put bluntly, a randomly-placed update within a long transitive dependency offers greater potential ripple-effect cost than a similar update within a short transitive dependency. Herein lies the pressure to increase the number of transitive dependencies within our software systems by snipping long transitive dependencies into multiple shorter ones.

From the opposite direction, however, rushes the breathless truth that more transitive dependencies imply more vectors along which ripple-effects worm their wicked ways. Programmers - notorious minimalists - seldom wish for more of anything; if they need more transitive dependencies then they accept it but only with much nose-wrinkling.

If the, "Snipping," above were the sole source of transitive dependency proliferation then this delicate state might hold with depth and breadth locking horns in eternal, primal if muted violence. When amplification stomps into the fray, however, it plays dirty, creating transitive dependencies almost arbitrarily, broadening source-code structure without limit.

Large software systems demand a multitude of transitive dependencies but this number should not grow unnecessarily: hence the razor. Moderation summons the razor to flick and swish where, in some sense, too many transitive dependencies burst into being - where breadth grows dominant, pushing structure to instability - as in the case of amplification. That earlier post showed how amplification can lead to a single function's contributing three hundred thousand transitive dependencies to a miserably-high total. (Ant is too damn broad.)

Yet what are we to do about it?

Identification and eradication.

Two problems face us. First, can we rigorously define amplification? Second, can we manage it?

Unlike in the previous post, which identified merely a specific case of amplification, we can now now formulate a definition: the amplification of a function (or class or package) is the number of transitive dependencies leading into that function (where greater than one) multiplied by the number of dependencies that that function has on other functions (where greater than one). Here, transitive dependencies, "Leading into," a function are the chains of transitive dependencies leading into that function.

For example, in figure 1 above, there is one transitive dependency leading into function c() and c() has five dependencies on other functions. Its amplification would therefore be 1 * 5 = 5 except for our two conditions: as this function has only one transitive dependency leading into it, it has no amplification by definition.

Two transitive dependencies lead into c() in figure 2, however, so its amplification is 2 * 5 = 10. Note, though, that despite figure 2's offering a demonstration of amplification it contains no transitive dependencies that can be removed; it contains no unnecessary transitive dependencies. Amplification comes in two flavours: necessary and (to match its cousin, complexity) accidental. We can eliminate accidental amplification; necessary, we cannot.

If, furthermore, amplification were concerned only with immediate dependencies - and hence basic fan-in and fan-out - then even the accidental variety would present little difficulty but amplification revels in spooky action-at-distance: the fan-in can be distantly remote from the fan-out, making it as tricky to spot as it is easy to create.

In figure 3 (colour-coded with red indicating amplification and black indicating no amplification) only f() is an amplifier yet the reason for its amplification languishes far away in the confluence of the waters at b(). Although only one direct dependency exists on f(), any new dependency from f() would create two new function transitive dependencies, one sourced at a() and one at z().

Image generated by Spoiklin Soice

Figure 3: Amplification in surprising places.

With our radars tuned to detect amplification we can manage it in the time-honoured way in which we manage our circular dependencies: death by interface. We simply create an interface to declare the f() function and can call the interface instead of the implementation, see figure 4 (the dotted line indicates Java interface implementation).

Image generated by Spoiklin Soice

Figure 4: Amplification eliminated via interface.

Of course, this is a toy example. The two elephants in the room trumpet about, gawking at us.

Firstly, this example contains no accidental amplification: the number of transitive dependencies remains unchanged even after the interface's introduction (albeit that one of the transitive dependencies - {f(), f()} - consists not of a set of function invocations but of a function's expressing its implementation of an interface declaration).

Secondly, no sane programmer on this earth would go to the bother of creating an interface and its implementing class just to mop up a little (necessary!) amplification.

Recall, however, our previous post and that single Ant function's contribution to three hundred thousand accidental transitive dependencies. That real-world example craves prophylactic abstraction; an interface flown-in solely to eradicate such rampant amplification must surely be worth the fare.

Summary.

Programmers, the modern world's beasts of burden, would seem to have enough to worry about without chasing ghosts. In the furious scramble to deliver software systems their precious focus would seem wasted in tracing fault-lines along which future costs might - potentially - accumulate.

Alas, such concerns matter.

Interconnectedness matters.

Profligate interconnectedness is unnecessary weight long before it is an immediate source of costs, a cargo we tolerate because of its apparent lack of immediate danger when so much around us burns for our attention. Yet how much of what now demands our attention does so only because of some unnecessary interconnectedness taken on-board in the past?

Airplanes do not struggle to take to the air; they struggle to stay on the ground.

Photo credit attribution.

CC image Ashdown ABM-300 Bass Amplifier courtesy of aplumb on Flickr.