phparchitect-2019-08

(Rick Simeone) #1

18 \ August 2019 \ http://www.phparch.com


How to Deal With Legacy Code


mind, delegating the rewrite to another
team risks repeating the same mistakes.


“We’re programmers. Program-
mers are, in their hearts, architects,
and the first thing they want to do
when they get to a site is to bulldoze
the place flat and build something
g r a n d .”
–Joel Spolsky

Refactoring of applications often
seems to be an ideal solution. Why
read the old code? I’m going to level
it to the ground and implement it ten
times better. However, will I find time
to maintain the old and new system
at the same time? Is the competition
so far behind that they can’t catch up
with us while we stand still? Sometimes
refactoring is a better and safer way
out. Refactoring allows you to get the
so-called quick win. It allows you to
improve quality or performance in the
short term and gain time to write the
same thing correctly from the begin-
ning. Serverless and microservices are
options that are also worth considering
in this case.


Plan


While working on a large legacy
project, we have a wide range of possi-
bilities when it comes to improving
the state of affairs. The bigger the
project, the more time it takes to get
it right. You have to have a plan before
you can take the first step. We need to
find out where we are at the moment
and identify the most important short
and long-term goals. It is worth noting
these goals, which makes it easier to
settle the commitments later. Maybe
we want to break down the monolith
into microservices? Maybe we want
to upgrade to the latest version of the
framework and use its capabilities? The
goal may also be to change the language
or technology stack, improve perfor-
mance, or cover it with tests for further
refactorization. Having a plan shows
that you know what you want to do.
Maybe you don’t know how to do it yet,
but knowing “what” is already a success.


Depending on the situation, we can
use some metrics as a success rate, such
as the number of requests per second
or test code coverage. Then you can
clearly state whether the goal has been
achieved.

Tests


“When we refactor, we should
have tests. To put tests in place, we
often have to refactor.”
–Michael Feathers

In the case of simple errors, it is
sometimes tempting to perform a quick
fix, add a line or two, and return to your
daily work. But how can you be sure
this fix works? The old code was prob-
ably not covered by tests, and you’re
only adding another code without tests.
The patterns proposed by Feathers work
well here: Sprout Method and Sprout
Class. We try to pull a piece of logic into
a separate method or class and cover
this extracted code thoroughly with
tests. In the old place, we leave only the
call to this new code. Thanks to this we
have new code, confirmed by tests and
less untested code in our legacy code-
base.
Making changes without tests makes
it easy to make an unintended mistake.
Most of the development is related to
maintaining the current behavior of the
application—if not for this, we could
write code much faster.
Another method is the “golden
master” technique. For different sets of
data at the input, we register what we
get at the output. After making changes,
we make sure the output is always the
same. This method can never replace
unit tests, but it is a good step towards
having tests.

20/80 (Pareto Principle)
The Pareto Principle works well when
choosing the tasks in the first place.
According to this rule, 20% of the effort
is responsible for 80% of the results. So,
let’s look for easy wins. If the goal is to
improve performance, let’s look for a
bottleneck in the application. There is a

chance a change in one place is enough
to improve performance noticeably. If
you want to do solid refactoring, start
by using automatic refactoring tools
(IntelliJ tells you enough). You can put
relatively little effort into it, and the
code improves immediately.
In the most straightforward cases, it
is enough to add a cache layer to relieve
the load on the database or shorten the
response time of the application. In
more complex cases, it means changing
an inefficient algorithm to a better one,
if you find a suitable place in the code.

Strangler Pattern
Strangler figs are tropical plants of
the fig type. Seeds spread by birds often
germinate on trees, then they start to
grow roots. The roots are on their way
to the ground, sometimes entangling
the tree on which they grow. An entan-
gled tree can die as a result. We can
apply an analogous concept to get rid
of legacy code while the application
is still operational and maintained. In
the meantime, we write new code next
to it, that handles the same events in
the system. The new code can also be
responsible only for new functions. In
the simplest case, we can rewrite the old
code to a new one and piece by piece,
switch part of the traffic from the old
to the new system. Ultimately, the new
code takes over all the responsibilities,
and the old one, though it works, no
longer receives any traffic and can be
safely removed.
By using this pattern, we minimize
the risk that something may go wrong
while rewriting the application. We
do it in small steps, and if something
unwanted happens, we can always go
back to the old code. This approach
works well if you want to break the
monolith into microservices.

Mikado Method
Mikado is an old social game with
simple rules. We collect spilled and
mixed sticks one by one, so as not to
touch any other by chance. Each color
is worth a different number of points,
the person who scores the most wins.
Free download pdf