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 32 posts at DZone. You can read more from them at their website. View Full User Profile

Amplification Revisited

06.22.2013
| 3926 views |
  • submit to reddit

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 dependency tuples - loosely speaking, chains of 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 dependency tuple.

Recall from that earlier post that figure 1 below contains five dependency tuples: {a(), c(), d()}, {a(), c(), h()}, {a(), c(), f()}, {a(), c(), g()} and {a(), c(), e()}.

System of five dependency tuples

Figure 1: System of five dependency tuples.

If we add a new function, b(), which calls c(), then we end up with ten dependency tuples, see figure 2.

System of ten dependency tuples

Figure 2: System of ten dependency tuples.

That is: one new dependency = five new dependency tuples.

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 dependency tuple offers greater potential ripple-effect cost than a similar update within a short dependency tuple. Herein lies the pressure to increase the number of dependency tuples within our software systems by snipping long dependency tuples into multiple shorter ones.

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

If the, "Snipping," above were the sole source of tuple-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 tuples almost arbitrarily, broadening source-code structure without limit.

Large software systems demand a multitude of dependency tuples but this number should not grow unnecessarily: hence the razor. Moderation summons the razor to flick and swish where, in some sense, too many dependency tuples 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 dependency tuples 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 dependency tuples 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, tuples, "Leading into," a function are the chains of transitive dependencies leading into that function.

For example, in figure 1 above, there is one dependency tuple 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 tuple leading into it, it has no amplification by definition.

Two dependency tuples 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 dependency tuples that can be removed; it contains no unnecessary dependency tuples. 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 tuples, one sourced at a() and one at z().

Distantly-nasty amplification

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).

A cure for amplification

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 dependency tuples remains unchanged even after the interface's introduction (albeit that one of the dependency tuples - {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 dependency tuples. 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.

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.)