Modularity in Web Development: How to Reduce Coupling and Maximize Cohesion in Your Code

I never imagined my high-school physics teacher’s advice would apply to software design principles. When students struggled in his class, he’d say, “Look, I was a terrible problem-solver in college until I learned how to crack problems into as many simple, digestible pieces as possible, and solve each piece on its own sheet of paper.”

He was onto something. It turns out that physics is not the only discipline that embraces modularity; it has been an established pillar of well-implemented software design since 1974, when W.P. Stevens, G.J. Myers, and L.L. Constantine published their highly influential piece, Structured Design [1]. What’s more, Constantine observed that “programs that were easiest to implement and change were those composed of simple, independent modules.”

Constantine is describing what is known as “coupling,” a way to measure simplicity within modules and their independence from other modules. High coupling, or tight-binding, describes modules that rely on acute awareness of other modules, or internal variables of other modules. On the other hand, low coupling, or loose-binding, describes modules whose internal behavior is simple, readable, serves a single purpose, and is entirely independent of other modules. Loosely bound modules are also more observable, meaning that a reader may easily see how actions are performed or how data is modified.

Ironically, coupling itself is tightly bound to the concept of cohesion. Cohesion defines and evaluates the quality of relationships between elements in a module. It is like a paste that glues all elements within a module together. Cohesion is desirable in robust software design, as high cohesion equates to loose coupling, and vise versa. Cohesion can also be used to “[give] the designer an idea about whether the different elements of a module belong together in the same module” [2]. There are several different ranks of cohesion, and developers can assess the quality of their software design by comparing their modules to the following categories:

  • Coincidental
    • This type of cohesion suggests that there is absolutely no unifying qualities among the elements in a module.
  • Logical
    • Logical cohesion describes a relationship between elements where they all perform similar tasks. An example is a math module, for example, that deals with various math operations.
  • Temporal
    • This is the same as logical cohesion, except that the elements within a module also execute at a shared time.
  • Communicational
    • With communicational cohesion, the elements within a module pass the same data (via arguments, for example) to operate on.
  • Sequential
    • This type of cohesion is similar to communicational. More specifically, however, it requires that the output from one element be the input to the next.
  • Functional
    • This is the highest form of cohesion. Functional cohesion describes a relationship between elements where they are all part of a greater cause. That is, each element within a module is a vital organ necessary to achieve a single, unified goal.

These categories are in order form least-cohesive to most-cohesive. Functional cohesion, being the highest form of cohesion, is what one should strive for when developing modules to minimize coupling. A cohesive application is fluid and adaptable to numerous contexts.

If the software design is stiff as cement, however, then any changes applied to the codebase will invariably rupture the application’s structure. Unfortunately, HTML, CSS, and Javascript are fundamentally coupled in a rigid fashion, making it arduous for developers to practice dynamic or decoupled design. Although creating web applications entirely free of coupling isn’t feasible, there are design patterns front-end developers can implement to minimize coupling and maximize modularity, readability, and independence between modules.

1 Javascript

In an effort to maximize modularity, traditional OOP concepts such as classes, encapsulation, and polymorphism are upheld as tenants of proper software design in any computer science class. But such design principles prove difficult to implement in the context of web development, as the languages and frameworks that define it are by nature tightly coupled.

Javascript, for example, is not fully object-oriented by design, making it easy for developers to write workable spaghetti code that’s as intelligible as ancient Sumerian. Although this may produce positive results like bolstering Advil’s profits and feeding the economy, it also leads to tight coupling and structurally weak applications that are impossible to read.

1.1 Reduce Coupling by Using Closures

Use closures instead of global variables unless there’s a good reason to do so otherwise. Closures align with the goals of encapsulation and modularity. They create purposeful interfaces between their internal contents and external interactions. As outlined in Structured Design, interaction coupling is the most desirable form of coupling [1]. In other words, passing arguments through parameters is the most preferred method of coupling.

function showSlogan(sloganName) {
  // the variable slogan is only available within this scope
  var slogan = sloganName
  // the function definition is only available within this scope 
  function tellSlogan() {
    return slogan;
  };
  return tellSlogan;
};
// it can be accessed and called this way
var func = showSlogan("I scream you scream we all scream for ice cream!");
var myNewSlogan = func();

The clear benefit of closures, when talking about coupling and cohesion, is the scoping of data and behavior. If we decide to change the slogan variable name to coolerSloganmyNewSlogan remains unaffected. Conversely, if myNewSlogan directly referenced slogan by name, such a change would break it. This may seem trivial given such a simple example, but one can imagine a situation where a variable is referenced in 50 different places. Changing something as simple as its name would mean sorting through all 50 references and enacting the same change.

1.2 The Publisher/Subscriber Design Pattern

The publisher-subscriber design pattern reduces coupling and dependence between two modules by creating a well-defined, event-driven interface. The subscriber signs up to receive data from the publisher, but knows nothing about its innerwoking. It’s and event-based communication system, where one module sends or “emits” data to other subscribing modules. Since the implementation of these modules is separate, and their communication is regulated by a well-defined, rigid interface, modification or changes made to one module do not directly affect the other. If, for example, a subscribing module breaks, it doesn’t subsequently damage the publishing module.

At Cloverhound, we applied a publisher/subscriber design pattern when creating the ACD that agents use to handle calls with Zomnio. This is an entirely front-end, Javascript application, where the subscribing module handles UI modification and the publishing module acts as a back-end. With this robust design, changes can be made to the front-end module without worry of inadvertently breaking the back-end module, and vise versa.

Here’s an example of a coupled system that doesn’t use the publisher/subscriber design system:

function Gorilla(name, specialPower, isCute) {
  this.name = name;
  this.specialPower = new SpecialPowers.getCoolPower("gorilla");
  this.isCute = isCute;
};

Gorilla.prototype.applyPower = function() {
  console.log(this.specialPower);
};

The Gorilla object is highly reliant upon the SpecialPowers object. It has a direct reference to the SpecialPowers class, resulting in high coupling. If the SpecialPowers getCoolPower(animal) method stops working, the Gorilla object will be damaged as well. Now consider the same scenario using a publisher/subscriber pattern:

function Gorilla(name, specialPower, isCute) {
  this.name = name;
  this.specialPower = "none";
  PB.subscribe(name, function(data){
    this.specialPower = data;
  });
  this.isCute = isCute;
}

Gorilla.prototype.applyPower = function() {
  console.log(this.specialPower);
}

In this scenario, the Gorilla object nows nothing about the SpecialPowers object– a sign of low coupling. What’s more, if the SpecialPowers stops working properly, the Gorilla object isn’t affected.

2 HTML

All too frequently, it’s difficult to read javascript code and understand its relationship to the DOM objects it manipulates without significantly digging around. Such tight coupling not only decreases readability, but also makes it difficult to enact changes to the code base, as changing one function or HTML element has a rippling effect on all other functions or variables tied to its name or definition.

On top of that, using unspecific class names or hierarchical references to DOM elements greatly increases coupling and the negative repercussions of deciding to change a single variable name or function.

In order to mitigate these issues, use specific class names that properly communicate their function.

2.1 Using Class Names that Communicate Their Purpose

  • Style specific classes
    • Prefix classes only used for styling with style-*
      • For example, style-submit-buttom
  • Javascript specific classes [3]
    • Prefix classes that are manipulated by Javascript with js-*
      • For example, js-menu-heading
  • Other UI specific classes [3]
    • Prefix classes used for UI toggling manipulation with is-*
      • For example, is-visible or is-green

The ultimate goal with using specific classes that communicate their function is to avoid ambiguous, multi-purpose classes. If a class is being used for styling and Javascript hooks, it not only complicates readability but also dramatically increases coupling. Any change on that class name will affect more components than it should. Use classes generously, so that each class serves a clear purpose.

2.2 Using the HTML5 Data Attribute

Having a large amount of interwoven connections between modules gets overwhelming. A large amount of connections between modules simultaneously increases the pipelines along which errors can propagate. To mitigate this, one can use the HTML5 data attributes to minimize the number of JQuery DOM references.

Note: this strategy has a tradeoff. It may consume up to twice as much time to process as using document.getElementById().

Consider an element that contains some arbitrary initial data attribute:

The content stored in the <div> under the data attribute can be accessed and changed as follows:

var div = $(".some-class")
div.dataset.content = "new content"

This reduces the number of DOM references necessary to modify a small amount of content in HTML. Without the data attribute, one would at least need to include another nested <span> element to house the data. In this heuristic example, going from 1 to 2 doesn’t seem significant, but consider going from 20 to 40, or 100 to 200. Doubling the number of coupled connections becomes increasingly more significant the larger the project or codebase becomes.

3 CSS

Another way to reduce coupling is to avoid referencing DOM objects via their hierarchal family tree. Doing so lessens the chances that changes to the HTML object tree will inadvertently affect specific CSS references.

This:

.style-menu-heading {}

As opposed to this:

.menu section < p < ul {}

4 The bottom Line

Reducing coupling and increasing cohesion results in an intuitive codebase architecture. It properly decomposes a complex problem into digestible pieces that are reliable, reusable, dynamic, and readable by other programmers. It’s incredible how much time can be wasted deciphering incongruent blocks of badly styled code that with proper design could be easily understood. The efficiency of collaboration and adaptation may be tremendously upgraded by employing the principles outlined above. What’s more, as the authors of Structured Design point out, it’s significantly harder to restyle or reduce coupling once an application has already been crafted in a highly coupled way [1]– so do it the right way from the get-go.

5 Works Cited 

[1] W.P. Stevens, G.J. Myers, L.L. Constantine, “Structured Design.” IBM Systems Journal 13, 2 (1974), 115-139

[2] Jalote, Pankaj. A concise Introduction to Software Engineering. London: Springer, 2008.

[3] https://philipwalton.com/articles/decoupling-html-css-and-javascript/