A Funny Thing That Happened on the Way to the Refactory
A Funny Thing That Happened on the Way to the Refactory
Join the DZone community and get the full member experience.Join For Free
DevOps involves integrating development, testing, deployment and release cycles into a collaborative process. Learn more about the 4 steps to an effective DevSecOps infrastructure.
Structure and why we care.
Structure may be defined as a set of elements and their relations.
Given that - in software development - a change to any of these elements has a non-zero probability of propagating backwards to smash any dependent element. Given that each such propagated change incurs a cost, then structures with a high probability of propagating statistically distributed changes usually cost more to change than those with a low probability. Furthermore, given that properties of the structure itself determine the probability of statistically distributed change propagation, then those principles which reflect properties that minimize such change propagation usually outclass those that do not.
In other words: ripple effect.
Professional programmers revel in their freedom to chose whatever principles they desire with which to cultivate software structures, such choices often informed by valuable if subjective experience. The structures they create, however, sit in permanent and objective judgment of their creators' works. In the trial of structure only source code testifies.
Java structures itself on (at least) three levels: method-level, class-level and package-level. For various interesting reasons, although similar principles operate at all levels some levels accommodate specific organizational apparatus unavailable to others. Despite the package structure's being largely derivative of the method structure, for instance, Java's package-handling mechanics nevertheless constrain the structure created at all lower levels. This post concerns just this level, package level, and a realization stumbled upon during a routine refactoring.
Radial encapsulation: a lightning reprise.
Oracle gives us the official terms, "Top level package," and, "Subpackage," when it writes, "If a package name consists of a single Identifier, then this identifier denotes a top level package named by that identifier," and later, "The naming structure for packages is hierarchical. The members of a package are class and interface types ... and subpackages."
A means of organizing package structures, radial encapsulation in Java then finds trivial expression: allow package dependencies only in the direction of the top level package. A dependency may fall on any package between the depending package and the top level package but on no other. So given the fully qualified package com.codetrumpet.base.app.model then the app package may only depend on packages com, codetrumpet and base but it must not depend on model. It is also forbidden to depend on, say, com.codetrumpet.scrape because it sees only com, codetrumpet and base when it looks in the direction of the top level com package: it does not see scrape, which is a declared, "To the side."
That same link above offers the unequivocal, "The hierarchical naming structure for packages is intended to be convenient for organizing related packages in a conventional manner, but has no significance in itself." It is precisely this significance that some principles attempt to capture and though this post concerns radial encapsulation alone, many wily programmers have unearthed such language-external guides to support the weight of their code.
A radial refactoring.
Figure 1 shows the hierarchical package structure of Spoiklin Soice (more accurately, it is the naming structure that is hierarchical, but we will apply that term loosely to the package structure itself). Suffering an orientation inversion, the top level package, com, appears at the bottom with subpackages layered above.
Figure 1: Spoiklin Soice package hierarchy.
Most of the details are unimportant but figure 2 shows a close-up of the proud view package and its brood of subpackages.
Figure 2: Close-up of the view package.
In figure 2 the view package has six subpackages:
com.edmundkirwan.base.spoiklin.link.view.sequence com.edmundkirwan.base.spoiklin.link.view.window com.edmundkirwan.base.spoiklin.link.view.option com.edmundkirwan.base.spoiklin.link.view.draw com.edmundkirwan.base.spoiklin.link.view.prep com.edmundkirwan.base.spoiklin.link.view.overview
These figures primarily show the package hierarchy and only hint at the inter-package dependencies. Figure 3, alternatively, presents a spoiklin diagram of the code base, skewering packages entirely on the basis of dependency, hierarchy be damned.
Figure 3: Actual package dependencies.
Figure 4 isolates the view package showing only its dependencies, both to and from other packages. The diagram reveals that seven packages depend on view (six being the subpackages of figure 2, plus the start package, a special case) and view depends on three others.
Figure 4: How the view package depends on others.
It was noted recently that the view package contains numerous implementation classes (otherwise it would have been a filled circle in figure 4, like the base package) that could be surgically extracted to leave view a pure interface repository (and thus enjoy slightly improved structural properties). This refactoring passed smoothly, resulting in a new common subpackage as shown in the close-up of figure 5.
Figure 6: The new common subpackage of the view package.
The extraction, however, produced a curious effect on the drawing of the inter-package dependencies, see figure 7.
Figure 7: The entire system with the common package extracted.
Compared to figure 3, figure 7 shows that the view package, with its 15 interfaces, now has no out-going dependencies: it depends on no other package. In itself, this presents no problem and merely highlights view's exclusive service to the rest of its subpackages; as radial encapsulation dictates that view's subpackages cannot see one another, then the subpackages publish interfaces into - and consume interfaces from - view in order to inter-operate, thereby manifesting the well-worn facade design pattern. Nevertheless, the view package now looks oddly anchorless, its predicament posing the question: is the view package now free to float to some other location within the package hierarchy? Indeed, what forces, if any, constrain package hierarchy?
As it happens, view is not free to drift at will up and down the package hierarchy. The reason concerns the nature of radial encapsulation itself. Radial encapsulation is not a principle but a means of realizing a principle, that principle being the minimization of potential coupling.
The potential coupling of a method is simply the number of methods to which it has access, thus it counts the maximum number of methods that could possibly be depended on. It measures not coupling but potential coupling. A method (usually) can see all methods in its class, all non-private methods in the classes/interfaces of its package, and all public methods in the public classes/interfaces of other packages. Given that a method cannot establish a direct dependency on one to which it has no access, minimizing potential coupling attempts to minimize the number of targets on which a dependency may form and thus joins ripple effect in battle in a statistical dimension. This explains programmers' ruthless minimizing of scope in Java programs. Radial encapsulation tries to reduce this scope even further by allowing a method see only those methods within its own package and in those packages that lie in the direction of the top level package. Radial encapsulation adds an extra-linguistic constraint to render inaccessible even public methods outside of this line-of-sight.
Concerning the view considered here, the question becomes not, "Whither can this view travel?" but, "Where will this view minimize the system's potential coupling?" It turns out that, despite the package's having no dependencies on any other, this requirement to minimize potential coupling shackles the package to its hierarchical location without hope of escape.
To begin with, view cannot rise any higher up the graphic (deeper into the hierarchy) as it would then elevate to the level of the six packages it serves, packages that would then not be able to see it. So its position below these six packages marks the upper limit of possible locations it might attain. Nor can it wander into a new branch of the hierarchy, say as a peer of link, because its subpackages depend on link so it must remain in their line-of-sight. Finally, it cannot fall any further below its current location because if it did, if it fell for example from com.edmundkirwan.base.spoiklin.link.view to reposition itself a package below link as in com.edmundkirwan.base.spoiklin.view.link, then it would become visible to every subpackage of link, a broadened exposure that would increase the system's potential coupling and violate the overarching principle. Thus radial encapsulation fixes view's hierarchical position brutally, despite its lack of out-going dependencies.
Indeed, all packages find themselves similarly immobile. Though this sounds restrictive (and it is), a powerful engineering benefit follows: programmers need not waste their energies carving package hierarchies from semantic containment schemes unproven in the war on costs. Once a programmer decides the services supplied and needed, and the consequent dependencies required, the package hierarchy comes for free.
(As it happens, this analysis lead to the discovery of a structural flaw in the source code inspected, a flaw clearly visible in figure 2: can you spot it?)
It is ripple effect management that validates or discredits syntactical structure and ripple effect flows from dependency.
In a normal Java program no rigid relationship exists - by design (see the Java Language Specification link above) - between package hierarchy and package dependencies, no correlation exists between package hierarchy and change propagation probability. The package hierarchy of a normal Java program serves no syntactic analytical purpose. Radial encapsulation, by contrast, fuses inseparably package hierarchy and package dependencies, rendering the former a unique derivation of the latter, re-tasking the package hierarchy as an aid to change-cost prediction.
The principle of potential coupling, which underwrites radial encapsulation, is not alone, however. Many principles attempt to imbue package structure with value beyond that afforded by the Java Language Specification. Programmers bloodied in vicious complexity battles do not limit their arsenal to JLS howitzers but fight dirty, weaponizing the tools others discard. The fields of software development lie weed-choked and fallow, all plowshares long-since re-forged.
Opinions expressed by DZone contributors are their own.