Branch by Abstraction
In the past I have been a big fan of Git Flow. I still am, but I recently read the book Continuous Delivery and it has me rethinking some things. In particular they talk about Continuous Integration and not using feature branches, which is a very large component of Git Flow.
The issue isn’t so much feature branches in and of themselves, it’s the fact that they are long-lived. I remembered on the Maintainable podcast once hearing someone say “Any Long-lived branch is technical debt” (sorry can’t remember which episode). The idea being you have to merge back at some point. The longer you put it off, the more chance for conflicts so the harder it will be (equivalent to having to pay interest).
How to make changes without branching?
So the question is how do I make changes without branching? One potential answer is branch by abstraction. The idea is to be able to make commits to the mainline without stepping on other’s toes. As LabVIEW programmers, we are already kind of familiar with this. We know the key is not working on the same files, because that is what causes merge conflicts. So how can we do this in an organized way?
1. Create An Abstraction
Let’s say we have some example code that uses a specific Keithley DMM. Let’s say we’re lucky and it already uses a class for the Keithley DMM. We can create an abstraction (ie a Generic DMM parent class or Interface) in parallel to the existing code. We can check that in without stepping on anyone’s toes because we don’t need to touch the existing code at this point at all.
2. Introduce The Abstraction
Once we have the abstraction, we can refactor the existing code to call the abstraction. Here we do run the risk of stepping on people’s toes, especially if the Keithley DMM is widely used, but it shouldn’t require substantial changes. All we need to do is find every Keithley DMM object and replace it with a Generic DMM, making sure that somewhere at the beginning of the program our program is hardcoded to use a Keithley DMM. If we run into conflicts, resolving them should be easy. We can just quickly accept their changes and replace the object and we are good.
3. Create Concrete Implementation
Now we are free to create our new DMM implementation, in this case let’s say an NI DMM. We can create our NI DMM alongside the existing code without creating conflicts because we are not touching any existing code. We can also test it in isolation and make sure it all works.
4. Switch to the New Concrete Implementation
Switching to the new concrete implementation is easy. We should have one spot at the beginning of our code where we decide what concrete implementation to use. We can either hardcode it or use a factory or plugin pattern.
The end result of using this pattern is that we can easily add features to existing code with minimal risk of stepping on other’s toes without creating a branch in our source code control. The only time we could possibly end up with merge conflicts is in steps 2 and 4. Step 4 is a very small change in one place. Step 2 may affect a lot of VIs but it is only making a small change to each one. Resolving conflicts in either of those cases should be trivial. This lets us check into the mainline without fear so that we can have all our code continuously integrated. This helps us to reduce the amount of time we would normally spend integrating our changes if we wait until the end when the feature is done.
If you need to implement this idea or any other idea from our blog, please reach out. We would love to talk to you about how we can help.