Well I can imagine hypothetical scenarios where I might just throw out the existing code base (hypothetical situations where the bucky balls have zero weight and volume, where no one cares if we lose weeks or months of productivity as we scramble to add overlooked features and bug fixes into the system, and where the system is so trivial and hated by an organization that I can replace the entire thing and army of servers with notepad++ and a netbook in ten minutes and boost everyone’s productivity and moral across the board.)
For almost any real world scenario though I would just recommend refactoring in place. ^_^. There tend to be too many hidden, unanticipated costs to rewrites when undocumented legal and business requirements start to pop-up and the last minute hacking things together last minute starts to ensue.
==Refactoring in Place for greater good, and stuff==
Refactoring legacy code with the regular old incremental approach,
If you have the chance convert to UML and document what little knarly architecture there is.
- If you are on version control look into generating some code churn reports and figuring out which files and file sections have been changed the most frequently. Make a note of these as they will likely be the areas you will want to deal with first.
Before you change any code always try to add some test coverage (even ugly full stack functional tests will do).
If dealing with large messy procedurial code extract logic you intend on changing into some reasonably named method or function and if possible add some test cases that verify your new method. make whatever god awful hack you have to and if possible paramatize the change if it is something commonly tweaked like margin width or title so it will be a little more straight forward to update the next time.
- Use the adapter pattern and work away at hiding the knarly bits of legacy code under a rug of logically named classes and methods so that for most common tasks you and the other developers perform you wont need to worry about the scary bits that are going on behind your back behind those nice little clean methods and classes you've hidden that legacy code behind -- like those nice families that keep deformed murderous zombie former family members in barns so that they don't spoil the day to day going-on’s of the farm . . . normally.
As you continue to whittle away and clean up the sections of the code continue to boost your test coverage. Now you can dig down even deeper and "rewrite" even more code when needed/desired with ever increasing confidence.
- Repeat, or apply additional refactoring approaches to continue to improve your codebase.
Branching By Abstraction
- Define the trouble spot in the code you want to remove (the persistence layer, the pdf generator, the invoice tally mechanism, the widget generator, etc.).
- run (write if necessary) some functional test cases (automated or manual but you know automated) against the code base that targets this functionality along with general behavior.
- Extract logic related to that component from the existing sourcebase into a class with some reasonable interface.
- Verify all code is now using the new interface to perform activity X which was previously dispersed randomly throughout the code(Grep the code base, add a trace to the new class and verify pages that should be calling it are, etc.), and that you can control which implementation will be used by modifying a single file.(object registry, factory class, whatever IActivityXClass = Settings.AcitivtyXImplementer();)
- rerun functional test cases that verify everything is still functioning with all of Activity X throwin into your new class.
- Write unit tests where possible around the new activity X wrapper class.
- implement a new class with less insane spaghetti logic than the legacy implementation that adheres to the same interface as the legacy class.
- verify that the new class passes the unit tests you wrote for the legacy class.
- update your code by changing the registry/factorymethod/whatever to use the new class instead of the old class.
- verify that your functional tests still pass.
Open Closed Principle and a Common Business Logic/Persistence Layer
To some extent you might be able to get away with separating out your presentation, business and persistence layer and writing a new app that is fully backwards compatible with the legacy solution, or at a minimum can still handle data inputted by legacy solution. I would probably not recommend this approach but sometimes it is a reasonable compromise of time/schedule/resources/required new functionality.
- At a minimum separate the presentation layer away from the business and persistence layers.
- implement a new ui and better presentation layer that uses the same common business and persistence layer.
- Verify that data created with new ui does not break old ui. (you're going to be in hot water if users of the new tool interrupt users of the old tool).
If you are striving for full backwards compatibility you should be saving everything to the same persistence layers. If you just want forward compatibility into the new tool then use a new database and new tables or extension tables to track data not in legacy system.
- For new functionality and persistence layer needs add new tables and methods don't change existing legacy logic, or add columns, constraints to existing tables.
e.g. if you need to start tracking employers emergency contact and some other fields don't modify the existing employee table (we have no idea what assumptions the legacy data makes about that table) add an extension table employee_ext id, employee_id, emergency_contact_id, etc_id.
Slowly migrate users onto new system. if possible put legacy system into read only mode, or just add some warning telling users it will no longer be available after date X.
implement any hi priority missed functionality or business requirements in the new ui
- roll over users base.
- continue on to clean-up the business logic and persistence layer users other refactoring methodologies.