Let try to design and write better code by looking at two 💕 of the important software quality metrics – Cohesion and Coupling. This is applicable for any programming language and help you write high quality code which ensure high reusability and easy maintenance. These high level topics in general talk about how easily our code can be changed and extended. Most people think they are the same – to a certain point it is, but there is a difference –
#Cohesion🔗
Cohesion is the degree to which the elements of a certain class or function belong together. Let's take a look at an example –
update_database(d, quantity)
for i -> [0:quantity):
calculate_profit(.. , ..)
status = "SUCCESS"
display_status(status, c)
...
.Before we start with cohesion, we see the name of the function is a red flag! But it is clear that the function has a weak cohesion. It does many things that really do not belong together. A function with a strong cohesion on the other hand has a clear responsibility. An example of one such function is the sine function available in your preferred language - it does only one thing– to calculate the sine of a number!
Having a strong cohesion makes the code more readable and easy to maintain.
#Coupling 🖇️
Coupling is the measure of how dependent two parts of your code are on each other. Again, let's take a look at an example –
if email.header.bearer.invalid():
return "IT'S SPAM - HEADER IS INVALID"
else if email.header.sender in email.header.bloocked_list :
return "MAIL FROM BLOCKED LIST"
else:
...
...This function checks if the email is spam by checking various parts of the header. This code accesses data that's deep in the structure of the email object. This means that this function is highly coupled to the Email object. Having high coupling means, changing one part of the program leads to changes in several other places which we tend to miss! Here, changes in the data structure of the email object leads to changes in this function as well.
In our real world applications we need to work together – the more coupling you introduce to your app, you tend to have more knots and twists in the spider web you are building – this even makes the software harder to maintain where one change breaks other functionalities which you never expect.
In this case, to solve this coupling, we could pass along only the data which the function needs instead of the whole object, also– other way of doing this is to make this function part of the Email class
#How to measure these metrics?
Unfortunately, we cannot come up with a number to determine how cohesive or how coupled your code is, but it is the duty of the developer to understand the code structure and be able to analyze the code to eliminate coupling and cohesion as much as possible to ensure the software quality which comes through experience.
#Code Example
Let's walk through some examples and eliminate cohesion and coupling in them which you can use in your software development –
function generate_vehicle_id(..):
...
function generate_vehicle_license(id, ..):
...
class App():
function register_vehicle(brand):
registry = VehicleRegistry()
vehicle_id = registry.generate_vehicle_id(..)
license_plate = registry.generate_vehicle_license(vehicle_id)
catelogue_price = 0
if brand == "Audi A8":
catelogue_price = 60_000
elif brand == "Tesla Model 3 Plaid"
catelogue_price = 45_000
else:
...
tax_percentage = 0.05
if brand == "Tesla Model 3 Plaid"
tax_percentage = 0.02 //Electric ;)
print("Registration is complete. Your vehicle information..")
print("Brand : ", brand)
....
..
.
app = App()
app.register_vehicle("Audi A8")#Code analysis
In the above code,
#General thoughts
One of the easy and efficient ways to decouple and remove cohesion is to look at the data flow and see where the information is stored in the code.
When we know that and have a defined logical structure of information flow, you can start to group the code around that which leads to –
This is more in line towards GRASP Design Principles by Craig Larman
#Where is the Information?
In the above example, we can see that the data is not stored logically –
So, let's make the changes to solve these problems –
string brand
int catalogue_price
bool electric
int tax
Constructor(brand, catalogue_price, electric):
self.brand = brand
self.catalogue_price = catalogue_price
self.electric = electric
class Vehicle():
string id
string license_plate
VehicleInfo info
Constructor(id, license_plate, info):
self.id = id
self.license_plate = license_plate
self.info = info#Reducing coupling and cohesion – (●'◡'●)
We are well aware that the vehicle_registry method has too many responsibilities, we can try to reduce it's responsibilities by the below changes –
string brand
int catalogue_price
bool electric
int tax
Constructor(brand, catalogue_price, electric):
self.brand = brand
self.catalogue_price = catalogue_price
self.electric = electric
self.tax = compute_tax()
function compute_tax():
if self.electric:
return 0.02
else:
return 0.05
function print_vehicle_info()
//Format and print Vehicle Info
...
----------------------------------------------------------------------
class Vehicle():
string id
string license_plate
VehicleInfo info
Constructor(id, license_plate, info):
self.id = id
self.license_plate = license_plate
self.info = info
function print_vehicle():
//Format and Print Vehicle Information
...
info.print_vehicle_info()
----------------------------------------------------------------------
class VehicleRegistry():
vehicle_info = {}
function generate_vehicle_id(..):
...
function generate_vehicle_license(id, ..):
...
function add_vehicle_info(brand, electric, catalogue_price):
vehicle_info[brand] = VehicleInfo(brand, electric, catalogue_price)
Constructor():
add_vehicle_info("Tesla Model Y Plaid", true, 60000)
//Add all vehicles here - probably fetching info from a DB
function create_vehicle(brand):
vehicle_id = generate_vehicle_id()
license_plate = generate_vehicle_license(vehicle_id)
return vehicle_info[brand]
function print_vehicle_information():
...
----------------------------------------------------------------------
class App():
function register_vehicle(brand):
registry = VehicleRegistry()
return registry.create_vehicle(brand)
-----------------------------------------------------------------------
app = App()
vehicle =app.register_vehicle("Tesla Model Y Plaid")
vehicle.print()#Conclusion
After this refactoring, we could easily say that each line of code is very specific in what it does – less cohesive and less coupled – which makes the code easy to understand and scale without breaking the code. And always remember to keep practicing!