Tony Marston's Blog About software development, PHP and OOP

Decoupling is delusional

Posted on 24th June 2024 by Tony Marston
Introduction
What does "decoupling" mean?
What does "dependency" mean?
What does "coupling" mean?
What does "delusional" mean?
"Decoupling" doesn't mean what you think it means
The three pillars of "good code"
The four pillars of OOP
More articles compounding the error
Conclusion
References
Comments

Introduction

Whenever I read about someone saying that the decoupling of dependencies in software is a good idea which should be practiced by everyone I want to reach out and give that person a smack on the back of the head for the simple reason that they have completely misunderstood the relationship between "coupling" and "dependency". This means that the act of "decoupling" does not mean what they think it means, and is actually a bad idea instead of a good one. Programmers are often taught the following:

Decoupling is important because it makes the code easier to understand and maintain.

In this article I will attempt to explain why this statement is factually incorrect and that anyone who believes it is deluding themselves. Decoupling is a bad idea as a completely decoupled system will not work. Instead the programmer should aim for loose coupling which has the following effect:

Low/loose coupling is often thought to be a sign of a well-structured computer system and a good design, and when combined with high cohesion, supports the general goals of high readability and maintainability.

What does "decoupling" mean?

According to The Importance Of Decoupling In Software Development decoupling is described as:

Decoupling is isolating the code that performs a specific task from the code that performs another task. It's important because it makes the code more maintainable, reusable, and easier to test. When you decouple functionality in your application, there is less chance of introducing errors when updating or replacing part of a system. At first glance, this concept might seem simple: "Just don't have functions that do too much!" But as applications become larger and more complex, this becomes impractical to implement manually, so we turn to design patterns.

This sounds more like the Single Responsibility Principle (SRP) or Separation of Concerns (SOC) (which actually mean the same thing), neither of which has anything to do with coupling or decoupling. Note that these principles can be implemented using patterns such as the 3-Tier Architecture or Model-View-Controller.

A common design pattern for solving problems like these is called Inversion of Control (IOC). IOC is a software architecture pattern where control flow goes against traditional methods. Instead of having modules dependent on each other, IOC works by having modules depend on an intermediary module with abstracted services. This intermediary module seamlessly manages all operations and allows you to switch between different service providers.

I'm afraid I have to disagree with the idea that decoupling is related to Inversion of Control (IOC) as that has several different meanings:

  1. The original meaning, as described in this wikipedia page, was as follows:
    In software engineering, inversion of control (IoC) is a design principle in which custom-written portions of a computer program receive the flow of control from a generic framework. The term "inversion" is historical: a software architecture with this design "inverts" control as compared to procedural programming. In procedural programming, a program's custom code calls reusable libraries to take care of generic tasks, but with inversion of control, it is the framework that calls the custom code.
    This is commonly known as the Hollywood Principle (Don't call us, we'll call you) which can be implemented using the Template Method Pattern.
  2. Unfortunately the same wikipedia page states that it has also been used to mean something else entirely:
    The phrase "inversion of control" has separately also come to be used in the community of Java programmers to refer specifically to the patterns of injecting objects' dependencies that occur with IoC containers in Java frameworks such as the Spring framework. In this different sense, "inversion of control" refers to granting the framework control over the implementations of dependencies that are used by application objects.
    Because of the confusion created by having the same term mean two different things this second meaning was renamed as Dependency Injection by Martin Fowler in his Dependency Inversion Principle (DIP).
  3. The article mentioned at the top of this page introduces yet another description:
    Instead of having modules dependent on each other, IOC works by having modules depend on an intermediary module with abstracted services. This intermediary module seamlessly manages all operations and allows you to switch between different service providers.
    The term "intermediary" means "go-between" or "middleman", so if I start by having moduleA calling moduleB, which makes moduleA dependent on (and coupled to) moduleB, then to implement this version of IOC I would have to place this intermediary module (let's call it moduleX) between those two modules which would change the calling sequence from moduleA-to-moduleB to moduleA-to-moduleX-to-moduleB. The theory is that by doing so I would be decoupling moduleA from moduleB, and this is supposed to be a good thing.

The correct meaning of decoupling is described in wikipedia as:

Decoupling usually refers to the ending, removal or reverse of coupling.

As coupling and dependency mean exactly the same thing the act of "removing the coupling" must also mean "removing the dependency".


What does "dependency" mean?

According to wikipedia Dependency is exactly the same as Coupling. This means that if there is a dependency between two modules then those two modules are automatically coupled. If there is no dependency then they are not coupled.

The definition can be refined as follows:

Dependency is a state in which one object uses a function of another object. It is the degree that one component relies on another to perform its responsibilities. It is the manner and degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between modules.

You can only say that "moduleA is dependent on moduleB" when there is a subroutine call from A to B. In this situation it would be wrong to say that "moduleB is dependent on moduleA" because there is no call from B to A. ModuleB is a dependent *OF* but not dependent *ON* moduleA. ModuleA is dependent *ON* but not a dependent *OF* moduleB.

If moduleB calls moduleC then B is dependent on C, but there is no direct dependency between moduleA and moduleC as A does not call C. ModuleA does not even know that moduleC exists. However, there is now an indirect dependency between moduleA and moduleC as moduleA is dependent on moduleB which, in turn, is dependent on moduleC. ModuleA cannot complete its operation without the assistance of both moduleB and moduleC.


What does "coupling" mean?

According to wikipedia coupling is described as:

In software engineering, coupling is the degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between modules.

This "coupling" or "connection" is established when one module calls another.

Coupling is usually contrasted with cohesion. Low coupling often correlates with high cohesion, and vice versa. Low coupling is often thought to be a sign of a well-structured computer system and a good design, and when combined with high cohesion, supports the general goals of high readability and maintainability.

If there is a dependency then there is coupling, but the degree of coupling can be one of the following:

Loose coupling is good, tight coupling is bad, decoupling is disastrous.

The meaning of "coupling" should be quite clear and unambiguous - if moduleA makes a call on moduleB then those two modules are coupled because of that call. The strength of that coupling can be either loose or tight. Coupling therefore describes how one module is connected to another, how control flows from one module to another. If you decouple this relationship then you are removing this connection, which means that control cannot flow from one module to another. In this case the entire system will come to a grinding halt when it is within the first module as there is no way for it to connect to the second and subsequent modules.


What does "delusional" mean?

According to google Delusional is defined as:


"Decoupling" doesn't mean what you think it means

Far too many programmers think that "decoupling" is the act of taking a large piece of code which has many responsibilities/concerns and splitting or dividing it into smaller modules each of which has a single responsibility/concern. This is incorrect. That process is known as the Separation of Responsibilities, otherwise known as the Single Responsibility Principle (SRP) or the Separation of Concerns (SoC) which means exactly the same thing. It is this separation, splitting or dividing which produces the smaller modules while the coupling is limited to how one module calls another. One act follows the other, but they are not the same act.

As explained previously coupling and dependency mean exactly the same thing. If there is a dependency between two objects then they they ARE coupled. If there is no dependency between two objects then they ARE NOT coupled. Coupling has two flavours - loose coupling, which is good, and tight coupling, which is not so good. Dependency does not have any flavours, it is a binary condition - there is either a dependency between two objects or there is not.

If two objects are decoupled then the coupling is removed, which also means that the dependency is removed.

While the concept of "decoupling" may sound like a good idea in theory as it helps to implement Inversion of Control (IOC) I am afraid that in practice the result is not what is generally expected. As explained previously IOC has several possible meanings:

  1. The Hollywood Principle which can be implemented using the Template Method Pattern.

    There is no coupling/dependency involved here in either the principle or the pattern.

  2. The Dependency Inversion Principle (DIP) which is implemented using Dependency Injection.

    This is supposed to "decouple" the place where a dependent object is constructed from the place where its services are consumed. Each of those two acts - constructing an object then consuming its service - can be achieved with a single line of code which can be run consecutively, so there is no call from the first line to the second. If there is no call there is no coupling, and therefore there is nothing that can be decoupled. Coupling is what links one module with another, not one line of code with another. In this case the word "decouple" is wrong. It should be replaced with "separate" or "split" as it would be more accurate and less confusing.

  3. The act of introducing an intermediary module between an object as its dependency.

    If the term "decouple" means to remove the coupling then this is NOT what happens here. Coupling exists between two modules when one calls the other, but in this case the single module call has been replaced by two module calls. This means that the coupling has not been removed, it has doubled.

    As "coupling" also means "dependency" the removal of the coupling would also mean the removal of the dependency, but this is NOT what happens here. Instead of moduleA being directly dependent on moduleB it is now directly dependent on moduleX and indirectly dependent on moduleB.

This means that for options #1 and #2 there is no "decoupling", and for option #3 the original coupling has not been removed, it has doubled.


The three pillars of "good code"

It is possible to identify just three concepts which form the basis upon which all other programming principles emanate, regardless of the paradigm. There were identified in the paper Encapsulation as a First Principle of Object-Oriented Design (PDF) in which the author Scott L. Bain wrote the following:

The three pillars of "good code", namely strong cohesion, loose coupling, and the elimination of redundancies, were not discovered by the inventors of OO, but were rather inherited by them (no pun intended).

The meanings of the concepts are as follows:

  1. Cohesion - Describes the contents of a module. The degree to which the responsibilities of a single module/component form a meaningful unit. The degree of interaction within a module. The functional relatedness of the contents of a module. High cohesion is better than low cohesion.

    This starts with the premise that an application should be broken down into a collection of modules where each module handles a different part of the functionality instead of a monolith which tries to do everything. This concept has been refined to produce the Single Responsibility Principle (SRP) and Separation of Concerns (SOC). These principles can be implemented using the 3-Tier Architecture and/or Model-View-Controller design patterns to produce a structure such as the following:

    Figure 1 - MVC plus 3 Tier Architecture combined

    model-view-controller-03a (5K)

    Note that within an application each of the boxes above does not represent a single object but a collection of different objects which serve the same purpose but with different implementations. For example, in the RADICORE framework I have the following:

  2. Coupling - Describes how modules interact. The degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between two modules. Loose coupling is better than tight coupling.

    If Figure1 above a Controller calls a Model, so the Controller is coupled to the Model. A Model calls a DAO, so those those two are coupled as well. For a user transaction (use case) to complete its function it requires the cooperation of one of each of those four modules. Control flows from one module to another because of a call from one module to another. If you remove any of those calls by "decoupling" the the transaction will be unable to complete its function.

    If a particular Controller was tied to a particular Model then this would be an example of tight coupling. If a Controller could be coupled with any Model then this would be an example of loose coupling

  3. Elimination of redundancies - where code can be classed as redundant when there is more code than absolutely necessary. This can appear in the following forms:

The four pillars of OOP

While the previous section dealt with ideas which are applicable in any programming paradigm, this section focuses on the pillars of Object Oriented Programming and how they can be used to produce "good code".

  1. Encapsulation - The act of identifying an entity that will be of interest to your application, something with data and operations that perform on that data, then creating a "capsule", called a "class", with properties to hold the data and methods to perform the operations.
  2. Abstraction - The act of separating the abstract from the concrete, the similar from the different. After creating several classes you examine them looking for similarities and differences. As each table is subject to the same operations - Create, Read, Update and Delete (CRUD) - you can move these operations to an abstract table class. The abstract class then holds the similarities while the differences can be isolated in any number of concrete subclasses.

    This topic is discussed in greater detail in The meaning of "abstraction".

  3. Inheritance - The act of combining common methods and properties in a superclass (such as an abstract table class) in order to create a subclass (such as a concrete table class) which need only contain the differences.
  4. Polymorphism - The act of creating several objects with the same method signature but a different implementation so that the signature can be called on an object whose identity is not known until run time. In this way the object which calls that signature can be reused with any of its dependent objects. All my concrete table classes share the same method signatures because they are inherited from the abstract table class.

The use of an abstract class then enables the Template Method Pattern to be employed. This pattern is at the heart of framework design as it illustrates the original meaning of Inversion of Control (IOC) which is also known as the Hollywood Principle - "Don't call us, we'll call you". The abstract class contains the invariant/fixed methods while the concrete subclass only need contain implementations for those "hook" methods which are necessary to supply the custom behaviour.

The mechanism by which the dependency can be switched a run time is known as Dependency Injection, which is what I use to inject Models into Controllers and to inject Models into Views. This makes my Controllers and Views reusable with any Models because the coupling is as loose as it is possible to be.


More articles compounding the error

While researching this misuse of the term "decoupling" I was amazed at the number of other articles which continue to perpetuate the same misconception. All it takes is one person to attach the wrong meaning to a word, then another person to add their own ill-conceived opinion into the mix, until after a while the original meaning has been corrupted beyond recognition. It is like the game of Chinese Whispers where the original message of "Send reinforcements, we are going to advance" is changed to "Send three and four pence, we are going to a dance".

Here are some of the erroneous statements I have found in other articles:

To maintain a high level of performance in such feature-rich applications, you might consider breaking the application up into different services that talk to one another - that is, decoupling your application's components.

https://thenewstack.io/how-decoupling-can-help-you-write-better-software/

"Breaking the application up" is the same as "separation", not the reverse of "coupling".

Decoupled Architecture is a software design approach that aims to enhance flexibility, scalability, and maintainability by reducing inter-dependencies between different components or modules of a software system. It promotes a modular design where individual components are designed to function independently and communicate through well-defined interfaces and/or protocols.

In simple words, the idea is to isolate changes in one component from affecting others, making it easier to modify, replace, or extend parts of the system without causing a ripple effect of changes across the entire Software Product or system.

https://medium.com/@saurabh.engg.it/decoupled-architecture-microservices-29f7b201bd87

"Reducing inter-dependencies" can only be achieved by reducing the number of modules which need to be connected.

"Reducing the ripple effect" is achieved by reducing the degree of coupling to as loose as possible, not by removing the coupling altogether.

Dependency injection is a design pattern that helps decouple software components by injecting dependencies into a component from the outside, rather than having the component construct them itself.

https://www.linkedin.com/pulse/decoupling-dependencies-software-architecture-path-scalable-childs-vl1ne/

Dependency injection is not related to either coupling or decoupling, it is a mechanism used to take advantage of polymorphism.

Decoupling, in software development, refers to the practice of breaking down a software system into smaller, independent components or modules. These modules are designed to be loosely coupled, meaning they have minimal dependencies on one another.

https://blog.covibe.us/the-pitfalls-of-excessive-decoupling-in-software-development-striking-the-right-balance/

I'm sorry, but "loosely coupled" does not mean reducing the number of dependencies, it means reducing the possibilty of the ripple effect when a change in one module necessitates a corresponding change in the other.

Decoupling is taking a functionality or set of functionalities out of an existing application, and making it completely independent that it can act and be developed on its own.

https://www.itarch.info/2020/05/decoupling-architecture.html

Yet again this is confusing "separating into smaller modules" with "decoupling one module from another".


Conclusion

Object Oriented Programming is an art, not a science, and it is constantly evolving. As well as new features being added to existing languages, some practitioners go so far as to create new languages. Others create new patterns, new principles, or new solutions in the form of libraries or frameworks. Unfortunately there is no control over who can contribute, and new ideas are not vetted before they are launched on an unsuspecting world, so it has been a case of too many cooks spoil the broth as some of these ideas have caused conflict with others. Some have been explained so badly and cause so much confusion that they lead to mis-interpretations which then leads to faulty implementations. Some of this confusion is caused by people using an existing term to mean something completely different, like the description for Inversion of Control (IOC) provided in the introduction. Others go so far as to create a new principle to cover a topic that has already been addressed with another name, a prime example being the Single Responsibility Principle (SRP) and the Separation of Concerns (SOC). To me it is obvious that the terms "responsible for" and "concerned with" mean exactly the same thing, but the descriptions of the two principles vary so much that some people are easily confused.

The term decoupling means different things to different people, and sometimes it doesn't mean what you think it means. To me the term means only one thing - you take something which is coupled and you undo or remove that coupling.

Two modules are coupled when one module calls the other. The act of "decoupling" or "uncoupling" therefore means to remove that coupling which means to remove the call that couples them together. By removing the call you are cutting off the flow of control, and by cutting off the flow of control you cause your software to hit a dead end, a cul-de-sac, and it comes to a grinding halt as it has nowhere to go. How can this be a good idea?

Here endeth the lesson. Don't applaud, just throw money.


References

The following articles discuss decoupling as if it were a genuine concept:

These are reasons why I consider some ideas on how to do OOP "properly" to be complete rubbish:


counter