Maintainable Code: Building Components of Large Scale Projects
Tags: learning, architecture, cleancode, python
Published: 2023-08-08 02:36:12

## Introduction


Every developer at some point has faced problems when it comes to implementing new features into legacy code. Your component needs to have another functionality implemented. What if the project that you are working on is getting bigger each day, how would you assure that creating new functionalities wouldn't be painful? By exploring a simple example we will understand what maintainable code really is.

_PS.: I choose Python due to its simplicity, what will be shown can be easily applied in any other modern language._

---

## Case Study 📝

So you are a developer building the back-end of a two numbers calculator. The most straightforward approach would be something like

```python
def calculate(first_number, second_number, operation):

    result = 0

    if operation == "addition":
        result = first_number + second_number
    
    elif operation == "subtraction":
        result = first_number - second_number

    elif operation == "multiplication":
        result = first_number * second_number
    
    elif operation == "division":
        result = first_number / second_number
    
    return result
```
What if you need to implement another operation, like raising the power of a number? Would you create another `elif` statement `operation == "power"`? What if you had to implement more complicated operations?

There are definitely better ways of writing the back-end of this calculator, that is what we are going to dive into.

---

## Refactoring the Code 💻

Whenever we think about large scale projects, it is always important to build components that can easily scale modifying as little as possible the existing code. Your new feature should be an extension of what is already there, not a restructure of it. That is what the 'O' of the S.O.L.I.D. principles stands for:

> software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification

Our goal is not to explain [SOLID Principles](https://en.wikipedia.org/wiki/SOLID) or [Design Patterns](https://refactoring.guru/design-patterns/what-is-pattern), but providing a solution for the problem we've pointed. For the moment let's keep these principles under the hood...

---

## 1. Creating Enum

It is important to define in our system which operations our calculator will handle, we can do it by creating an `Enum`.

```python
from enum import Enum

class Operations(Enum):
    ADDITION="addition"
    SUBTRACTION="subtraction"
    MULTIPLICATION="multiplication"
    DIVISION="division"
```

As soon as we have our `Enum` let's rewrite our function using the type we created

```python
def calculate(first_number: float, second_number: float, operation: Operations) -> float:

    result = 0

    if operation == Operations.ADDITION:
        result = first_number + second_number
    
    elif operation == Operations.SUBTRACTION:
        result = first_number - second_number

    elif operation == Operations.MULTIPLICATION:
        result = first_number * second_number
    
    elif operation == Operations.DIVISION:
        result = first_number / second_number
    
    return result
```
We are no longer manipulating strings to define which operation has to be performed. From now on every operation is going to be strictly defined in the `Enum` created. 

---

## 2. Segregating Responsibilities

As we discussed earlier what if we want to implement more complicated operations? It does not seem like a good idea to let this function get bigger and bigger as we need to grow our project. So let's decouple the main calculator function from its functionalities

### 2.1. Operations

```python
def addition(first_number: float, second_number: float) -> float:
    return first_number + second_number

def subtraction(first_number: float, second_number: float) -> float:
    return first_number - second_number

def multiplication(first_number: float, second_number: float) -> float:
    return first_number * second_number

def division(first_number: float, second_number: float) -> float:
    return first_number / second_number
```

### 2.2. Calculator

```python
def calculate(first_number: float, second_number: float, operation: Operations) -> float:

    if operation == Operations.ADDITION:
        return addition(first_number, second_number)
    
    if operation == Operations.SUBTRACTION:
        return subtraction(first_number, second_number)

    if operation == Operations.MULTIPLICATION:
        return multiplication(first_number, second_number)
    
    if operation == Operations.DIVISION:
        return division(first_number, second_number)

    raise ValueError(f"{Operations} value not supported => {operation}")
```

As we can see, now it is much easier to create and apply new operations to our calculator. We also took the opportunity to create error handling. If an invalid value for operation is inputted, an error will be raised so we can more easily track unexpected arguments so avoiding unwanted behaviors. 

However, our initial proposal was building it as if it were made for large scale projects, so let's take a step further and improve this function...

---

## 3. Taking a Step Further

In our example we have simple mathematical operations to be implemented. Nevertheless if we were building features which depend on complex business logic, we should make our code more generic, so to speak. We must structure our components so they rely on interfaces, not on concrete implementations. This is also a SOLID principle, so it's highly recommended to know these concepts by heart.

### 3.1. Creating Classes for Implementations

Since we are taking into account complex implementations, let's create classes to our calculator operations; these classes will have a public method called `calculate`. For the sake of simplicity only one example will be shown

```python
class AdditionOperation(CalculatorOperationsInterface):

    def __init__(self, first_number: float, second_number: float):
        self.__first_number = first_number
        self.__second_number = second_number
    
    # Here you can create more methods to apply business logic    

    def calculate(self) -> float:
        return self.__first_number + self.__second_number
```

One might be wondering what is `CalculatorOperationsInterface`. That is the interface we are applying to all of our operations.

### 3.2. Creating Interface

Now we need an interface for all our operations to conform. This interface requires a method which returns a `float`. That is how we create interfaces in Python

```python
from abc import ABC, abstractmethod

class CalculatorOperationsInterface(ABC):

    @abstractmethod
    def calculate(init) -> float:
        '''calculate operations'''
```

Every class that implements this interface must have the `calculate` method.

### 3.3. Factory Pattern

In order to make it more generic, we need to have a component that will create the objects of the implementation classes. The component just mentioned is a Factory Design Pattern. This is how it is gonna work: depending on the operation - `Enum` - we're going to provide, our Factory has to create an object in which we can call the method `calculate`. 

Since new Python versions 3.10+ have introduced `Switch` statements, that is what we are going to use, however you are free to use `if` statements too

```python
class CalculatorOperationsFactory:

    @staticmethod
    def create_operation_object(
        operation: Operations, 
        first_number: float, 
        second_number: float
    ) -> CalculatorOperationsInterface:
        
        match operation:
            
            case Operations.ADDITION:
                return AdditionOperation(
                    first_number=first_number,
                    second_number=second_number
                )
            
            case Operations.SUBTRACTION:
                return SubtrationOperation(
                    first_number=first_number,
                    second_number=second_number
                ) 

            case Operations.MULTIPLICATION:
                return MultiplicationOperation(
                    first_number=first_number,
                    second_number=second_number
                ) 
            
            case Operations.DIVISION:
                return DivisionOperation(
                    first_number=first_number,
                    second_number=second_number
                )
            
            case _: # default case
                raise ValueError(f"Enter a valid {Operations} => '{operation}' was entered")
```

### 3.4. Refactored Calculator Function

Once we have created our components, we can present our new calculator function

```python
def calculate(first_number: float, second_number: float, operation: Operations) -> float:

    operation_object = CalculatorOperationsFactory.create_operation_object(
        operation=operation,
        first_number=first_number,
        second_number=second_number,
    )

    return operation_object.calculate()
```

---

## 4. What We Have Built


Okay, you might be thinking that this whole structure for a simple calculator is kind of overkill, and you are not completely wrong. However, what we built is much more than a calculator. What we have accomplished is a maintainable solution to apply to large scale systems whenever we need to implement new features to an existing component, as long as it is possible to gather those input conditions inside an `Enum`.


### 4.1. How to Create New Features?

Creating new features is as simple as:

_1. Creating a new `Enum` value_
_2. Creating the feature class implementing the interface created_
_3. Creating a new `case` in Factory_

As you can perceive almost no existing code is modified to implement new features. This is ideal when we are in a project in which dozens of developers are contributing to, it reduces the possibility of conflicts and makes it easier to maintain.