Long living feature branches - An Anti Pattern

leena

March 14, 2017

A few years back, we were working on a Javascript heavy web application which had built using backbone.js. We found that we were loading all the Javascripts at once than applying lazy loading. This affected the user experience because of the high page load time and wanted to fix that.

We decide to use Require.js’s AMD [Asynchronous Module Definition] to implement lazy loading. We had to change our own Javascript files and also needed to apply certain techniques to the external JS [like JQuery] to support AMD. We started on that and realized that we are getting into a black hole as we had to literally change the entire JS code. We didn’t react to our learning and it took around two weeks to completely support AMD.

We saw changes in the application performance, but the as a team we felt bad because We couldn’t work on anything else during this time because:

We thought we were doing continuous delivery i.e. every commit can go to production, but it was broken this time. We needed to find out how to handle long running rewriting in a "continuous delivery" manner.

Learning from experience

We came across a similar situation in the recent past. This was for an application which allowed the user to create custom designs, which they can print in their T-Shirts or hoodies. We were using RGhost for creating the designs in PDF but later realised that because of certain constraints with RGhost it’s better to use LaTex.

We didn’t want to repeat the earlier mistake, we want to do the rewrite in small batches. We had an abstraction layer for the PDF Generation somewhat like as follows:

class PDFGenerator

  def generate_pdf_from_text(text,font_attributes)
    #Call the RGhost’s implementation
  end

  def generate_pdf_from_image(image_path,image_attributes)
    #Call the LaTex’s implementation
  end

  #other methods

end

And we change one function by function to the new implementation. We added feature toggle too so that we can switch to old implementation in case we find a problem in production.

  def generate_pdf_from_image(image_path,image_attributes)

    if USE_LATEX
      #new implementation
    else
     #old implementation
    end
  end

The approach was really great, because we could fine tune the new implementation with the learnings from production and as it was in small batches and it was completely fine to complete the port in few weeks time as there was no dependency on this.

Branch by Abstraction

The above technique is similar to the technique called Branch by Abstraction, i.e. introduce an abstraction layer and route it to the existing implementation.

image alt text

Let the abstraction layer redirect to newer implementation as an when it is ready.

image alt text

You can visualise Branch by Abstraction with the following example given by Paul Hammant i.e consider it as changing the wheels of a car with the conditions:

image alt text

It works in large scale too. This case study talks about two major changes implemented using Branch by Abstraction i.e.:

Every commit should be ready for deployment

Mainline Development and Feature toggles help you to achieve this mantra to a great extend, but the question is how do you handle major refactorings or rewritings? Use Branch by Abstraction.

Yes, it requires the discipline to use abstractions appropriately. But why not, if the same helps for better maintenance.