Clean Code - Part-6: Classes, Objects & Data Containers

Greetings! 🚀 I'm a dynamic Fullstack Software Engineer, driving advancements in technology to empower organizations globally. Proficient in a diverse tech stack, I contribute to the architecture and development of high-performance applications. Beyond coding, I thrive on creating empowering and collaborative environments, championing technological innovation, and fostering connections to push the boundaries of excellence. As a Gamer🎮, and a Footballer⚽️, I bring passion to both my professional and personal pursuits. A Wanderlust🛣️ enthusiast and Orophile⛰️, I thrive on exploring diverse landscapes. A Melomane🎵 and avid TV😍 enthusiast, I find joy in the finer aspects of life. Also, a proud enthusiast of Tea☕️ who appreciates bike rides, beaches, and mountains alike. Always eager to connect with like-minded professionals and individuals who share a zest for technology, innovation, and life's adventures. Let's push boundaries together! 🌐
Imagine classes as specialized tools in a toolkit, each serving a specific purpose. Just like you wouldn't use a hammer to tighten a screw, avoid bloating classes with unrelated functionalities. Instead, keep them focused and concise, adhering to the Single Responsibility Principle (SRP). This ensures that each class does one thing and does it well, making your code cleaner and easier to understand.
Similarly, think of data containers as organized compartments to store your tools. Whether it's a simple list or a custom class, choose the appropriate container based on the complexity and structure of your data. This helps in maintaining data integrity and improves code readability.
Tell, Don't Ask Principle: This principle suggests that instead of asking an object for its state and then making decisions based on that state, we should tell the object to do something. For example, instead of asking a Car object for its speed and then deciding whether to accelerate, we should simply tell the Car to accelerate.
Example:
# Example of a class adhering to SRP
class UserManager:
def authenticate(self, username, password):
# Authentication logic
pass
def updateUserProfile(self, user_id, new_data):
# Profile update logic
pass
# Example of a data container
class Customer:
def __init__(self, name, email):
self.name = name
self.email = email
# Usage
user_manager = UserManager()
customer = Customer("John Doe", "john@example.com")
Cohesion
Maximum Cohesion:
High cohesion means that all methods within a class are closely related and work together to achieve a common goal. This leads to more readable and maintainable code.
Example: In a BankAccount class, methods like deposit, withdraw, and getBalance all directly relate to managing the bank account's balance and transactions.
No Cohesion:
Low or no cohesion occurs when methods in a class are unrelated or don't work together towards a common purpose. This often indicates poor design and can make the code harder to understand and maintain.
Example: A Utilities class with methods like sortArray and calculateAverage, which don't relate to each other or any specific entity.
Strive for high cohesion in your classes, where every method collaborates harmoniously towards a common goal. This is like having a well-orchestrated team, where each member knows their role and works seamlessly with others. By maximizing cohesion, you create classes that are focused, understandable, and easy to maintain.
Conversely, avoid creating classes with low or no cohesion, resembling a group of individuals with no shared purpose. This leads to confusion and complexity in your codebase. Instead, aim for classes where every method contributes meaningfully to the overall functionality, promoting clarity and cleanliness.
Example:
# High cohesion example
class Calculator:
def add(self, x, y):
return x + y
def subtract(self, x, y):
return x - y
# Low cohesion example
class Utilities:
def sortArray(self, arr):
# Sorting logic
pass
def calculateAverage(self, arr):
# Average calculation logic
pass
# Usage
calculator = Calculator()
result = calculator.add(5, 3)
Law of Demeter
The Law of Demeter, also known as the principle of least knowledge, states that a module should not know about the internal workings of the objects it manipulates. Instead, it should only interact with its immediate friends (its own attributes, parameters passed to the method, objects it creates, or global variables).
Embrace the Law of Demeter as your guiding principle when interacting with objects. Just like maintaining professional boundaries, ensure that your modules only communicate with their immediate 'friends' and avoid delving into unnecessary details. By following this principle, you foster modularity and encapsulation, resulting in code that is less prone to unexpected changes and easier to maintain.
Remember, clean code is not just about achieving functionality; it's about creating code that is intuitive, organized, and easy to modify. By incorporating these principles into your coding practices, you not only write cleaner code but also empower yourself and your team to build robust and scalable software solutions.
Example:
# Without adhering to the Law of Demeter
class Customer:
def __init__(self, name, address):
self.name = name
self.address = address
def getAddress(self):
return self.address
# Usage
customer = Customer("John Doe", "123 Main St")
street = customer.getAddress().split(',')[0] # Directly accessing internal state
# Adhering to the Law of Demeter
class Customer:
def __init__(self, name, address):
self.name = name
self.address = address
def getStreet(self):
return self.address.split(',')[0]
# Usage
customer = Customer("John Doe", "123 Main St")
street = customer.getStreet() # Indirectly accessing internal state
In conclusion, writing clean code is not just a matter of syntax and formatting; it's a mindset that values simplicity, clarity, and maintainability. By embracing principles such as Single Responsibility, Polymorphism, Maximum Cohesion, and the Law of Demeter, developers can elevate their code to a higher standard.
Encouraging users to adopt these principles fosters a culture of clean coding practices within their projects. It empowers developers to create classes that are focused, data containers that are well-organized, and interactions between modules that are cohesive and respectful of boundaries.
SOLID Principles
The SOLID principles are a set of five design principles that help developers create more maintainable, flexible, and scalable software systems. Each principle focuses on a different aspect of object-oriented design:
Single Responsibility Principle (SRP): This principle states that a class should have only one reason to change, meaning it should have only one responsibility or job. By adhering to SRP, classes become more focused, easier to understand, and less prone to unexpected changes.
Open/Closed Principle (OCP): The Open/Closed Principle suggests that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means that the behavior of a module can be extended without modifying its source code, promoting code reuse and minimizing the risk of introducing bugs in existing code.
Liskov Substitution Principle (LSP): LSP states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. In other words, derived classes should be substitutable for their base classes without altering the desired properties of the program.
Interface Segregation Principle (ISP): ISP suggests that clients should not be forced to depend on interfaces they do not use. Instead of implementing large, monolithic interfaces, it's better to break them into smaller, more specific interfaces. This promotes loose coupling and allows clients to depend only on the functionality they need.
Dependency Inversion Principle (DIP): DIP advocates for high-level modules to not depend on low-level modules, but rather both should depend on abstractions. Furthermore, it suggests that abstractions should not depend on details; instead, details should depend on abstractions. By decoupling high-level and low-level modules, DIP increases flexibility, maintainability, and testability of the codebase.
Find more details here



