How to Refactor like a Something star
- Plan your refactoring Planning should never be considered overrated. Before I start a refactor, I like to assess everything that the existing code does, and map where I want that to sit. I also determine which angle is the best to start from after looking at all the possible combinations, so that I am always working with minimal changes. I like to use post-its to guide my work, so I usually start with a handful of post-its to show where I am going. If there are too many post-its, I also start questioning the scope of the refactor: what could I get done if I only had 2 days to do it in.
- Parallelize your work This really works well when, instead of shuffling bits around, you are actually doing a rewrite of the current functionality. For instance, the other day I rewrote the legacy login code to the application. Obviously I couldn’t be too ruthless as everyone needed to still login, and this code was not under test. So, I wrote the new login section alongside the existing code, and just swapping in my login code in the DI container on my local machine. When it was time to switch it over, we checked in the DI container change, and voila, everyone started using the new login code. This allowed us to check in frequently without breaking anyone regardless of it being incomplete. It also meant that we always had the original version to refer to as we proceeded
- Checkin often This is helped by either doing minimal changes at a time, or by parallelizing the work. Checkin frequently, checkin small. That way if you ever need to revert your changes, you know the brilliant work that you did before the “screwup” won’t be wasted. If you are working with a source control like mercurial or git, you are in an even better situation, as you still have source control without affecting the rest of your team with your breaking changes.
- Never be afraid to revert your changes This one is such a valuable lesson to learn and it feels so wrong when your doing faced with the possibility but when you have painted yourself into a corner, the best thing to do is to revert all changes. What it does mean, however, is that all the lessons that you learned from your first attempt will help guide your decisions the next time round.
- Prepare your system before you add new concepts I know that refactoring is all about improving your world without adding new functionality but sometimes people start adding new features before the world is setup for them. Ensuring that your world is good before you add new features will make your life simpler.
- Introduce new concepts top down Obvious? I thought so, but I have seen a refactoring go horribly wrong because it was started from the wrong place. When you change a class, you can isolate the changes within it, but every consumer of it will be affected. This bubbling effect can be catastrophic if you start from objects in the lowest level.
- Only have top level tests This one is slightly controversial, and I don’t know if I completely agree with it anyway, but if you only test your entry-point object, and never use fake objects during your testing (except to simulate external dependancies like databases or external services) then you will not have the annoyance of changing loads of unnecessary tests. You will also find that you can be 100% certain that your tests are still relevant without touching every single test file.
- Start with tests which pass Very important to ensure that you don’t break anything, so start with passing tests on the highest layer, whether that means browser based tests or controller tests. Make sure you have a test for all scenarios as that way you can easily test that your refactor has not changed the world.
- Making sure your world better than you found it (or at least no worse) reduces the number of big risky refactor adventures
Posted: September 27th, 2009 under Design.
Comments
Comment from Mark Needham
Time September 28, 2009 at 2:18 pm
Nice post – I particularly like the bits about doing refactoring in parallel to existing code – I’ve found that works out much better than breaking chunks of code while you try to change it.
Sometimes it’s also interesting just to take a copy of the code base on another checkout and just play around with refactorings while not caring whether or not you break anything.
That can be quite interesting for identifying abstractions that weren’t obvious previously or showing other ways the code can be improved.
Comment from felix
Time September 28, 2009 at 9:47 pm
Interesting one. Even though I don’t quite understand, what you mean by top-down introduction of new concepts. I usually find that if you don’t go for the rewrite (possibly because there is no nice interface in your existing code base), it’s far easier to start extracting small concepts until the bigger ones emerge. But then again I think the rewrite (of a well defined subsystem) has had way too much bad press in agile circles.
Comment from Jen Smith
Time September 30, 2009 at 7:34 am
I can see the argument for dumping all low level unit tests when refactoring a subsystem (like your login example) – if the code is in that much need of a rewrite then the current unit tests aren’t going to help.
But in the case of smaller changes to fairly well-test-covered code, you can often leave the unit tests in place. Even if you need to put some ‘Assert.Fail()’ lines in a few tests here and there, the behaviour is still documented and you can revisit it later.
Parallelizing can work very well at the method level too. I have often started a refactoring by adding in the new method to be used and gradually moving callers over to it. That way you don’t break existing tests/behaviour until you actually need to and can perform regular checkins. Gives you a good scoping tool as well: i.e. ‘We will get rid of all calls to this guy then stop and do something useful’.
Comment from Siim
Time September 30, 2009 at 10:32 am
to felix:
I think by top-down she meant that when refactoring class’s public interface, for example, you should first look its usages and start from there if necessary. And then proceed with class refactoring. When doing opposite, you could end up with big bubbling effect like Sarah described.
Comment from Ben Butler-Cole
Time September 30, 2009 at 10:34 pm
I would add: find a safe path. The ideal refactoring consists of a series of small steps that are known to be safe (that is, they don’t change the behaviour of the code). Sometimes you have an improved design in mind but it isn’t obvious how you get *there* from *here* (which reminds me of a joke about asking for directions). In that case it pays to try various routes to your destination (checking in frequently, being happy to revert) and persist in looking for a safe route.
As soon as you step off the path and make (even, what you think to be, temporarily) breaking changes then all is lost.
The great side-effect of this is that you can check in and walk away at almost any time, knowing that you have at least started things moving in the right direction, even if you haven’t finished what you set out to do.
Ben
Comment from Rob
Time September 28, 2009 at 11:23 am
Nice post Sarah!
I have to admit sometimes I am too quick to code when refactoring – and it has cost me. Recently I have been adding a process to my dev work (incl. refactoring) – I called it COMIK (http://bit.ly/4eMOm2) but the main emphasis is on good planning.
As you put it “painting yourself into a corner” can be both demoralising and frustrating (time wasting aside!).