Let's summarize the degree of coupling between modules with Python code

Purpose

I haven't touched on it much since I studied with basic information, and at that time I didn't try to write code, so I decided to study again.

What is the degree of coupling between modules?

It shows how strong the module-to-module relationship is. It is said that the independence of modules can be increased by reducing the degree of coupling between modules. Increased module independence has the following benefits:

For example, take a look at the code below. This function determines whether it is a leap year based on the current year.

Code with low module independence has low reusability

from datetime import datetime
from calendar import isleap

#A function that determines if this year is a leap year and outputs the result
def A():
    this_year = datetime.now().year
    if isleap(this_year):
        print('This year is a leap year')
    else:
        print('This year is not a leap year')

Now, let's say you want to incorporate a "mechanism to determine if 10 years ago was a leap year" in this module.

from datetime import datetime
from calendar import isleap

#A function that determines if this year is a leap year and outputs the result
def A():
    this_year = datetime.now().year
    if isleap(this_year):
        print('This year is a leap year')
    else:
        print('This year is not a leap year')

#A function that determines if 10 years ago is a leap year and outputs the result
def B():
    this_year = datetime.now().year - 10
    if isleap(this_year):
        print('10 years ago is a leap year')
    else:
        print('10 years ago is not a leap year')

Code with low module independence is hard to test

Almost the same code is written, and it doesn't feel very good intuitively in the first place. In addition, there are problems when testing. Suppose you want to put the following test data into function A and test whether the leap year is judged correctly.

test data Expected value
2000 This year is a leap year
2020 This year is a leap year
2100 This year is not a leap year

So how do you test it?

That's right. This cannot be tested. This is because both functions A and B have a mixture of "processing to find the current year" and "mechanism to determine leap year", and their functions are not independent.

Therefore, it is better to do this.

def A(year):
    if isleap(year):
        print('This year is a leap year')
    else:
        print('This year is not a leap year')

In this way, the year information should be input from the outside. Now, whether it's 10 years ago, 100 years ago, or 200 years later, you only need one function, whether you have multiple test data.

There is an evaluation standard for module coupling

There are different types of inter-module coupling.

Evaluation criteria Coupling between modules Module independence
Inner join high Low
Common join
Outer join
Control join
Stamp combination
Data join Low high

So, from here, I will summarize it with a code. This time I will reproduce it with Python.

Inner join

This is almost impossible nowadays, and it's difficult to reproduce ... Is it like this if you force it?

There is such a module. username, level, attack, defense, etc. are defined as global variables.

moduleA.py


username = 'hogehogekun'
level = 25
attack = 20
defence = 5

def show_user_status():
    print('User name:' + username)
    print('level:' + str(level))
    print('Offensive power:' + str(attack))
    print('Defense power:' + str(defence))

Suppose you have code that takes advantage of this.

main.py


import moduleA

#Raise the level by 1
moduleA.level += 1

#Display status using function of moduleA
moduleA.show_user_status()

result


Username: hogehogekun
Level: 26
Attack power: 20
Defense: 5

The initial value of the level was 25, but it has increased by 1. There is no problem with the behavior, but the behavior of moduleA depends deeply on the behavior of the main module.

Common join

Now, let's say you have grouped user information into one class and managed them in one list.

moduleA.py


class User:
    def __init__(self, username, level, attack, defence):
        self.username = username
        self.level = level
        self.attack = attack
        self.defence = defence

    def show_user_status(self):
        print('User name:' + self.username)
        print('level:' + str(self.level))
        print('Offensive power:' + str(self.attack))
        print('Defense power:' + str(self.defence))

#Manage the list of users in a list
user_list = [User('hogehogekun', 75, 90, 80), User('fugafugakun', 10, 5, 7)]

And suppose there are two functions in mainA that use this module.

mainA.py


import moduleA

def funcA():
  del(moduleA.user_list[0])

def funcB():
  print(moduleA.user_list[0].username)


#Execute in the order of funcA, funcB
funcA()
funcB()

result


fugafugakun

This time around, funcA has removed the first element of the global list. By the time funcB peeked in, hogehoge had already disappeared, leaving only fugafuga. By the way, if funcB refers to moduleA.user_list [1], IndexError will occur. In this way, with common joins, if you change or delete part of a common data structure, you will need to review all the modules that reference that common part.

Outer join

It's very similar to a common join, but it's a perception that the shared information is a collection of single pieces of data rather than a data structure such as a list or object.

This time, suppose you have information about the cumulative number of users and service status.

moduleA.py


class User:
    def __init__(self, username, level, attack, defence):
        self.username = username
        self.level = level
        self.attack = attack
        self.defence = defence

    def show_user_status(self):
        print('User name:' + self.username)
        print('level:' + str(self.level))
        print('Offensive power:' + str(self.attack))
        print('Defense power:' + str(self.defence))

user_count = 123091 #Cumulative number of users
service_status = 200 #Service status

main.py


import moduleA

def funcA():
    print(moduleA.user_count)

def funcB():
    print(moduleA.service_status)

funcA()
funcB()

result


123091
200

This code has no behavioral issues. Both the number of users and the service status have been acquired correctly in funcA and funcB. However, if there is a specification change such as the service_status of moduleA.py becoming a character type instead of a numeric type, it is necessary to modify funcB that refers to the corresponding information.

Control join

Control join uses arguments to control the processing of the called function. In this code, different processing is done depending on whether 1 or 2 is passed to some_command.

moduleA.py


class User:
    def __init__(self, username, level, attack, defence):
        self.username = username
        self.level = level
        self.attack = attack
        self.defence = defence

    def some_command(self, command_id):
        if command_id == 1: #Status display command
            print('User name:' + self.username)
            print('level:' + str(self.level))
            print('Offensive power:' + str(self.attack))
            print('Defense power:' + str(self.defence))

        elif command_id == 2: #Level up command
            print(self.username + 'Level has increased by 1!')
            self.level += 1

main.py


from moduleA import User

user1 = User('hogehogekun', 40, 20, 20)
user1.some_command(1)
user1.some_command(2)

result


Username: hogehogekun
Level: 40
Attack power: 20
Defense: 20
The level of hogehogekun has increased by 1!

It looks good at first glance because the information called command is passed from the outside. Another module that calls some_command needs to know the internal structure of some_command. Therefore, the degree of coupling is relatively high.

Stamp combination

User class as usual. This time, we will focus on the exchange between funcA and funcB in main.py. Calling funcB inside funcA and passing a list as an argument looks like some kind of code.

moduleA.py


class User:
    def __init__(self, username, level, attack, defence):
        self.username = username
        self.level = level
        self.attack = attack
        self.defence = defence

    def show_user_status(self):
        print('User name:' + self.username)
        print('level:' + str(self.level))
        print('Offensive power:' + str(self.attack))
        print('Defense power:' + str(self.defence))

Suppose another module behaves like this.

main.py


from moduleA import User

def funcA():
    user_list = [User('hogehogekun', 20, 10, 10), User('fugafugakun', 99, 99, 99), User('piyopiyokun', 99, 99, 99)]
    funcB(user_list)

def funcB(user_list):
    print(user_list[2].username)

funcA()

result


piyopiyokun

There is nothing wrong with this at this point, but if the number of elements in funcA changes, for example, funcB will be affected.

main.py


def funcA():
    user_list = [User('hogehogekun', 20, 10, 10), User('fugafugakun', 99, 99, 99)]
    funcB(user_list)

def funcB(user_list):
    print(user_list[2].username)

funcA()

result


IndexError: list index out of range

In stamp combination, the entire list is passed, but the calling module uses only some information. This time, funcB is only using the third element, even though it is passed a list with three elements. At this time, even if there is a change in the elements that are not used on the funcB side (the number in this case) in the stamp combination, funcB may be affected.

Data join

Minimizes the information passed between multiple modules. Following the example of stamp combination, it looks like this.

moduleA.py


class User:
    def __init__(self, username, level, attack, defence):
        self.username = username
        self.level = level
        self.attack = attack
        self.defence = defence

    def show_user_status(self):
        print('User name:' + self.username)
        print('level:' + str(self.level))
        print('Offensive power:' + str(self.attack))
        print('Defense power:' + str(self.defence))

main.py


def funcA():
    user_list = [User('hogehogekun', 20, 10, 10), User('fugafugakun', 99, 99, 99), User('piyopiyokun', 99, 99, 99)]
    funcB(user_list[2])

def funcB(target_user):
    print(target_user.username)

funcA()

result


piyopiyokun

In funcB, only piyopiyokun is processed, so the data to be passed is only user_list [2] in the first place. If this reduces the number of user_lists, it should affect funcA but not funcB. In the first place, the decrease in the number of user_list means that funcA is being modified, so the range of influence may be considerably smaller.

In this way, you can weaken the coupling of modules by exchanging only the necessary information.

Addendum: When I was reading the book I had, it said, "The called module will be able to directly manipulate the called data with the arguments it receives." In other words, it seems that you should pass by reference. There seemed to be variations in the way of thinking around here depending on the developer and development method.

Summary

There are various advantages to setting the strength of the module to be high and the degree of coupling between the modules to be low.

And so on!

Recommended Posts

Let's summarize the degree of coupling between modules with Python code
Convert the character code of the file with Python3
Let's break down the basics of TensorFlow Python code
Let's touch the API of Netatmo Weather Station with Python. #Python #Netatmo
Let's measure the test coverage of pushed python code on GitHub.
[Python3] Rewrite the code object of the function
Let's read the RINEX file with Python ①
Let's summarize the Python coding standard PEP8 (1)
Let's summarize the Python coding standard PEP8 (2)
Let's summarize the construction of NFS server
[Python] Get the character code of the file
[Python] Read the source code of Bottle Part 2
Summary of the differences between PHP and Python
2016 The University of Tokyo Mathematics Solved with Python
[Note] Export the html of the site with python.
The answer of "1/2" is different between python2 and 3
Calculate the total number of combinations with python
Check the date of the flag duty with Python
Code for checking the operation of Python Matplotlib
Static analysis of Python code with GitLab CI
[Python] Determine the type of iris with SVM
[Blender x Python] Think of code with symbols
Let's simulate the transition of infection rate with respect to population density with python
Let's play with Python Receive and save / display the text of the input form
Align the number of samples between classes of data for machine learning with Python
Easy way to check the source of Python modules
Extract the table of image files with OneDrive & Python
Learn Nim with Python (from the beginning of the year).
Destroy the intermediate expression of the sweep method with Python
the zen of Python
Visualize the range of interpolation and extrapolation with python
Get the return code of the Python script from bat
Calculate the regression coefficient of simple regression analysis with python
Let's use the Python version of the Confluence API module.
Let's use the open data of "Mamebus" in Python
Summary of the basic flow of machine learning with Python
Get the operation status of JR West with Python
Extract the band information of raster data with python
Let's operate GPIO of Raspberry Pi with Python CGI
[Python] Let's change the URL of the Django administrator site
I tried to summarize the string operations of Python
I tried to find the entropy of the image with python
Try scraping the data of COVID-19 in Tokyo with Python
I tried "gamma correction" of the image with Python + OpenCV
The story of implementing the popular Facebook Messenger Bot with python
Let's run jupyter natively supported by VS Code with python3.8
Unify the environment of the Python development team starting with Poetry
Visualize the results of decision trees performed with Python scikit-learn
Calculate the square root of 2 in millions of digits with python
Let's execute the command on time with the bot of discord
Tank game made with python About the behavior of tanks
Run the intellisense of your own python library with VScode.
I evaluated the strategy of stock system trading with Python.
The story of rubyist struggling with python :: Dict data with pycall
[Homology] Count the number of holes in data with Python
Try to automate the operation of network devices with Python
Let's visualize the number of people infected with coronavirus with matplotlib
The story that Python stopped working with VS Code (Windows 10)
Rewrite the record addition node of SPSS Modeler with Python.
The process of making Python code object-oriented and improving it
Mass generation of QR code with character display by Python