Finally, the tests. They are what will give you confidence in future modifications, so if you are building an abstract layer, we need to test it’s outcome!

Test structure:

As an example, let’s consider a data that invokes a toUpperCase method in its transformation. In the end, this abstraction will provide us with all characters in uppercase.

Fake structures:

The structures that are stubs or fakes work as our helpers. Today, we have various libraries that can manipulate and structure returns through mocking, but to fully grasp the power of the pattern, we will perform integration and testing manually.

Utilization:

In the example, we will modify the Any structures to real objects so that it is possible to visualize the execution flow clearly.

Execution:

In the execution structure, which is where the interface would be implemented in the legacy code, we have the definition of the ExecutionFlowFake class.

Simulation structure of the emitter from the legacy code.

In this structure, the data will be created externally to the class. This allows us to define the call manually, without the need for a connection with the legacy code. This allows the structure to be completely independent of its implementation.

It’s also possible to implement it directly; however, in the example, a concrete class was used to demonstrate pragmatically and directly that it works for any object-oriented language.

Reception or emission:

Moving on to the second phase of the test, a fake will also be created for the data output.

Implementation which receives the message.

It’s worth noting that in the test structures, which are not the real structures, we can create helpers to validate what was actually received by the class. In the example shown, the variable and method were created to allow validation of what was actually received by the fake implementation that was created.

Production structure:

In the production structure, we have the transformation, execution, and validation methods. In our example, we will only have a converter to transform the String into uppercase letters. Finally, its emission will be a plain String, without encapsulation in new data objects.

Production structure

In this example, we created a simple structure, but in its real-world application, there will be various operators, and probably some methods will be involved before the emission.

Test execution

Returning to the test implementation, follow the execution logic, and it will be noticeable that everything is now controllable and easy to manipulate within the structure that interconnects the execution points.

Finally, we’re able to successfully perform our tests, and thus, we can continuously improve the structure of our code through a control pattern.

It’s noteworthy that we face daily challenges with old code, and careful implementation is necessary. It is common to have deadlines for development, and often there is limited time that does not allow us to make modifications to existing structures.

The principle of this pattern is to enable you, a software engineer, to create abstractions that over time allow for change. Your legacy structures won’t disappear quickly, but with small accumulated improvements in the medium and long term, results will come, and it will be simple through the interface structure to replace what was once difficult.

The pattern demonstrated here in the example is one way to implement it, and its limit is your imagination. After all, each context is unique!

Legacy code is present in almost all the software I have worked on, and I always try to study mechanisms on how to deal with it. Making the code more efficient for the team and everyone who will work in that context someday.

Create your abstractions and be an engineer who can work with various types of code.

I hope you enjoyed it! Gabriel Brasileiro.

Source link