DEV Community

James Hickey
James Hickey

Posted on • Updated on • Originally published at blog.jamesmichaelhickey.com

Refactoring Legacy Monoliths - Part 1: First Steps

Originally published on my blog.

Where do you even begin when considering "fixing" or refactoring legacy monoliths?

What Is A Legacy Monolith?

In the book Working Effectively with Legacy Code, "legacy" software is merely defined as "code without tests." A few notes are made which include the idea that software with poor design or unmaintainable code is a large piece of what "legacy" means.

"Refactoring legacy software" means refactoring your system to allow for code testing, and then implementing those tests.

A "monolith" is basically "all your code is one place." In other words, you have not separated different logical areas of your product into isolated areas.

If you are working on a .NET based web application (for example) and all your code is in one project (the web project) - then you most definitely have a monolith.

If your deploy process requires that the entire system is "pushed" when any new features are added or bugs are fixed - then that's a monolith.

Realistic Goals

Before we being changing existing code, the idea that "legacy" code exists is an indication that the software process for this product is not working well. We need to consider a higher level question: "How do we even create software?"

Problems that you may experience in legacy projects, I think, boil down to one core issue:

Software is not designed using known current industry standards.

What are some of these standards?

  • Testing code
  • Separating business logic from presentation logic
  • Not re-inventing the wheel when reliable libraries exist to do the thing you need
  • Avoiding depreciated third-party software (whether deemed so by vendor or development community)

Can you think of any more? I'd love to hear from you in the comments. :)

Nip It In The Bud

So then, our first steps need to begin addressing these issues. But even before that, we need to realize that all future software creation is still following whatever process currently exists!

Having gone through this recently and creating a foundation for future development to - at the very least - allow for these standards to be followed, I think I have some insight. And so our first step is just that:

Make the system at least allow future development to follow industry standards.

Practical Steps

The most important point, which goes back to the definition of "legacy" software - is that you need code tests. Why?

  • A clearer definition of system requirements (since tests correspond to individual business requirement)
  • Forces your system to be modular (i.e. code that can be tested well is generally designed well)
  • Early safety net when your code does something wrong

But, to be able to test properly, you need to have your business code (i.e. "business logic") separate from your (for example) web project. You can't test business rules unless you can test business logic on it's own. Splitting your code into (at least) 2 different layers is necessary for web projects because:

Your business code can rely on database access abstractions rather than database access implementations.

What? Let me ask you a question: "Can you test your code without having an active database available?" In true "legacy" projects, that's answered 99% of the time with "no".

What we need is a way to make our code rely on an interface which exposes methods that provide database access. Then, we can create our production code against that interface. Our system needs to somehow automatically "pass in" a concrete implementation of that interface to our business code.

Our tests, on the other hand, can use a stub or mock object which implement that interface. Our business code will still do what it does and not crash because there's no database connection.

We're Thinking...

So we're thinking about the core issues and what we need to do to solve them. In the next part of the series, we'll look at trying to convince management that we do in fact need to change how software is made in our organization.

Let me know what you think - or if I've missed something etc. Thanks!

Keep In Touch

Don't forget to connect with me on twitter or LinkedIn!

Navigating Your Software Development Career

An e-mail newsletter where I'll answer subscriber questions and offer advice around topics like:

✔ What are the general stages of a software developer?
✔ How do I know which stage I'm at? How do I get to the next stage?
✔ What is a tech leader and how do I become one?

Sound interesting? Join the community!

Top comments (7)

Collapse
 
pim profile image
Pim

Great post! I'm embarrassed to admit that I only recently started implementing unit testing in a serious way. Two things became very apparent to me, and very quickly. Despite thinking I wrote "clean" software, it was in fact polluted with things like SRP violations and rigid dependencies. Having to implement unit testing, not only made my code better but actually made me like coding all over. Equipping me with a new set of glasses to peer through. Oddly enough the small bit of functional programming I dabbled in helped a lot, which I've learnt I do not like at all, it just feels clunky to me.

As a total side note, Coravel looks freaking AWESOME. I haven't had a chance to use it yet. But I will most certainly be doing that on Monday. Are you accepting contributors? Pull requests?

Collapse
 
jamesmh profile image
James Hickey • Edited

Thanks for feedback! I agree 100% - I'll be covering the issue of dependencies in a future article. One of the main benefits of functional programming is the idea of pure functions - functions that get an input and give an output and don't change the outside world (no side-effects). If you build your objects/classes this way you get the same benefits.

I'm taking contributors for Coravel - mostly for smaller issues right now. If you have any ideas then feel free to create an issue and we can go from there ;) Thanks for the comment Pim!

Collapse
 
aleksikauppila profile image
Aleksi Kauppila

Good post! Separation of business domain from presentation and persistence concerns is essential for long running development. In legacy systems these can however be completely mixed and separation will constitute to a full rewrite.

Collapse
 
bosepchuk profile image
Blaine Osepchuk • Edited

Great post, James.

I've gone through this a couple of times and in my experience there are a few steps that help "prepare the code" for the changes you are suggesting such as:

  • Making sure the entire project is under modern version control
  • Running the entire project through a code formatter
  • Delete dead and commented-out code
  • Delete unused features
  • Run a bunch of static analysis tools on the code to get a feel for the magnitude of the problem
  • Run the existing tests with coverage and see where that's at
  • Identify and fix any emergency issues (update libraries to fix security vulnerabilities, or update the code to run on a newer version of the language if the currently used version is no longer supported, fix obvious and serious security vulnerabilities in your code)
  • Flag suspicious code with TODO or FIXME contents for further inspection at a future date

If you do these steps, I guarantee you'll find things that shock and surprise you and that you'll be able to delete quite a bit of code (about 10% of the project in my limited experience).

There's a really good book called Modernizing Legacy Applications in PHP, which I highly recommend if PHP is your language.

After that you have some options. I'm not exactly sure where James is going to go next but I'm sure he'll have good advice.

We use tons of the "extract method" and "extract class" refactorings to break out pieces of testable code from dependencies using the humble object pattern. We also break dependencies by using dependency injection. Classes and methods in legacy code are often too big and are doing too many things. So our first priority is usually to break things up and test what we can.

But there are a number of reasonable ways forward from here depending on your priorities.

Collapse
 
jamesmh profile image
James Hickey

Love it! Should turn this into a blog post altogether ;)

I'll have to repeat much of what you said as the series continues. I think one of the main issues that need to be tackled is the direction of dependencies and (as you mentioned in much more detail) how to break things apart in a way that will isolate dependencies. This leads to being able to test individual parts in an isolated fashion.

But I'm just repeating what you've said :)

I appreciate the additions - I'm sure this will be super helpful to anyone who's interested in this topic.

Collapse
 
fgiraldi profile image
fgiraldi

Nice post. I wish I would have one of this readings many years ago. I've been developing in PHP using what I learned in college since 2001 (I'm 39 years old) and back then my only tools where the PHP documentation and a text editor. All I could wrote was monolithic code and, embarrasingly, I´ve been doing that for the last 17 years.
As of today, I know what a framework is, what the MVC stack means and their benefits. I´ve changed mi mind thanks to some of my frineds who showed me a better way of coding and now I´m on a new train with new (better) concepts and a refreshed mind about how to make my daily job, which turns out to be my passion, so I feel again I´m just enjoying my hobby again.
Thanks for the post.

Collapse
 
jamesmh profile image
James Hickey

Thanks for the comment! Drop me a message if you ever have questions etc. :)