Living with Legacy Code

Spread the news

Chances are you have some legacy code that you have to deal with.  If not, then I’m guessing you haven’t been programming very long and are doing everything on your own.  Legacy code is old code – it uses older technologies, generally has some technical debt, and takes longer to modify than packages you create from scratch.  Since we all deal with this, but to some varying degree, I’m going to approach the topic as if you are coming into a project that may not be your own and is rather old and clunky!

Defining Legacy Code

Maybe you’re looking at your code and seeing some good things and some bad. That is common. Paul M. Jones, in his book, Modernizing Legacy Applications in PHP, gives a pretty good list on the first page of chapter 1 of some good indicators that a PHP project is definitely considered Legacy Code, saying that Legacy code will have some of these characteristics, but not necessarily all of them:

  • Uses page scripts placed in document root of web server
  • Has special logic to die() or exit() if a certain value is not set.
  • Architecture is include-oriented instead of class- or object-oriented.
  • Relies heavily on functions instead of class methods
  • Page scripts, classes, and functions combine model-view-controller concerns into the same scope
  • Shows evidence of one or more incomplete attempts to rewrite
  • Has no automated test suite for the developers to run

I highly recommend every developer read this book and take it to heart. I have definitely had to work with this kind of code on a regular basis, and sometimes it just sucked the life right out of me! Let’s look at some of the ways that we can deal with this stuff and avoid dreading it.

Staying Sane

There are five main ways that I believe a developer should attack every Legacy Code problem. We all know that a rewrite is rarely possible due to both time and money constraints, but there are some ways to make the process of a gradual “rewrite” easier, while doing newer stuff in a more modern way.

  1. Get some semblance of testing
  2. Modernize code to run in the current version of PHP
  3. Create a “hybrid” system (support a new MVC as well as the old way of doing things)
  4. Start modern coding for all new features
  5. Bucketize and Modernize surrounding code as you go

Testing

Testing should always be the first thing to start implementing in the old Legacy codebase.  Unfortunately PHPUnit probably can’t do anything with that old mess, but we need to have it ready for all the new stuff.  Automated testing is of utmost importance because it allows the developer to quickly check their changes along the way to make sure that nothing is broken because of the new code.  With that, it would be detrimental if a change was made and released only to find out that now the shopping cart doesn’t work and there are no orders coming in.  This might deter users from the site at the moment, but worse, it also causes them to loose a little faith in the company – if dev issues are allowed to bubble to the top, then maybe security issues will to!

Step 1 would be to implement end-to-end testing.  There are multiple packages that can be used for this. Many are based on CasperJS or Selenium.  The idea is that the test package opens a web browser, goes to the test site, and performs an automated task. Maybe it will search for a particular term, add the first item to the cart, and check out. If you run an online store, then this would be a critical action for users.  It is up to the company to determine what these critical tests are and create the tests accordingly. These are referred to as “Acceptance Tests,” but you can also make some “Functional Tests” to do similar things. Functional Tests are tests that hit a particular page of the application with specific GET/POST variables set and then check for the presence of certain characteristics in the output.  This could be used to make sure that the search function of the website is returning the right things and doesn’t have any errors.

After defining these acceptance/functional end-to-end tests, it is important to get ready for modern coding and also get the system set up for Unit Tests. At this point I’d like to point out that there is a great package available to PHP developers called Codeception.  Codeception is a package that gives you the ability to write all three kinds of tests in one system.  It simplifies the process of Behavior Driven Development (similar to TDD, but based on User Behavior) and gives you all the tools you will need to get everything set up automatically.  You will find great docs at their site, codeception.com. There may be a few things in the old system that can be Unit tested, but it is highly unlikely that there is much depending on how old this old stuff really is.

One thing to keep in mind at this stage is that there is really no way to perfectly test the spaghetti code found in legacy code, so don’t overdo it.  This stage really is about determining the critical points in the system and getting some automated tests in place so you can easily double check that the system will perform those critical tests on release.

Modernizing to Current PHP Version

Continuing to run an old version of PHP is dangerous.  At the time of writing, PHP v5.5 has already stopped receiving all security updates.  If your server is running anything up to PHP 5.6 you are already putting your business at a security risk.  PHP 5.6 has already stopped receiving constant updates and now only receives major security updates.  According to the PHP lifecycle, it is imperative that the codebase be constantly updated to keep with the current PHP version. This is the only way to ensure Security. I don’t even have to mention the recent astronomical data breaches by name, and everyone knows from the news that these were because of security updates not being installed. Having your customer data on a server is a necessary risk that we as professionals must not take lightly. We must guard that data in every way we can.  The first line of defense is in making sure there are no underlying vulnerabilities in our servers.

It has been my experience that this process is relatively easy if you have access to PHPStorm by JetBrains.  Their current marketing says the IDE is “Lightning smart” and I can’t agree more. There are many outdated sites out there that tell how to use PHPStorm to perform this action. I struggled to find the right way to do it, but have since figured it out, and it is still easy (albeit a little hidden).

  1. Set the project PHP version to your desired PHP version. (Preferences -> Languages and Frameworks -> PHP)
  2. Run the “Language Level” inspection (Code -> Run Inspection by Name -> “Language Level”)

Those two steps will analyze the entire project for structures that need to be considered in order to upgrade the underlying PHP version.

If you don’t have access to this great software, there are a few free suites out there that will help get your code running on PHP 7.

I have heard great things about all three of these tools, but have personally only used PHPStorm for upgrading from PHP5 to PHP7.

A Hybrid System

If the old, legacy code is not MVC, then the ultimate rewrite would mean moving everything into MVC! Many developers think this has to be an all-or-nothing kind of deal.  They might believe that since the old system is not MVC, that the only way forward is to either stop production for a full rewrite in MVC, or to continue doing things the old way. However, this is simply not the case.

Even 15-year old software probably has some basic Objects and Classes strewn throughout it.  And if not, it probably has a lot of helper functions that are in some include files. The goal here needs to be to do the absolute minimum to get a MVC architecture running.  Which packages you use for this depends completely on the internal team and the business – whatever makes sense for your team going forward.  It could be a slew of Symphony components, an off-the-shelf framework, or a completely custom-designed MVC. The difficulty with using something off the shelf is that in a hybrid system, the old and the new both need to be able to live harmoniously together and the user should be able to move back and forth between endpoints in the old and new system without noticing. For this reason, it might be slightly less work to roll your own MVC with Symphony components just because each aspect of the MVC will need to be customized or extended in some (hopefully) small ways to make it integrate.

In my day job we have a hybrid system that was customized over many years.  The old legacy stuff still functions well enough, but all new dev is done in the MVC.  We have several helper libraries that are basic include files we have ported over.  This means that there are generally two ways to get at things. For example, I can access the global db functions to write me own SQL query, or I can access Doctrine in the new areas.

Unfortunately there is not much more advice I can give on this, as the process will be very application-specific for every project.  Give it a little thought, come up with an end goal, and then devise a way to make the end goal work side by side with the old legacy code.

Practice Modern Coding

Now that there is a testing suite and an MVC set up in the project, it should be a hard rule that no new development is done in the old system.  Use TDD practices as you go, and always be certain to create things that are modern, decoupled works of art. It goes without saying that you will spend a lot of time doing things in both the old code as well as the new code, but always be sure that you leave the old code better off than it was previously.

At this point you can also start to lump things that can be rewritten into buckets for improvement.  For example you could set aside some dev time to start modernizing the shopping cart or the product display pages.  Not everything has to be done all at once (nor should it) and it can certainly be something that’s planned for and executed on a timeline.  As new features are requested, if they are part of one of the buckets and are low enough in priority, they can be set aside in the bucket for later. Once there are enough features to make the rest of the section rewrite worthwhile, then the rewrite can commence. It won’t just be a rewrite though – it will be adding new features.  This approach makes sense from several perspectives.  The overhead of a rewrite is tremendous and can get tedious – so the developer will enjoy it more if there are some new features strewn throughout it.  Also, from a business perspective, adding a 20% work increase to a group of projects looks more appealing to a manager than adding a 200% increase to a small project (and WAY better than spending x hours to have what looks to them like nothing).

Conclusion

Legacy code can really get a developer down, but by including some fun aspects through out the process of modernization it can be a rewarding experience. One of the worst tasks for me as a developer is boring days of refactoring existing code. I love a challenge – I love to think about new features and how to overcome barriers. Following the steps outlined here allows some of that new feature feeling to be present in the entire process. It staves off burnout and, in the end, keeps the project relevant! It leverages all the countless hours that went into creating the old system with the talent and modern technology available today.


Spread the news

Leave a Reply

Your email address will not be published. Required fields are marked *