Preface

I recently reviewed the company's code and found that the degree of code coupling is particularly high, modifying a place, unknowingly affect the other places, which made me think about how to make the code we write cohesive enough to reduce coupling?

"Highly cohesive and loosely coupled" is a very important design idea, which can effectively improve the readability and maintainability of code and reduce the scope of code changes caused by functional changes. It can be used to guide the design and development of code at different levels of granularity, such as systems, modules, classes, and even functions, and can be applied to different development scenarios, such as microservices, frameworks, components, and class libraries. In this article, let's explore how to make our application highly cohesive and low coupling.

What is high cohesion?

Let's first cast our eyes on cohesion. Usually the cohesion of our code is grouped into 7 categories, as shown below, from high to low cohesion.

1. Functional Cohesion

The highest degree of cohesion is achieved by putting the same functionality into a class or module.

2. Sequential cohesion

If the output of one function is the input of another function, and there is a sequential dependency, we group them into a module called sequential cohesion.

3. Communication cohesion

If function points use the same input or output data, we call it communication cohesion when they are cohesively grouped into a module.

4. Process Cohesion

If different functions are governed by the same control flow, we call it process cohesion.

5. Temporal cohesion

When different functions are executed in the same time period, such as different batch running tasks in a bank, the time to control whether they are placed in the same module is called temporal cohesion.

6. Logical cohesion

Different functions may have the same internal logic, so we group them together and call it logical cohesion.

7. Contingent cohesion

Incidental cohesion is when different features are put together directly because they are not related to each other. This situation is very common, for example, team A is responsible for the development of payment, logistics, product and other functions at the same time, in order to save development resources, they put the unrelated functions into one module, then this accidental cohesion will encounter various problems as the business develops.

The above explains the possible 7 classifications of cohesion, you don't have to be too serious, just understand it.

To summarize, what we call high cohesion is actually used to guide the design of the class or module itself, which simply means that similar functions should be put into the same class, and dissimilar functions should not be put into the same class. Similar functions are often modified at the same time, so if they are put into the same class, the modifications will be more focused and the code will be easier to maintain.

What is low coupling?

Now let's focus on coupling. Coupling is actually concerned with the design of dependencies between classes or between modules, and we usually have the following 7 types of coupling relationships.

1. Non-direct coupling

No direct relationship between two modules, the strongest module independence

2. Data coupling

The two modules are associated with parameters passing between them, and the modules have the least impact on the coupling relationship. For example, the order system input logistics number ID, logistics system returns your specific logistics information.

3. Marker coupling
The two modules depend on the same one data structure that is passed.

For example, the rental cost calculation system needs to calculate water and electricity bills, if you pass the data structure of user information and let the water and electricity system pick up the water and electricity consumption of the user object, this is marker coupling. A better approach would be to pass only the necessary water and electricity usage, rather than transmitting the entire user object.

4. Control coupling

The transfer of control information between two modules, such as a flag or switch, requires the calling module to know the internal logic of the called module, increasing the complexity of interdependence and understanding.

5. External coupling
A group of modules needs to be associated with an external environment, and this group of modules accesses the same global variables. External coupling is sometimes essential, but the number of such modules should be minimized.
6. Public coupling

A group of modules all accessing the same global data area is called public coupling. For example, if there is a public variable, different modules modify it.

7. Content Coupling

One module directly manipulates or modifies the internal book of another module, and one module does not access the other module through the normal portal, this is content coupling, which is the worst case and needs to be avoided. For example, directly calling the set method of another class, so this requires us not to brainlessly add the set method to any property of the class.

By low coupling I mean that the dependencies between classes are simple and clear in the code. Even if two classes have dependencies, a change in the code of one class will not or rarely lead to a change in the code of the dependent class

What does high cohesion, low coupling have to do with it?

The previous explained the various cases of cohesion and coupling, so what is the relationship between high cohesion and low coupling?

In the code design in the left part of the figure above, the class granularity is relatively small, and each class has a single responsibility. Similar functions are put into one class, and dissimilar functions are split into multiple classes. This makes the classes more independent and the code more cohesive. A change to a class only affects the code changes of one dependent class. We just need to test whether this one dependent class still works properly.

The code design in the right part of the figure above has a large class granularity, low cohesion, large and comprehensive functionality, and disparate functionality in one class. This causes many other classes to depend on this class. When we modify the code of a feature of this class, it will affect multiple classes that depend on it. We need to test these three dependent classes and whether they still work properly.

So, "high cohesion" helps "loose coupling", and "low cohesion" also leads to "tight coupling " .

How to achieve high cohesion and low coupling?

There is a classic guiding rule on how to achieve high cohesion and low coupling of modules or classes, and that is Dimitri's Law.

Dimity's Law says that classes that should not have direct dependencies on each other should not have dependencies; classes that do have dependencies on each other should only rely on the necessary interfaces as much as possible. To put it more figuratively, each module only "talks" to its own friends (talk), not to strangers (talk).

To expand on this, we pay special attention to the following points in our design.

1. Use more interfaces to hide implementation details, interfaces are a contract and relatively stable.

2. Try to avoid sharing global variables between different modules or classes, in case one place needs to be modified, the other modules will be affected as well.

3. Do not add set methods to any properties of a class to reflect good encapsulation, the less exposure, the less impact.

4. Reasonable use of design patterns, because design patterns will be a good guarantee of code scalability, encapsulation

5. Pay attention to the layered design and invocation, for example, in the business layer directly with SQL statements to operate the database, but should speak of database operations encapsulated in the DAO layer, the business layer to call the DAO layer

6. Avoid direct operation or call other modules or classes (content coupling), that is, directly call the set method, but should be called through the interface

7. Try to use data coupling and less control coupling. For example, transferring a common data variable is appropriate. But transferring a control command to you to execute branch a or branch b or branch c is not very appropriate, so try to use some other method to circumvent the sending of these control commands.

8. The modules are divided into as many functions as possible, following the single responsibility principle

9. Modules should only expose a minimum number of interfaces outside the heap, following the principle of interface isolation

10. The interaction between modules should be minimal, and the design of the interface should be as simple as possible, e.g., do not transfer entire objects if you can pass basic class types.

Summary

"Highly cohesive and loosely coupled" is a very important design idea that can effectively improve the readability and maintainability of code and reduce the scope of code changes caused by functional changes. "High cohesion" is used to guide the design of the class itself, and "loose coupling" is used to guide the design of the dependency relationship between classes.

High cohesion means that similar functions should be placed in the same class, and dissimilar functions should not be placed in the same class. Similar functions are often modified at the same time, so if they are placed in the same class, the modification will be more concentrated. Loose coupling means that the dependencies between classes are simple and clear in the code. Even if two classes have dependencies, code changes in one class will result in no or little code changes in the dependent class.

So when we design, try to use more brains to think about whether this function belongs to this module or not? Is the interaction between them reasonable? Is it complex? Is there a simpler way to do it? Ask yourself a few more questions, and the design you make will get better and better.