Blog Datasheets Home About me Clients My work Services Contact

G2Labs Grzegorz Grzęda

Writing clean and SOLID code in object-oriented programming

April 23, 2023

Writing Clean and SOLID Code in Object-Oriented Programming

As developers, we all strive to write code that is easy to understand, maintain, and extend. One of the ways to achieve this is by writing clean and SOLID code in object-oriented programming. In this blog post, we will explore the concepts of clean code and SOLID principles, and we will provide extensive examples in both C and Python to illustrate these concepts in detail.

Clean Code

Clean code is code that is well-organized, easy to follow, and has a minimal amount of complexity. It adheres to best practices and standard conventions, making it readable and understandable by other developers. Writing clean code not only makes it easier for others to work with your code but also makes it easier for you to come back and make changes later on.

Here are a few principles of writing clean code:

Now, let’s look at an example of clean code in both C and Python to illustrate these principles.

Example in C:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>

// Descriptive naming and simplicity
int calculateSum(int a, int b) {
  return a + b;
}

int main() {
  // Avoiding duplication
  int num1 = 5;
  int num2 = 10;
  int sum = calculateSum(num1, num2);
  printf("The sum of %d and %d is %d\n", num1, num2, sum);
  return 0;
}

Example in Python:

1
2
3
4
5
6
7
8
9
# Descriptive naming and consistency
def calculate_sum(a, b):
  return a + b

# Avoiding duplication
num1 = 5
num2 = 10
sum = calculate_sum(num1, num2)
print(f"The sum of {num1} and {num2} is {sum}")

SOLID Principles

SOLID is an acronym that stands for five object-oriented design principles: Single Responsibility Principle, Open/Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle. These principles aim to make our code more maintainable, flexible, and understandable.

Let’s go through each SOLID principle and provide examples in both C and Python to illustrate their application.

Single Responsibility Principle (SRP)

The Single Responsibility Principle states that a class should have only one reason to change, meaning it should have only one job or responsibility. This helps in keeping our classes and functions focused and easier to understand.

Example in C:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// SRP violated
struct Employee {
  // ...
  void calculateSalary() {
    // calculate salary
  }
  void saveEmployee() {
    // save employee
  }
};

// SRP adhered
struct Employee {
  // ...
};

void calculateSalary(struct Employee* emp) {
  // calculate salary
}

void saveEmployee(struct Employee* emp) {
  // save employee
}

Example in Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# SRP violated
class Employee:
  # ...
  def calculate_salary(self):
    # calculate salary
  def save_employee(self):
    # save employee

# SRP adhered
class Employee:
  # ...

def calculate_salary(emp):
  # calculate salary

def save_employee(emp):
  # save employee

Open/Closed Principle (OCP)

The Open/Closed Principle states that classes should be open for extension but closed for modification. In other words, we should be able to extend the behavior of a class without modifying its source code.

Example in C:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// OCP violated
struct Shape {
  // ...
  void draw() {
    // draw shape
  }
};

// OCP adhered
struct Shape {
  // ...
};

void draw_circle(struct Shape* shape) {
  // draw circle
}

void draw_square(struct Shape* shape) {
  // draw square
}

Example in Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# OCP violated
class Shape:
  # ...
  def draw(self):
    # draw shape

# OCP adhered
class Shape:
  # ...

def draw_circle(shape):
  ...

def draw_square(shape):
  ...

Liskov Substitution Principle (LSP)

The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of its subclasses without affecting the functionality of the program.

Example in C:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// LSP violated
struct Bird {
  void fly() { ... }
};

struct Ostrich : Bird {
  void fly() {
    // can't fly
  }
};

// LSP adhered
struct Bird {
  // ...
};

struct Ostrich : Bird {
  // ...
};

Example in Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# LSP violated
class Bird:
  def fly(self):
    ...

class Ostrich(Bird):
  def fly(self):
    # can't fly

# LSP adhered
class Bird:
  ...

class Ostrich(Bird):
  ...

Interface Segregation Principle (ISP)

The Interface Segregation Principle states that a client should not be forced to depend on interfaces that it does not use. We should create small, cohesive interfaces that are specific to the needs of the client.

Example in C:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ISP violated
struct Worker {
  void work() { ... }
  void eat() { ... }
};

// ISP adhered
struct Workable {
  void work() { ... }
};

struct Eatable {
  void eat() { ... }
};

Example in Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# ISP violated
class Worker:
  def work(self):
    ...
  def eat(self):
    ...

# ISP adhered
class Workable:
  def work(self):
    ...

class Eatable:
  def eat(self):
    ...

Dependency Inversion Principle (DIP)

The Dependency Inversion Principle states that high-level modules should not depend on low-level modules. Both should depend on abstractions. Additionally, abstractions should not depend on details; details should depend on abstractions.

Example in C:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// DIP violated
struct Worker {
  // ...
};

struct Manager {
  void manage(struct Worker* worker) {
    // manage worker
  }
};

// DIP adhered
struct Worker {
  // ...
};

struct Manager {
  void manage(struct Workable* worker) {
    // manage worker
  }
};

Example in Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# DIP violated
class Worker:
  ...

class Manager:
  def manage(self, worker):
    ...

# DIP adhered
class Worker:
  ...

class Manager:
  def manage(self, worker):
    ...

Conclusion

Writing clean and SOLID code in object-oriented programming is crucial for building maintainable, flexible, and understandable software. By adhering to principles of clean code and the SOLID principles, we can create codebases that are easy to understand, maintain, and extend, in both C and Python.

I hope this blog post has provided you with a clear understanding of these concepts and their application in real-world programming. By following these principles, you can elevate the quality of your code and become a better software developer. Happy coding!


➡️ VT100 shell coloring for python logging


⬅️ Set up github keys


Go back to Posts.