Skip to main content

  1. Blog Posts/

From Modular Coupling to Quantum Connascence: Understanding Modularity in Modern Software Architecture

·1987 words·10 mins
Architecture quantum: An independently deployable artifact with high functional cohesion and synchronous connascence. - Fundamentals of Software Architecture by Mark Richards & Neal Ford.

Modularity #

Before we try to understand what quantum connascence is, let’s take a brief tour of the modular concepts.

Modularity can be defined as a logical grouping of related code. All languages/frameworks provide mechanisms to group code. Go(golang) has modules and packages, Java has packages, Angular has modules and components, & .NET has namespaces and so on. It is considered an implicit architectural characteristic and is much talked about in design discussions. But rarely do people talk about measuring modularity.

Consider for a moment that you are asked to refactor a codebase that was perhaps written in haste. How would you go about your task? Outright starting to read the code following from an entry point may be intuitive to many software engineers. Or maybe start with the API documentation- if at all there was time to build one. One may even run some code coverage tests and code smells to figure out the quality of the current code. But think of one of the most common asks when someone assigns a refactoring job -

“The new code should be modular and loosely coupled”.

Half the people just say this because they know this is a sound principle in theory. Or that they have heard so. However, if your retort was to ask -

“Well, how modular is it now?”

You probably won’t be getting a quantifiable answer. It is possible to measure modularity though.

Cohesion #

Cohesion is a measure of how things are grouped because they need access to the same things. A cohesive module is one where all fragments must be packaged together to achieve useful results, and breaking them apart would require inter-modular communication - resulting in an overhead of calls.

Types of Cohesion #

Functional This is a positive case of cohesion. All relations within a module are essential for the functioning of the module.
Sequential Think of this like a chain of functions where one's output becomes the other's input.
Communicational This is an if-this-then-that kind of scenario. Think of an account closing function triggering another to delete any records of the user.
Procedural Two modules are tied together in the order they must execute code.
Temporal Temporal cohesion is seen in many daemon jobs. Things that must work together on precise timing. Code must be executed in sync with a clock.
Logical This is a kind of cohesion where things are grouped logically, but that is not necessary for their functioning. Think of a utils package in your code base where you house all your helper functions.
Coincidental Elements are just placed in a single file in the source code without any functional or logical reasoning. Just happenstance.

Measuring Cohesion #

A common metric to determine cohesion is The Chidamber and Kemerer Lack Of Cohesion in Methods or simply, LCOM. Going by the fact, that the formulas for LCOM have gradually become more and more complex - such as the one below:

\(LCOM96b = \dfrac{1}{a}\sum_{j=1}^{a}\dfrac{m-\mu(A_j)}{m} \)

…we can, for now, assume a simpler definition of LCOM:

The sum of sets of methods not shared via sharing fields.

Tools to Analyze Cohesion #

jPeek helps measure cohesion in Java artifacts. LCOM4Go is a relatively less used, but effective package for Go.

Note that there is also substantial work done on LCC or Lack of Cohesion in Classes. A 2004 paper by Linda & Mourad Badri argues in favor of this method over LCOM for object-oriented systems.

Coupling #

Coupling is perhaps one of the most overused words in technical discussions. Here’s what Merriam-Webster defines coupling as:

A device that serves to connect the ends of adjacent parts or objects.

While that definition may be off-base in computer science terminology, it does give an idea about what we are getting into.

Afferent and Efferent Coupling #

Afferent coupling measures the number of incoming connections to a code artifact (component, class, function, and so on). Efferent coupling measures the outgoing connections to other code artifacts.

Measuring Coupling #

There are three widely accepted metrics to calculate coupling in a code base.

Abstractness #

Abstractness is the ratio of abstract elements to non-abstract ones. In the OOPs world, abstract elements would be interfaces and abstract classes while non-abstract ones would be their implementations.

\(A = \dfrac{\sum{m^a}}{\sum{m^c}+\sum{m^a}} \)

In the above equation, A is abstractness, \( m^a \) represents abstract elements, and \(m^c\) represents concrete implementations. Thus, making abstractness equal to the ratio of the sum of abstract artifacts to the sum of the concrete and abstract ones. A pretty high abstractness means that there’s a lot of unnecessary generalization in the code.

Instability #

Just like abstractness, instability also refers to a ratio. This time between efferent and afferent coupling. Its self-explanatory equation is as below:

\(I = \dfrac{C^e}{C^e + C^a} \)

The higher the number for instability, the higher chances of code breakage when implementing any changes.

Distance from the Main Sequence #

Distance from the main sequence is a metric that combines abstractness and instability to determine the usefulness of a codebase. Its simple equation is:

\(D = |A + I -1| \)

D is most useful when plotted on a graph:

Distance From The Main Sequence
Normalized Distance from the Main Sequence

Furthermore, this can be interpreted using the below diagram and where D lands on it.

Distance From The Main Sequence
Zones of Uselessness and Pain

Historically, such calculations have proven useful in refactoring exercises with a target to bring D into the green zone.

Tools to Analyze Coupling #

There exist many official and community-contributed packages to measure these metrics. For Go, Effrit does a nice job of calculating afferent and efferent metrics. Jdepend is one of the popular ones for Java. In the Typescript/Javascript world, Dependency-Cruiser is a depend-able library!

Now that we have discussed cohesion and coupling, let’s dive into the concept of connascence…

Connascence #

The book, What Every Programmer Should Know About Object-Oriented Design by Mellir Page-Jones, gave the first-ever definition of connascence in 1996.

Two components are connascent if a change in one would require the other to be modified in order to maintain the overall correctness of the system.

Static Connascence #

Static connascence refers to the degree to which afferent or efferent coupling exists in a system. Here are the types of static connascence:

Connascence of Name (CoN) Different components must agree on the name of something - either a variable or a method. This is the most common type of connascence in modern code bases where IDE-driven refactoring makes it trivial
Connascence of Type (CoT) In statically typed languages such as Java, Typescript or Go, connascence of type refers to the case when components must agree on the type of an entity.
Connascence of Convention (CoC) Also known as Connascene of Meaning (CoM), one can find connascence of type in a constants file where a certain constant is used at multiple places and the file must be referred to get its true meaning.
Connascence of Postion (CoP) CoP is most often seen in the order in which variables must be placed when calling a function or a method. The method definition hover in IDEs usually shows what position is used in what order.
Connascence of Algorithm (CoA) When two different modules/components must agree on an algorithm to understand something. Think of hashing algorithms such as Argon2, Bcrypt or Scrypt.

Dynamic Connascence #

Dynamic connascence is the connascence of runtime. It refers to calls between components when an application is actually running. Here are the types of dynamic connascence:

Connascence of Execution (CoE) This can be related to procedural coupling when things must happen in a particular order for it to work. Imagine sending a pub/sub message before the line of code that sets the value for the message.
Connascence of Timing (CoT) Race conditions are a typical case for this type of connascence. For example, when two Java threads try to execute something at the same time.
Connascence of Values (CoV) When different values are related to each other through logic. For example, a grid defined as a fixed matrix in the code must adhere to the grid boundaries when dealing with it.
Connascence of Identity (CoI) A pub/sub topic name can be thought of as a connascence of Identity. Both publisher and subscriber must refer to the same identity of the queue at runtime.

Strength, Locality & Degree #

While the different types of connascence described above are useful to understand behavior, additional properties have also been defined to assist in refactoring code.

Strength can be understood as a factor of the ease with which a developer may refactor that type of coupling. This diagram gives clear guidance on how to go about refactoring coupled code.

Strength on Connascence
Refactoring code - Dynamic to Static Connascence Rankings.

Locality refers to how close to each other does connascence occur in the code base. For example, connascence of a certain type in between classes within a single component is less worse than that between two different components.

Degree refers to the size of the impact of a particular type of connascence. For instance, if there exists a Connascence of Identity (CoI) that impacts only a few classes/components then its degree may be less than a Connascence of Name (CoN) that affects the whole codebase. Note that CoI (dynamic) is at the bottom of the strength chart, while CoN (static) is at the top.

Wise Words #

The book that introduced connascence also offered three guidelines for modularity improvement:

  • Minimize overall connascence by breaking the system into encapsulated elements
  • Minimize any remaining connascence that crosses encapsulation boundaries
  • Maximize the connascence within encapsulation boundaries

Jim Weirich later gave two golden rules for the same.

  • Rule of Degree: convert strong forms of connascence into weaker forms of connascence
  • Rule of Locality: as the distance between software elements increases, use weaker forms of connascence

You can find his famous “Connascence Examined” talk here on youtube.

Architectural Quanta #

What we have analyzed up until now is extremely useful for developers and architects alike, but lacks certain aspects of modularity common to modern systems. Coupling isn’t limited to structure. We must look at a system holistically to uncover the semantically bound parts and reveal functional cohesion. To do so, modern architecture defines a newer level of granularity - The Architecture Quantum.

We defined Architecture Quantum at the very beginning of this post, but here it is again for proximity relevance:

An independently deployable artifact with high functional cohesion and synchronous connascence.

There are three parts to this definition:

  • Independently Deployable: This denotes a grouping that could be deployed on its own. Whether it would function independently is as relevant to the whole system is a different matter.
  • Functional Cohesion: A component for cart in an e-commerce system may be seen as exhibiting high cohesion with all its attributes & methods relevant to only items that are added to and subsequently removed from it.
  • Synchronous Connascence: This could be understood simply by the fact that most REST calls are synchronous, while broker-based communication is asynchronous. Factors such as relative scaling come into play when considering these types of connascence.

Unifying Coupling, Connascence & Quantum #

Coupling, Connascene & Quantum
Coupling, Connascence & Communication Quantum.

This diagram maps each type of coupling to its connascence counterpart and also adds the communication (sync/async) quantum into the mix.

Such analysis is helpful not only in refactoring but also validating new architectures. As with everything else, good architecture is a form of feedback/learning loops and iterations.

Here’s a sample diagram showing where restructuring fits in a component identification lifecycle:

Component Identification Lifecycle
Restructuring is always an iterative process.

This post has its inspirations from various chapters of The Fundamentals of Software Architecture & is written with an aim to enrich and unify scattered information from the book. All diagrams in this article are taken from said book.