Technical debt and how to pay it back
There is a common saying that the code you write today will be tomorrow's legacy code. Soon it may start to rot and you are incurring technical debt. That is based on the assumption that code you don't work with frequently, that you don't enhance, modify, put into a new form to do new things will become an ancient artifact which only the original builders can understand.
If after some years new people will have to modify such a code base, you find yourself between a rock and a hard place. On one hand you don't want to break existing functionality and on the other hand to satisfy the request of the customer you may need to modify the entire system!
The approach that appears to be the safest is to not touch anything but create a new system from scratch that will then replace the old system. This way you don't risk breaking anything and you can satisfy the request of the customer. But unless the system you want to replace is trivial you will soon find out that creating a replacement from scratch is not really a fast way and so you will face a different issue, eg. how to create a system that has all the features of the one it should replace plus all the new feature requests while not exceeding a probably tight time constraint.
To start to fix: model
To make modifications to a system you have little knowledge about one good approach is to create a model of it first. Acceptance-Test Driven Development (ATDD) and Behavior-Driven Development (BDD) have provided us with some tools to do so. A particular popular tool is Cucumber (http://cukes.info). It allows us to describe functionality in scenarios using statements like:
Given [context]
And [some more context]
When [event]
Then [outcome]
And [another outcome]
Those natural language statements can be made executable and then will operate the software system like a little robot. By making those statements executable you verify that the software under test indeed behaves as you have described it and thus you verify your assumptions about the software you are about to modify.
Now you have created yourself a safety net and can begin to modify the old code. It is likely that you will soon find that one of your Given/When/Then Scenarios reports a problem and so you know you have just broken something and can safely step back and use a different approach.
For programmers the use of natural language to describe features of a software system might seem cumbersome. They usually prefer non-ambiguous statements (code) in a programming language. But non-programmers are not used to code and so code does not make a good communication tool. Therefore I suggest using natural language to verify features with the users of the system. That way you can obtain confirmation of the correctness of your model.
You can now start to carefully make modifications to the existing code. I suggest that you write unit tests for every piece of code that you want to modify. If there are very long methods that do multiple things, isolate the interesting parts into a new method, write some tests for that new method and then call it from within the block of code where its function was performed before. Step by step you create a tight safety net. It is important not to go too far, as you might lose control over what you do.
The process described allows you to iteratively shape something new without upsetting users and customers. It might take a while, but no longer than modifying the unknown and having to fix all of the unexpected side-effects.
Photo by Tim Lucas via Flickr, CC Licence Info