Git Workflow: The Case of the Lost Refactor

This has happened to me more than once, so I've been thinking about how to organise my branching and merging to allow for this scenario.

  1. I get my ticket to do some new feature on the application I'm working on.
  2. I create a feature branch, usually in the format of feature/(ticketNumber)-(title) (Yeah, we follow A successful Git branching model)
  3. I think to myself "I've got some time to get this done, I'll take the opportunity to refactor this code so it's nicer, in the process of getting this new feature done" (Insert "Upgrade library/framework" here as well).
  4. So I start refactoring, and continue on working on my new feature…
  5. Until I get a: "Hey Mark, we have changing priorities/emergency/we have a better idea/never-mind, so backburner/kill/delay/switch to working on this totally new thing over here".
  6. So I go off and start a whole new feature, and get that done instead.
  7. Later on, I come to do something else in the area I was working in previously, and realise the refactoring work I was doing previously would be super useful. However all of it is tied in with other half finished code in my previous feature branch. That sucks, because it would make what I'm working on a million times easier.

I call it the "Lost Refactor", and it drives me nuts, as you know you could be working on better code, but it's all attached to other code that you would have to manually pull apart.

So what do you do? I have two thoughts on the matter:

  1. Separate the refactoring into a separate ticket, and build a separate feature branch for it.
  2. Create a refactor/(ticketNumber)-(title) branch, specifically for the refactoring work, and merge that into my feature branch.
  3. Make sure my commits are clean enough so that I can cherry pick out which ones are pure refactor, and which ones are the partial implementations of the new feature.

I've yet to try out option #2, but it seems to me that that would be the best approach.

Technically you could claim that the refactoring would be a totally separate ticket – but that seems like more trouble than it's worth from a pragmatic standpoint. However, the refactoring is a separate effort than the ticket, which means it's nice to split off to it's own branch, in case you want to integrate that work separately. Having the refactor/ branch be the same as the feature/ branch name, it also gives you a really good idea of what the original impetus for the refactoring came from.

Option #3 is not a bad one – and probably the easiest from the development workflow (not having to switch branches around), but the idea of cherry picking commits makes me shudder a bit. I guess it depends how often you end up in a "lost refactor" type scenario, that if it was rare enough, doing the occasional cherry pick is not the end of the world.

What does everyone else do? Do you find yourself in this situation often? What approach do you take?

Leave a Comment

Comments

  • Barney Boisvert | June 6, 2012

    For places where refactoring is "part of" another task, I do the refactoring first, and then implement the real stuff. That way I can do all my refactoring with proven unit tests and no other change in the mix and then separately introduce the new requirements/changes. So I guess that makes it option #3, but cherry picking is easy because it’s always "from branch until revision X" in a linear fashion with no skips and jumps. And in reality, I’m a big fan of "merge early, merge often", so I’ll usually merge serious refactors into the parent line before working on features built upon them (sometimes even killing the branch and taking a new one after the merge). And that’d make it option #1 in terms of branching strategy, but not ticketing strategy.

    Option #4 could be the reverse of #2, where you create a refactor branch and then branch for your feature from that, so you merge your feature into the refactor branch. Obviously that means you have to know you’re going to use a refactor branch before you start working on the feature, but I don’t think that’s unreasonable.

  • Adam Tuttle | June 6, 2012

    I don’t think it really matters whether you use #2 or Barney’s #4, but my rule of thumb is that the refactor, while inspired by the ticket, is a separate effort, so it has to live in its own branch.

    I would branch both the feature and the refactor from the development branch instead of one from the other; and then merge the refactor into my feature branch (possibly many times) if I’m planning to roll them out together. This would give me a good opportunity to test them together, and if there are any bugs with the refactored code, adjust them in the refactor branch, re-merge, re-test, etc. That way, at any time, you can easily get just the refactored code back into development without anything from the feature branch "stowing away".

  • Steve Bryant | June 7, 2012

    I am mostly commenting in hopes of getting notices of future tidbits of wisdom, but I will add that historically I have done the refactoring in the same branch as the new feature and I started running into just the problems Mark described.

    I am looking forward to trying out some of the other options discussed here.

  • Mark | August 15, 2012

    So I just went through this again. Pragmatically decided – doing a whole new branch was just going to take too long. So I made sure it was a nice clean commit that also links to my private Github issue for that specific refactor (yes, we have tickets for refactors). That way, at any later date, I can click the ticket in github and cherry pick that specific commit into other code (if I have to).

    We’ll see how this goes!