Connascence as a vocabulary to discuss Coupling

Matheus Sales

As software engineers, we often discuss complex concepts that might lead to nebulous and endless discussions. Coupling is one of these complex concepts that raise common questions. How harmful is coupling? How can we measure coupling? What are the different types of coupling? Connascence introduces a shared vocabulary for determining different types and the severity of the coupling in our systems, answering these questions, and making discussions around coupling less nebulous and goal-oriented.

What is Connascence

Let’s define the concept of Connascence:

Two pieces of software share connascence when a change in one requires a corresponding change in the other.

Connascence allows us to go beyond the binary of “coupled” and “not coupled”, serving as a tool to measure coupling and describe how bad it is under different levels and kinds.

How we measure the Connascence

  1. Strength: Stronger connascences are harder to discover, and/or harder to refactor.
  2. Degree: Describes how many components are coupled. Components with a large number of connascent entities are a more significant issue than just a few connascent entities.
  3. Locality: Describes how close the coupled components are. A high locality is better and means coupled components are in the same module.

Connascence = Strength x Degree / Locality

Types of Connascence

Connascence comes in many forms. Below are three of the most common that appear in Rails applications and how to avoid them. They are listed from weakest connascence to strongest, so it is worthwhile to refactor from one of the later forms on the list to one of the earlier forms.

Connascence of Name (CoN)

Connascence of name happens when multiple components must agree on the name of an entity. Constants and object names are an example of this form of connascence: if the name of an object changes, every reference to that object needs to change. It’s the weakest form of connascence and almost always unavoidable.

class Order
  def Order.most_recent
    ...
  end
end

Strength -> πŸ’ͺ (weakest form of connascence)
Degree -> πŸ“ˆ (only one component)
Locality -> πŸ“πŸ“πŸ“πŸ“πŸ“ (inside same model)

This code has an explicit dependency between our Order class name and how we defined our Order.most_recent class method. Ruby provides a way to refactor this code and remove the CoN (Connascence of Name).

class Order
  def self.most_recent
    ...
  end
end

Strength -> (zero connascence)
Degree -> πŸ“ˆ (only one component)
Locality -> πŸ“πŸ“πŸ“πŸ“πŸ“ (inside same model)

Even though CoN is often harmless and expected to exist, it should be eliminated when possible so this was a nice refactor.

Connascence of Position (CoP)

Connascence of Position happens when multiple components must agree on the position/order of their values. Let’s see an example to explain this type of connascence better.

class Order
  # Always add a new status to be the last element of the array
  enum status: [:pending, :published, :delivered]
end

Strength -> πŸ’ͺπŸ’ͺ (stronger than connascence of name)
Degree -> πŸ“ˆπŸ“ˆπŸ“ˆ (model and database are coupled)
Locality -> πŸ“πŸ“πŸ“ (models are distant from the database)

This example is one of the most common CoP in Rails codebases and it has a low Locality because it’s coupled to a distant database. Declarations like that often come with a comment, explaining that the position of the enum items matter – but fortunately, it has an easy fix: it is possible to use a hash instead of an array, which would explicitly map the enum values instead of relying on their implicit position within the array. This refactoring changes from CoP to CoN, thus reducing the Strength of the connascence.

class Order
  enum status: { pending: 0, published: 1, delivered: 2 }
end

Strength -> πŸ’ͺ (connascence of name)
Degree -> πŸ“ˆπŸ“ˆ (model and database are coupled)
Locality -> πŸ“πŸ“πŸ“ (models are distant from the database)

Connascence of Meaning (CoM)

Connascence of Meaning happens when multiple components must agree on the meaning of particular values. In its most basic form, it’s all about magic values.

class Order
  def deliver!
    if self.type == '001'
      ...
    else
      ...
    end
  end
end

Strength -> πŸ’ͺπŸ’ͺπŸ’ͺ (connascence of meaning)
Degree -> πŸ“ˆπŸ“ˆπŸ“ˆ (multiple models checking this condition or using this value)
Locality -> πŸ“πŸ“ (multiple models, database)

The meaning of type == '001' is unclear, and such a conditional tends to be repeated throughout our codebase, leading to increased Locality. If that value changes in one place, it must also change in another.

CoM can be improved to CoN by moving magic values to named constants and creating convenient methods to be used externally. However, in doing so, we have increased the amount of CoN but again lowered the Strength of connascence.

class Order
  B2C_TYPE = '001'

  def b2c?
    type == B2C_TYPE
  end

  def deliver!
    if b2c?
      ...
    else
      ...
    end
  end
end

Strength -> πŸ’ͺ (connascence of name)
Degree -> πŸ“ˆ (single method to check)
Locality -> πŸ“πŸ“πŸ“πŸ“

In Summary

Connascence’s arguably most important benefit is to give developers a common vocabulary to talk about different types of coupling and DRY principles in a less abstract way, allowing software engineers to easily share experiences by referring to a standard set of nouns, resulting in productive discussions on code reviews and pull requests.