Hacker News new | ask | show | jobs
by SeanKilleen 3092 days ago
If pieces of a codebase need to be changed, I break it into a few general steps (varies depending on specifics):

1) Look at the code and examine what it would take to make it testable.

2) make tiny, safe refactorings to prepare the code to be tested -- only if you are absolutely certain these changes can cause no side-effects (usually I'll rely on tools to help me do this, just to increase the confidence level). If you can safely extract related code into appropriately small & related files / objects, that can be a great start.

3) Put the existing code under tests. Write tests around that code -- preferably unit tests that exercise the legacy code, as written, to verify its behavior.

4) refactor the code that the tests are using to represent a cleaner codebase -- maybe you extract some objects / functions.

4) Write new tests to demonstrate the desired changed behavior. Write them as if you're writing them for a brand new codebase.

5) Make the code pass those tests. Some old tests may fail -- if they represent the old requirement, you can delete them. If they don't represent the old requirement, you know you have a bug.

Repeat this process for each piece of the application that needs to be changed. NOTE: In a legacy app, you sometimes have to make peace with the fact that some of the app in production will remain legacy, untested code. If it doesn't need to change, then you don't necessarily need to sink a huge amount of time trying to refactor / put all the code under test. Get in the habit of doing it whenever you need to make a change (and factoring in that time into any estimates, etc.) -- over time, the cost of a change will hopefully go down as you pay off that technical debt.

If you're trying to figure out what the cost of change will be, sometimes you can use static analysis to look at a codebase and show potential issues for a given section. Using such tools can sometimes help you understand how heavy of a lift it will be to modernize the code.

As has been suggested, "Working effectively with legacy code" is a guide book here. You'll likely also want to seek out language-specific material about refactoring, unit/integration testing patterns & tooling, etc.

Good luck!

1 comments

Since you are working with dynamic language, step 2 should include adding type annotations or whatever they are called in php. Specifying stricter types makes refactoring much easier.
Agreed. Adding type annotations is already a huge refactor in itself though and pretty risky with PHP. Once you have that done life will be much easier.
For those unfamiliar with php, or its type annotations, why is adding type annotations a risky step? Do they become enforcing?

    function foo(string $bar) { ... }
    foo(1234) // kaboom?
Yeah pretty much. It's risky because type checking produces fatal errors that aren't always easy to trap properly. You don't realize how much a code base relies on PHPs type coercion until you add annotations and watch it blow up, in often subtle ways.
"Subtle" is the keyword here.
I think your code will work because 1234 can be converted to a string. I think the trouble starts once you pass around objects.
It actually doesn't according to http://sandbox.onlinephpfunctions.com/code/8804b49c23d620228..., though different PHP versions could produce different results.
This works with PHP 7

<?php //Enter your code here, enjoy!

function foo(string $bar) { return $bar . $bar; }

echo foo(1234);

Wait? You mean you get runtime errors? That's well...good in a way but as you say dangerous on old code.

To get the true benefits of typing you should also run a static type analyzer.

Are there any for PHP?