How to Decouple a Legacy System
Tags: codenewbie, programming, poo, tutorial
Published: 2021-03-01 04:37:33

## An exercise improving legacy code

_There are many articles explaining how to make a good design and what rules to follow. In this note we will see a concrete example on how to convert a_ [_legacy design_](https://en.wikipedia.org/wiki/Legacy_system) _into a better one._

# The problem

Many existing systems have coupling problems. Therefore, their maintainability is reduced. Making a change in this type of system brings a large ripple effect.

{% post https://dev.to/mcsee/coupling-the-one-and-only-software-design-problem-2pd7 %}

{% post https://dev.to/mcsee/code-smell-16-ripple-effect-3881 %}

Let’s assume we have an existing process.

The system applies various algorithms to deduce the [hyper-parameters](https://en.wikipedia.org/wiki/Hyperparameter_(machine_learning)) of a supervised learning model.

A new requirement is requested:

> To be able to see, in production, data on the performance of each strategy in real time.

## Decoupling the system

Let’s see the process entry point:

{% gist https://gist.github.com/mcsee/f5a1eb2029a0fe9eda501b15af24dc6e %}

… the supervised learning class:

{% gist https://gist.github.com/mcsee/c78cb4669ac25321a7bd5df168291394 %}

and the method invoked:

{% gist https://gist.github.com/mcsee/ebfd83f5a1e176d6a769de243fb8d422 %}

In the case of a productive system, the first thing we must do is identify its current coverage. The system has a series of automated unit and functional tests.

To measure coverage we will use the [Mutation testing technique](https://en.wikipedia.org/wiki/Mutation_testing).

{% gist https://gist.github.com/mcsee/956c2e3d5ce16ec1964021c08441cceb %}

Unfortunately just a single test fails, so we discovered that the process is not covered and we see that the Michael Feathers maxim is sadly applied:

> “An inherited system is one that has no tests”

The strategy to refactor an inherited system is to **cover** the existing functionality before making any changes.

## 1 — Creating deferred tests.

Writing tests reveals good design interfaces among objects. Due to the current solution and the coupling it has incorporated, it is very difficult to write tests.

However, we cannot refactor to write the tests without writing the tests previously. It seems that we are facing a vicious circle.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rl34ggn1y4lp0j6hwhce.jpg)
Photo by [Justin Chen](https://unsplash.com/@jkcphotos) on [Unsplash](https://unsplash.com/s/photos/no-exit)

The possible solution to this deadlock is to write the tests **declaratively**, thus generating better interfaces.

We will run them manually until the coupling is resolved.

## 2 — We write tests to cover pre-existing functionality.

Tests can be written with a tool from the [xUnit](https://en.wikipedia.org/wiki/XUnit) family with a false assertion (they always fail).

After having covered (for now manually) the necessary cases we can start with the refactor.

## 3 — The class name does not represent a real name in the bijection.

Helpers do not exist in the real world, nor should they exist in any computable model.

Let’s think about the responsibilities to choose the name in [MAPPER](https://codeburst.io/what-is-software-9a78c1172cf9).

{% post https://dev.to/mcsee/the-one-and-only-software-design-principle-3086 %}

{% gist https://gist.github.com/mcsee/3d11a4f44134f4de3f3d60b286f19dd8 %}

For now the name is good enough, and it gives us an idea of the responsibilities of your instances in the real world.

## 4 — The class is a singleton.

There are no valid reasons to use singletons. This fact, in addition to generating all the problems described here:

{% post https://dev.to/mcsee/singleton-the-root-of-all-evil-50bh %}

yields a very implemental invocation (coupled to _getInstance()_) and not very declarative...

{% gist https://gist.github.com/mcsee/756c4d1547d031a6a184373abbab3ce7 %}

which we will change to:

{% gist https://gist.github.com/mcsee/244f934c8d755f7ad018ce3c98b2a145 %}

leaving the class definition as follows:

{% gist  https://gist.github.com/mcsee/43845711d2a84e446d62530082aef0d9 %}

An important design rule is:

> Do not subclass concrete classes.

If the language allows this, we explicitly declare it:

{% gist 
https://gist.github.com/mcsee/7a685e33d9b00c13d5192a9dccc0c30a %}

## 5 — The same parameter in all methods.

The object is created and then it gets a magic parameter setting the identifier of the process to be optimized. This argument travels by all methods.

This is a _code smell_ suggesting us to check the **cohesion** between this parameter and the process.

{% gist https://gist.github.com/mcsee/b471c753797f1834ddc3b69c08c19bc8 %}

Looking at **bijection** we conclude there can be no algorithm without a process. We don’t want to have a class with _setters_ to mutate it:

{% post https://dev.to/mcsee/nude-models-part-i-setters-5c9i %}

Therefore we will pass all the **essential** attributes during construction.

The way to know if an attribute is **essential** is to take away all the responsibilities associated with that object. If it can no longer carry out its responsibilities, it is because the attribute belongs to the [minimal attribute set](https://en.wikipedia.org/wiki/Maximal_and_minimal_elements).

{% gist  https://gist.github.com/mcsee/29ef86bca93e15f691a1a40e12473e4c %}

In this way, the strategy is **immutable** in its essence, with all the benefits it brings us.

{% post https://dev.to/mcsee/the-evil-powers-of-mutants-1noo %}

## 6 — We find a design pattern.

The process, according to bijection, models a real world process. This seems to fit the [**Command**](https://en.wikipedia.org/wiki/Command_pattern)pattern.

However, we believe that it is closer to a [**method object**](https://refactoring.guru/replace-method-with-method-object) where there is an ordered sequence of executions, modeling the different steps of an algorithm.

## 7 — Interchangeable behavior resembles yet another pattern.

As the name we assigned to the object according to its responsibilities suggests, this process models an execution strategy that will compete with other polymorphic strategies.

This is the intention of the [Strategy pattern](https://en.wikipedia.org/wiki/Strategy_pattern).

Names should match the observed responsibilities.

{% post https://dev.to/mcsee/what-exactly-is-a-name-part-i-the-quest-61k %}

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q7dbo3bxp9kp3j4ftn9l.jpg)

Photo by [Nicolas Hoizey](https://unsplash.com/@nhoizey) on [Unsplash](https://unsplash.com/s/photos/athletics)

{% gist https://gist.github.com/mcsee/719ec5c9d38316fadca3749b1cd13e6f %}

## 8 — We remove nulls.

There is never a valid reason to use **null**. **Null** does not exist in real life.

It violates the principle of **bijection** and generates coupling between the function caller and the argument. Also, it generates unnecessary **ifs** as **null** is not polymorphic with any other object.

{% gist https://gist.github.com/mcsee/862500b561af3151dee26e8c645ee4f7 %}

We change the absence of the argument to a boolean truth value.

{% post https://dev.to/mcsee/null-the-billion-dollar-mistake-1h1c %}

## 9 — We remove the default parameters.

The private function in the previous example has a default parameter.

Default parameters produce **coupling** and ripple effect. They are available for the programmer laziness. Since it is a private function the replacement scope is the same class. We make it explicit, replacing all invocations:

{% post https://dev.to/mcsee/code-smell-19-optional-arguments-48gc %}

## 10 — We remove hard coded constants.

These constants coupled within the code will not allow us to make good tests “manipulating time”.

Remember that the tests have to **be in control of the entire environment** and the time is **global** and **fragile** to match the tests.

From now on, it will be an essential parameter of object creation (Refactoring by [adding parameters](https://refactoring.guru/es/add-parameter) is a safe task, which can be done by any modern IDE.

{% post https://dev.to/mcsee/code-smell-02-constants-and-magic-numbers-obb %}

## 11 — We decouple the log.

The log stores relevant information in production about the executions of the strategy. As usual, using a _Singleton_ as a global reference.

This new bond prevents us from being able to test it. This _Singleton_ is in another module over which we have no control, so we are going to use a wrapping technique.

{% gist https://gist.github.com/mcsee/34f6706535f866c50e5aaee21984388c %}

Besides from being a Singleton, the log uses **static** class messages.

{% gist https://gist.github.com/mcsee/783152c267fa646dc34099a5ad9d4d84 %}

Let’s remember that:

> The only protocol that a class should contain is the one related to its single responsibility (the S for Solid): creating instances.

Since the reference is to a static method, we cannot replace the class call with a polymorphic method. Instead, we will use an anonymous function.

{% gist https://gist.github.com/mcsee/bf90641050515afd1b1271572f0d9507 %}

Then, we can decouple the reference to the log and extract it from the class by reducing the coupling, generating better cohesion from the strategy and favoring its testability.

We can now use the object with several different kind of loggers (like [tests doubles](https://en.wikipedia.org/wiki/Test_double)).

{% gist https://gist.github.com/mcsee/135d5fa5b2e90ece1abc555f05cf7229 %}

With the call from the productive code:

{% gist https://gist.github.com/mcsee/eafe7b5c3391c87cbe12d06ef6fe2bf6 %}

And the call from the tests:

{% gist https://gist.github.com/mcsee/65c634a08f4dd86c9827d9f3fcbbbcba %}

## 12 — We reify objects.

On the way of our refactoring we find some fixes with persistent data. Such data travel cohesively, so it makes sense to think of it as an object with real-world responsibilities:

{% gist https://gist.github.com/mcsee/37108b975f23284e69eff888ba7b146e %}

By creating the new concept, we are in danger of building an [anemic model](https://en.wikipedia.org/wiki/Anemic_domain_model). Let’s see what hidden responsibilities you have:

{% gist https://gist.github.com/mcsee/211688cc5fcdc55ca9d26c708346fa85 %}

{% post https://dev.to/mcsee/code-smell-01-anemic-models-4dg2 %}

## 13 — We complete the coverage.

We did not forget to program the tests that we could not write at the beginning. As we have a much less coupled design it is now very easy to do.

{% gist https://gist.github.com/mcsee/c278dcb011a45969b27b4752add129bb %}

And our system is much less “legacy” compared to when we found it.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4f8bc5ddl4frfbdn9qiq.jpg)

Photo by [Kelly Sikkema](https://unsplash.com/@kellysikkema) on [Unsplash](https://unsplash.com/s/photos/tidy)

# Summary

After hard iterative and incremental work, through short steps, we have achieved a better solution in the following aspects:

*   Less Coupling.
*   Immutability.
*   Better Names.
*   No Setters / Getters.
*   No Ifs.
*   Without Null.
*   Without Singletons.
*   No default parameters.
*   Better test coverage.
*   Following the Open/Closed principle (Solid’s O) to be able to add new polymorphic algorithms.
*   Following the principle of Single Responsibility (The S for Solid).
*   Without overloading the classes with protocol.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cmz80j8qddizz71w0g2x.jpeg)

Photo by [Zac Farmer](https://unsplash.com/@zacfarmerart) on [Unsplash](https://unsplash.com/s/photos/very-old)

# Conclusions

Modifying an existing system by improving its design is possible, taking into account clear design rules and taking small steps. We must have professional responsibility and courage to make the relevant changes, leaving a much better solution than when we found it.

We can even make TDD on legacy systems using these techniques.

{% post https://dev.to/mcsee/how-to-squeeze-test-driven-development-on-legacy-systems-8m9 %}

* * *

Part of the objective of this series of articles is to generate spaces for debate and discussion on software design.

We look forward to comments and suggestions on this article.

This article is also available in Spanish [here](/diseño-de-software/cómo-desacoplar-un-sistema-heredado-e7fca5ffe628).