We all wish the code need to work on is well-maintained, easy to enhance and easy to test. However if you have work with legacy code it’s different ball game. Imagine a real life situation where you add features to existing spaghetti code and two weeks later you need to rollback (which tells why you have spaghetti code in the first place).
Most people write a test case for the required feature and make it fail, add new feature and make the test pass and refactor the code to remove duplicate code and make sure the test pass and all of the other tests pass. This is a typical approach and usually works most of the time as long as you are not required to rollback the features you added. As soon as you want to rollback the features, you faced with dilemma that whether you want to rollback everything including the refactored code or just the features you added. However if you rollback everything, you end-up loosing all your refactored code. If you want to remove only features you need to comb the code to make sure you didn’t leave any residue of the new features.
Here is a different approach, whenever you encounter unmanageable code, write a test case (if there is none) first to make sure it checks the existing functionality. Refactor the code to the point so that you can understand the code as well all the tests pass. Commit the code so that you don’t loose all your refactored code. Now add a test case to check the new features you are going to develop, make it fail and change the source code, make test case pass, refactor it, make sure all the tests pass. If you ever need to rollback the feature, you have a base point to rollback. In this approach, it’s easy to rollback and still keep all the refactored code so that if you visit next time, you don’t have spend another session of cursing the spaghetti code.