Blog Datasheets Home About me Clients My work Services Contact

G2Labs Grzegorz Grzęda

Refactoring legacy code to adhere to SOLID principles

May 17, 2023

Refactoring Legacy Code to Adhere to SOLID Principles

In software development, refactoring is the process of restructuring existing computer code without changing its external behavior. Often, legacy codebases need to be refactored to make them more maintainable, flexible, and scalable. One popular approach to refactoring is to adhere to the SOLID principles, which are a set of five design principles that aim to make software designs more understandable, flexible, and maintainable.

In this blog post, we’ll explore the concepts of SOLID principles and demonstrate how to refactor legacy code to adhere to these principles using examples in C and Python.

Understanding SOLID Principles

SOLID is an acronym that stands for:

Single Responsibility Principle (SRP)

The Single Responsibility Principle states that a class should have only one reason to change. In other words, a class should have only one responsibility.

Open/Closed Principle (OCP)

The Open/Closed Principle states that a class should be open for extension but closed for modification. This means that the behavior of a class should be extendable without modifying its source code.

Liskov Substitution Principle (LSP)

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

Interface Segregation Principle (ISP)

The Interface Segregation Principle states that a client should not be forced to depend on methods it does not use.

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.

Now, let’s dive into some examples of refactoring legacy code to adhere to these SOLID principles.

Refactoring in C

Example: Single Responsibility Principle

1
2
3
4
5
6
// Legacy code with multiple responsibilities
void processInputAndPrintResults(FILE *inputFile) {
    // code to read input from file
    // code to process input
    // code to print results
}
1
2
3
4
5
6
7
8
// Refactored code conforming to Single Responsibility Principle
void processInput(FILE *inputFile) {
    // code to read input from file
    // code to process input
}
void printResults(const char *results) {
    // code to print results
}

Example: Open/Closed Principle

1
2
3
4
5
6
7
8
// Legacy code without adherence to Open/Closed Principle
void calculateArea(float *shape, char *type) {
    if (strcmp(type, "circle") == 0) {
        // calculate area of circle
    } else if (strcmp(type, "rectangle") == 0) {
        // calculate area of rectangle
    }
}
1
2
3
4
// Refactored code conforming to Open/Closed Principle
void calculateArea(Shape *shape) {
    shape->calculateArea();
}

Refactoring in Python

Example: Liskov Substitution Principle

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Legacy code violating Liskov Substitution Principle
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

class Square(Rectangle):
    def __init__(self, size):
        self.width = size
        self.height = size
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# Refactored code conforming to Liskov Substitution Principle
class Shape:
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Square(Shape):
    def __init__(self, size):
        self.size = size

    def area(self):
        return self.size * self.size

Example: Dependency Inversion Principle

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Legacy code without adherence to Dependency Inversion Principle
class Authenticator:
    def authenticate(self, username, password):
        # authenticate user
        return True

class UserManager:
    def __init__(self, authenticator):
        self.authenticator = authenticator

    def login(self, username, password):
        if self.authenticator.authenticate(username, password):
            # login user
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Refactored code conforming to Dependency Inversion Principle
class Authenticatable:
    def authenticate(self, username, password):
        pass

class Authenticator(Authenticatable):
    def authenticate(self, username, password):
        # authenticate user
        return True

class UserManager:
    def __init__(self, authenticatable):
        self.authenticatable = authenticatable

    def login(self, username, password):
        if self.authenticatable.authenticate(username, password):
            # login user

Conclusion

Refactoring legacy code to adhere to SOLID principles can significantly improve the maintainability and flexibility of a codebase. By following the examples outlined in this post, you can effectively refactor legacy code in C and Python to conform to the Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion Principles. Adhering to these principles can lead to cleaner, more modular, and more extensible code, ultimately benefiting both developers and end-users.

I hope you found this post helpful in understanding how to apply SOLID principles to your own codebase. If you have any questions or want to share your own experiences with refactoring legacy code, feel free to leave a comment below!


➡️ Template Method design pattern


⬅️ Observer design pattern


Go back to Posts.