Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this questionCurrently performing a fairly major refactoring of an application due to a very late major requirement change.
I always feel when I'm doing this sort of thing that I'm not approaching it in a very disciplined manner. My application is likely to go days/weeks at a time in a state where it either doesn't build or requires commenting out huge swathes of code just to get it to compile.
I don't like checking into source control in this state so I often feel I have no fall back if I make a big mistake. I am literally right now destroying my application, and I'm holding 100 different threads in my head. Like one of those people you read about who takes their car engine apart out of curiosity and then realises开发者_JS百科 they have no idea how to put it all back together again.
Are there any good resources out there which discuss how to approach refactoring in a more incremental and less destructive manner? Can anyone offer any advice? Or does everyone feel like this?
What you describe is in fact not refactoring.
Refactoring is a disciplined effort to improve the design of the code without changing its functionality, done in small - even simplistic - steps, safeguarded by unit tests, which ensure that the system is functional after each step. Moreover, it is typically done in small increments over a longer period of time, not in one big whoosh.
This is not to be overly zealous about anything, just to clarify the terms :-) There is less chance of misunderstanding and communication problems when we understand the same words the same way.
Of course, if you have the time to do a lot of refactoring at once, all the better! But before you embark on such an effort, you absolutely need to build a good set of unit tests which cover - ideally - all the functionality in the code parts you are about to change.
Since you are talking about a "major requirement change", it is not clear whether what you call "refactoring" is actually implementing new functionality or only improving the design to prepare for the introduction of new functionality. I strongly recommend to keep the two phases separate: refactor first without changing the existing functionality, to make your design more open to extensions at the right places, which then allow you to incorporate the desired functional changes more easily.
The Refactoring book linked by @Eric is fundamental; I would add Refactoring to Patterns by Josh Kerievsky, which is about the practical application of refactorings.
You need a good unittest suite to ensure you don't break what's already working. There's also a good book from Martin Fowler on this subject: Refactoring: Improving the Design of Existing Code
I would also recommend abstracting the part of the code you intend to refactor and still provide the old implementation for everyone else while you can access the new code you're writing so you can still benefit from source control.
Using a distributed source control system would also let you commit without disrupting others work.
Use a version control system that allows for local branching, git for example. That way you can keep checking in at minor milestones as you're working through the refactoring process.
I am working through a similar problem and here is what I do...
- No matter how hard it is, only refactor in "small" chunks even if it means you don't have the final refactoring for that block of code yet
- Build/Test each step in the refactor process
- Focus on data structure refactoring first
- Focus on "flow" and "logic" next
- Focus on speed last
The key is to keep each refactoring step as small as possible and test/commit often.
At the point where you are trying to achieve more than one thing with your uncommitted changes, you have officially veered from "refactoring" to "hacking".
Each change you make should have a purpose and be testable, in and of itself. Of course, with many systems this is a challenge, but somehow you need to verify that given a set of inputs, the outputs don't change with your refactored code.
I know this is language-agnostic but from a .net perspective I employ a good set of unit tests and use Resharper to help with the process. This tool is invaluable in my refactoring endeavours.
As you are finding out -- an ineffective or non-existent "design" means that subsequent changes are indeed destructive.
Upfront, when choosing a design, you have to try to predict and account for the likely changes you will be facing. Some designs accept changes better -- for example if you are designing to accommodate rapidly fluctuating requirements, then use an autogenerated datalayer, keep validations in a single place and keep the GUI easy to modify -- a kind of normalization in the app. If you are looking for speed scalability, etc, then you need to denormalize the code and place validations in multiple places, write heavy layers, etc.
And so, if you later find yourself trashing your app to make needed fixes, accept that the design didn't work out and learn from it.
When you are faced with an ineffective design AND this will cost you a lot of time to make predicted changes, then the case can be made to refactor the design, as described in previous replies. This can be done while making other changes, although ideally you would say to your boss "Hey, I am just going to rewrite this code for a few weeks and, no, I am not addressing those features you really wanted and oh-yeah I am going to write a bunch of test cases too, but please don't fire the QA guy because its not that kind of testing". And he would agree!
精彩评论