Should High Cohesion and Low Coupling be Emphasized in Embedded Software Written in C?

I am Lao Wen, an embedded engineer who loves learning.Follow me to become even better together!

1 – Principles

Low coupling means that modules should exist as independently as possible. While some connection between modules is inevitable, the interfaces between them should be minimal and simple.

Thus, high cohesion from the internal characteristics of each module in the program and low coupling from the relationships between various modules impose requirements on our design.

Many technologies and design principles that have emerged during the development of program design and software engineering can be interpreted from the perspectives of cohesion and coupling.

As a beginner in C programming, we will explore how to achieve high cohesion and low coupling based on our current understanding of functions.

Regarding low coupling, the lowest level of coupling is non-direct coupling, which means that the connection between two functions is entirely achieved through the control and invocation of a common calling function, resulting in the weakest coupling and the strongest independence of functions.

However, it is clearly unrealistic for a group of functions to have no data transfer. The next best option is to pursue data coupling, where only simple data parameters are passed between the calling and called functions, such as functions using value passing.

Some functions, when called, use the method of passing addresses through formal parameters, allowing the function body to modify storage units outside its scope via pointers, which constitutes a stronger coupling known as characteristic coupling, where the connection between functions is identified by addresses.

Additionally, if two functions open the same file for operations, this also constitutes a form of characteristic coupling.

Stronger coupling is external coupling, where a group of modules accesses the same global variable without passing information about that global variable through parameter tables. When an abnormal program execution result is found, it is difficult to locate which function has the error.

Many beginners find parameter passing cumbersome and define the data to be processed as global variables as much as possible, simplifying the interfaces between functions but creating a structure with strong coupling.

In C, static local variables can also be used to share data between two calls of the same program, which can also be seen as a form of external coupling, although the scope of static local variables is limited to the function itself, and their impact is also confined to the function, making the coupling degree much weaker than using global variables.

Thus, we can understand the principle of “use when appropriate, do not misuse” when using global variables and static local variables.

Regarding high cohesion, the highest level of cohesion is functional cohesion, where all elements within a module exist to accomplish the same function, collectively completing a single function, making the module indivisible.

Such functions have very clear and defined functionalities and generally appear at lower levels in the program structure diagram.

Next is sequential cohesion, where the various processing elements in a function are closely related to the same function, usually with the output of one processing element being the input of the next. For such functions, if it does not lead to high coupling, they can be separated into two functions.

Some functions have different processing functionalities that are only related because they access a common data, which is called communication cohesion and informational cohesion, further decreasing the level of cohesion.

Lower levels of cohesion will not be listed one by one, with the worst being coincidental cohesion, where there is no connection between the processing elements within a function, merely grouped together by chance.

One can imagine such a module as a disorganized group, corresponding to low quality. In summary, when solving problems and dividing functions, the principle of “one function, one feature” should be followed to achieve functional cohesion as much as possible.

To achieve high cohesion and low coupling, the key is to spend some time on good design before writing code.

In the following example, we will discuss how to consider the above factors in conjunction with specific problems.

2 – Example

This example is inspired by Teacher Qiu Zongyan’s “From Problem to Program – Introduction to Program Design and C Language”.

Task: Output perfect squares within 200 (a number is called a perfect square if it is the square of another integer, also known as a square number), requiring a newline after every 5 data points.

Solution and Comments: For this simple task, we completed it in a main function. The program is as follows in Solution 1:

// Solution 1: High cohesion single-module implementation#include <stdio.h>int main(){    int m, num=0;    for (m = 1; m * m <= 200; m++)    {        printf("%d ", m * m);        num++;        if (num%5==0)            printf("\n");    }    return 0;}

Since the task itself is simple, implementing it in a single main function results in a cohesion level close to functional cohesion, which is already quite high. For the sake of helping readers understand the technical aspects of module quality, we will attempt to further improve the cohesion of the program and then examine various solutions with different coupling levels.

To improve the cohesion level of the function in the above solution (only the main function), we consider the functional aspects of “finding perfect squares and outputting them” – “finding perfect squares” and “outputting” are two functions in themselves, and when detailing the output, there is also the requirement of “5 data points per line”. These functional details are all contained within one function, indicating that there is room to further enhance the level of cohesion.

In practical applications, almost all processing can be decomposed into the “input-computation-output” pattern. Excellent solutions often require at least these three modules to be independent. For the “computation” module, it should not include input and output, but rather accept input data and return results after computation. Of course, for complex problems, further decomposition may be necessary at each stage.

Next, we will explore a solution that separates the implementation of “finding perfect squares and outputting” from “new line after every 5 data points”. Such decomposition helps improve cohesion, while the coupling level between the two decomposed modules becomes the focus of our attention.

Now, we will keep the functionality of “finding perfect squares and outputting” in the main function (it can also be made a separate function, but it is not necessary), while designing a function named format for the functionality of “new line after every 5 data points”, which outputs a space as a separator between two perfect squares each time it is called, and outputs a newline on the 5th call.

Between these two modules, there is a need to pass information about “how many times it has been called”, making it impossible to use the loosest coupling of non-direct coupling. We consider data coupling, using simple formal parameters to pass values, resulting in Solution 2.

// Solution 2: A low coupling solution that cannot meet functional requirements#include<stdio.h>void format(int);int main(){int m, num=0;for (m = 1; m * m <= 200; m++)    {printf("%d", m * m);        format(num);    }return 0;}void format(int n){    n++;if (n%5==0)printf("\n");elseprintf(" ");return;}

In this program structure, the coupling level between format and the main function is data coupling. In main, the local variable num is reasonably initialized to 0 when it has not been output at all. When calling format, the parameter n representing the number of outputs (which perfect square number) is passed, and n is incremented, controlling the output of space or newline.

However, analysis and execution of the program reveal that the functionality of “new line after every 5 data points” has not been achieved. This is because the change in the formal parameter n within the format function corresponds to a different memory space than the actual parameter num, and the result of n++ modification does not affect num, leading to the loss of important information about “which output is being made” in the next call.

A remedy is to have format return the changed value of n back to the main function, resulting in the program of Solution 3:

// Solution 3: Using return value to increase coupling but achieve functionality#include<stdio.h>int format(int);int main(){int m, num=0;for (m = 1; m * m <= 200; m++)    {printf("%d", m * m);        num = format(num);    }return 0;}int format(int n){    n++;if (n%5==0)printf("\n");elseprintf(" ");return n;}

Maintaining the original function’s return value as void and changing the parameter to pass by address results in the following program of Solution 4. This solution has a higher coupling level but still achieves functionality.

// Solution 4: Address passing to achieve functionality, higher coupling level#include &lt;stdio.h&gt;void format(int*);int main(){    int m, num=0;    for (m = 1; m * m &lt;= 200; m++)    {        printf("%d", m * m);        format(&amp;num);    }    return 0;}void format(int *p){    (*p)++;    if ((*p)%5==0)        printf("\n");    else        printf(" ");    return;}

Some may have thought of using global variables as a solution. This way, num can be defined as a global variable, and its lifespan is no longer dependent on function calls, allowing its value to remain unchanged between function calls (as long as it is not reassigned), thus completing the task of passing information. At this point, format can be designed as a no-parameter function, resulting in the following program of Solution 5:

// Solution 5: Highest coupling level global variable solution#include &lt;stdio.h&gt;void format();int num=0;int main(){    int m ;    for (m = 1; m * m &lt;= 200; m++)    {        printf("%d", m * m);        format();    }    return 0;}void format(){    num++;    if (num%5==0)        printf("\n");    else        printf(" ");    return;}

This is the solution with the highest coupling level for this problem. Defining num as an external variable means that if there are other functions, num can be modified by any function, making it difficult to locate errors when format counts incorrectly, and modifications may lead to errors elsewhere. In such a short program, this solution may still be acceptable, but as the scale of the program increases, the issues it may bring must be taken seriously. Therefore, in practical applications, it is emphasized that global variables should be used cautiously (not avoided).

Considering that num is private data used in format – only format cares about which data point it is, main does not need to care. Thus, we can consider defining num as a static local variable within format, resulting in the program of Solution 6:

// Solution 6: Using static local variable, higher coupling but best encapsulation solution#include &lt;stdio.h&gt;void format();int main(){    int m ;    for (m = 1; m * m &lt;= 200; m++)    {        printf("%d", m * m);        format();    }    return 0;}void format(){    static int num=0;    num++;    if (num%5==0)        printf("\n");    else        printf(" ");    return;}

Here, the scope of the static local variable num is local, defined within the function body, providing the best encapsulation among all solutions, thus ensuring information concealment and avoiding unintentional access by other functions.

However, the lifespan of num is global, allowing it to pass information between different calls of the function, resulting in a higher coupling level (self-coupling).

But it achieves the ideal coupling between the main function and the format function, ensuring both functional correctness and the safety of local data, demonstrating the advantages of static local variables.

In summary, there are many solutions when solving a problem.

Solution 1 is acceptable, but improvements are desired to enhance cohesion; Solution 2 achieves low coupling with a simple parameter passing method but unfortunately cannot meet functional requirements; among other solutions, the preferred order for this problem is:

Solution 6, Solution 3 > Solution 4 > Solution 5

Readers are encouraged to review the previous content and consider the reasoning behind this ranking.

In the process of discussing various solutions above, we should realize that improving programming design skills involves continuously learning new technologies and understanding new evaluation criteria, which is a process of broadening one’s perspective.

In later exercises, it would be beneficial to think of more solutions and be able to evaluate the pros and cons of solutions from a professional perspective, ultimately achieving a level of expertise where the best solution is instinctively chosen.

Original article: https://helijian.blog.csdn.net/article/details/79401703

The article is sourced from the internet, and the copyright belongs to the original author. If there is any infringement, please contact for deletion.

Should High Cohesion and Low Coupling be Emphasized in Embedded Software Written in C?

-END-

Previous recommendations: Click the image to jump to readShould High Cohesion and Low Coupling be Emphasized in Embedded Software Written in C?

There are many embedded software engineers who can only code but cannot read timing diagrams!

Should High Cohesion and Low Coupling be Emphasized in Embedded Software Written in C?

In embedded software development, I will teach you how to implement a software timer step by step.

Should High Cohesion and Low Coupling be Emphasized in Embedded Software Written in C?

When refactoring embedded software, what key points should be focused on?

I am Lao Wen, an embedded engineer who loves learning.Follow me to become even better together!

Leave a Comment