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

Decoupling is delusional

Posted on 24th June 2024 by Tony Marston

Amended on 25th January 2025

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 three pillars of OOP
More articles compounding the error
The Pitfalls of Excessive Decoupling
Achieving Balance: Decoupling in Software Design
Conclusion
References
Amendment History
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. The term "decoupling" means the removal of "coupling", but instead it is being used, incorrectly, to describe the act of "separation" as described in the principle known as Separation of Concerns (SoC). Decoupling, which involves the removal of coupling, 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.

This is saying that low/loose coupling can be combined with high cohesion which immediately identifies them as separate and different concepts which may work in unison. Yet, as you will see in the remainder of this article some authors treat them as being the same concept.


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.
...
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.

This description of decoupling is echoed in How Decoupling Can Help You Write Better Software which states the following:

Decoupling is a powerful tool that can help alleviate many issues arising from the ever-growing complexity of modern software. 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.

These statements are absolute rubbish. The act which is being described here, that of breaking up a large chunk of code into smaller piece, is called "separation" (as in Separation of Concerns (SoC) or the Single Responsibility Principle (SRP)) and not decoupling which is the reverse of coupling. Note that these principles can be implemented using patterns such as the 3-Tier Architecture or Model-View-Controller as shown in Figure 1 below.

I also 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 which involves a mixture of invariant and variable methods which are inherited from an abstract class. The variable methods are defined in the abstract class as empty stubs (or "hook" methods) which can be overridden in each concrete class to provide custom behaviour.
  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". ModuleA can be coupled to or dependent on moduleB by virtue of the fact that there is a call from moduleA to moduleB. The only way to remove that coupling is to remove that call. Introducing a call to an intermediate object replaces a single module call with two module calls, and it does not remove the dependency, it merely changes it from direct to indirect.


What does "dependency" mean?

According to wikipedia Dependency is exactly the same as Coupling. This means that if there is a direct 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 as there is no inter-module communication.

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.

    I have debunked the idea that this "decoupling" is the aim of Dependency Injection in The true purpose of Dependency Injection.

  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.

There are also some people who claim that the purpose of an Object Relational Mapper is to "decouple" the database from the software, but yet again this is pure bunkum. Everyone knows that it is to cure that self-inflicted problem known as Object-relational impedance mismatch.

I recently came across an article which suggested that the purpose of an Object Relational Mapper (ORM) was to decouple the database from the software code. This again is using the term coupling in the wrong place and for the wrong thing. In this instance the term should be "connected", not "coupled" and that connection takes place where the code calls an API which instructs the database to perform an operation on its behalf. If you de-connect from that database (i.e. remove that connection) then there will be no database activity and the software will be unable to achieve its purpose. Everybody knows that the true purpose of an ORM is to cure that self-inflicted problem known as Object-relational impedance mismatch. If you don't build your database and your software using incompatible designs then you won't have to deal with that incompatibility.


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)

    Each of those four modules is responsible for only one area of logic within the application, so all logic concerning HTML forms is contained within the Presentation Layer, all SQL logic is contained within the Data Access layer, and all business logic is contained within the Business/Domain layer. Note that the logic for receiving an HTML request is entirely different from sending back a response, which is why the Presentation layer in my software has been split into two.

    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" then 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 three 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. 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.

    Note that the practice of inheriting from one concrete class to create a different concrete class should be avoided as you are liable to inherit methods which are out of place in the subclass. The best advice is to only ever inherit from an abstract class as none of the inherited methods should cause a conflict.

  3. 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.

Some people seem to think that abstraction is the fourth pillar of OOP, but I disagree. Firstly, it is a mental process which can be described as the act of separating the abstract from the concrete, the similar from the different. It is only after the similar has been identified that the necessary code can be moved into reusable/sharable modules (such as abstract classes) while the different can be moved to unique modules (such as concrete classes). This topic is discussed in greater detail in The meaning of "abstraction".

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 dependent object can be switched at run time is known as Dependency Injection, which is what I use to inject Models into Controllers and to inject Models into Views. This is because all these objects share the same set of method signatures. 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 so that it is 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 (separating) the creation of an object from the place where its methods are consumed is not the purpose of dependency injection, it is the means by which it achieves its purpose.

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 possibility of the ripple effect when a change in one module necessitates a corresponding change in the other. This involves making the function signature to be as generic as possible. Low coupling refers to a relationship in which one module interacts with another module through a simple and stable interface and does not need to be concerned with the other module's internal implementation.

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".


The Pitfalls of Excessive Decoupling

To counter the argument that Decoupling is important because it makes the code easier to understand and maintain I found the following comments in Decoupling in Software Development: Striking the Right Balance:

Too Much Complexity

One of the most significant risks associated with excessive decoupling is the introduction of excessive complexity into the codebase. When every aspect of a software system is broken down into numerous microservices or modules, it can lead to interconnected parts that are challenging to manage and understand.

Developers may find themselves spending an extra amount of time navigating through the code, trying to understand the relationships between these modules. Debugging can become a daunting task, as issues might arise from unexpected interactions between seemingly isolated components. As a result, the overall complexity of the system increases, diminishing the benefits of decoupling.
Steep Learning Curve

Excessive decoupling can lead to a steep learning curve, particularly for new developers joining a project. When there are too many layers of abstraction and interconnected components, it becomes challenging for team members to understand the big picture quickly.

Each module may have its own unique way of functioning, its own set of dependencies, and its own peculiarities. Navigating this maze of components can be overwhelming for newcomers. Understanding the interactions between these modules and the flow of data within the system can require a significant investment of time and effort.

Moreover, maintaining documentation for an overly decoupled system can be a herculean task. Documenting not only the individual modules but also their interdependencies and how they contribute to the overall functionality of the system can become a full-time job.

A steeper learning curve and increased documentation burdens can slow down the onboarding process for new team members, reducing overall productivity.
Over-Abstraction for Hypothetical Changes

Another pitfall of excessive decoupling is the tendency to over-abstract the codebase in anticipation of future changes that may never occur. While it's essential to design systems that can adapt to evolving requirements, over-optimizing for hypothetical scenarios can lead to wasted effort and unnecessary complexity.

Consider a situation where a development team decides to decouple the database access layer extensively to accommodate a potential switch in the future. This decision involves introducing a layer of abstraction to mediate between the application and the database, with the assumption that such a migration will eventually take place.

However, if this migration never happens - if for example MySQL continues to be the chosen database - then the added layers of abstraction serve no real purpose. Instead, they introduce complexity and potential performance overhead without delivering any tangible benefits.

The point about providing the ability for an application to switch from one DBMS to another is quite valid. Providing the ability to do something which is never actually done can be considered as a violation of YAGNI. However, the key part of that criticism revolves around the phrase the chosen database. I deliberately built the RADICORE framework around the 3 Tier Architecture with its separate Data Access layer so that developers who wanted to use my framework to build their own applications were not tied to a single DBMS, instead they had the choice of several. They had the ability to choose which DBMS to use before they built their application.


Achieving Balance: Decoupling in Software Design

The article Decoupling in Software Development: Striking the Right Balance also says the following:

Balancing decoupling with pragmatism is essential to effective software development. Here are some strategies and considerations to help strike that balance:

Understand the Problem Domain

Before embarking on a decoupling spree, take the time to understand the problem domain and the specific needs of your project. Not every component requires the same degree of decoupling. Focus your efforts on areas where it genuinely benefits the project's goals.

Start Simple

Begin with a straightforward architecture and only introduce decoupling when it becomes clear that it's needed. Avoid overcomplicating things from the start. Simplicity can be a powerful ally in software development.

Prioritize Realistic Changes

When planning for future changes, prioritize those that are likely to happen based on your project's roadmap and business requirements. Avoid overengineering for unlikely scenarios. While it's essential to remain adaptable, it's equally vital to use resources wisely.

Keep Communication Open

Ensure that your team maintains clear communication about the level of decoupling and abstraction required for each component. Avoid siloed decision-making, as it can lead to inconsistent approaches and unexpected challenges down the road.

Refactor When Necessary

As your project evolves, be prepared to refactor when it becomes evident that decoupling is needed to improve maintainability, scalability, or other essential factors. Refactoring requires deliberate and thoughtful action, not impulsive decisions.

That article concludes with the following:

Decoupling for Success: Beyond the Obvious Choices

Decoupling is undeniably a valuable practice in software development, offering numerous benefits in terms of modularity, reusability, and maintainability. However, like any tool, it should be used cautiously. Excessive decoupling can introduce complexity, hinder the learning process for developers, and lead to over-abstracted designs.

Striking the right balance between modularity and simplicity is essential to ensure that your software remains effective and maintainable over time. By understanding the problem domain, starting with simplicity, prioritizing realistic changes, fostering open communication, and refactoring when necessary, you can navigate the complexities of software development while reaping the rewards of well-considered decoupling.

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 onto 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 caused 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 of each vary so much that some people are easily confused into believing that they are different principles.

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?

The idea of coupling (how modules interact) is being confused with cohesion (the functional relatedness of the contents of a module). If a module has more than one responsibility then it is good practice to move the code which carries out each responsibility into its own module. This means splitting one large module into smaller modules whereas "decoupling" to some people means replacing a direct dependency between two modules with an indirect dependency by introducing a call to an intermediary module. These two concepts are obviously different (at least to me they are) yet far too many authors write in such a way that you cannot tell which principle they are talking about. If a word has different meanings to different people, or a single meaning can be called by different names, then it is easy to see how confusion can arise. If you encounter a word then the meaning of that word should be unambiguous. If you encounter a description of a practice then that practice should only have a single name. Too much ambiguity leads to a loss of precision, and the world of computing cannot be called a science if there is not enough precision.

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:


Amendment History

25 Jan 2025 Added The Pitfalls of Excessive Decoupling
Added Achieving Balance: Decoupling in Software Design

counter