5 ms·
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
by SeanKilleen 9y 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!
- Too 9y agoSince 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.
- maxxxxx 9y agoAgreed. 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.
- mdaniel 9y agoFor 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?
- maxxxxx 9y agoI think your code will work because 1234 can be converted to a string. I think the trouble starts once you pass around objects.
- w0rd-driven 9y agoIt actually doesn't according to http://sandbox.onlinephpfunctions.com/code/8804b49c23d62022854e928ef433c0ab9a5cbc5f http://sandbox.onlinephpfunctions.com/code/8804b49c23d620228..., though different PHP versions could produce different results.
- maxxxxx 9y agoThis works with PHP 7 <?php //Enter your code here, enjoy! function foo(string $bar) { return $bar . $bar; } echo foo(1234);
- w0rd-driven 9y agoYeah 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.