Working with feature branching reminds me of building a house. You don’t see general contractors insisting on making copies of their own houses when remodeling. Working in the house at the same time forces them to talk to each other and come to an agreement over a conflict. Imagine that a contractor finishes a room, tries to leave, and finds the door suddenly blocked by another wall. The same thing could happen in the code after you’ve been too focused on your feature changes. That’s why communication needs to be enforced—and with feature branches, that’s a thing that can easily get overlooked.
There are some situations in which working exclusively with feature branches might make sense. But generally speaking, if this can be avoided, a team’s productivity and confidence in the code will increase by a factor that you never imagined. For that reason, let’s talk about some of the pitfalls of working with feature branches in your development workflow.
You’ll Waste Time Fixing Unnecessary Merge Conflicts
Merge conflicts are the biggest pitfall of using feature branches. Nothing hurts more than spending unnecessary time fixing merge conflicts, especially when a feature branch has been there for a while. But time is not the only factor. The risk of removing any existing code or introducing new issues increases considerably. In some occasions, you’d also need to freeze any changes in code while you stabilize everything. Everyone on the team that was involved must make sure any code is removed or overridden.
I felt this pain when working with a remote team. They were always removing my recent changes. Every time they changed something, I had to stop what I was doing and review their work to see if any of my code was altered—or even removed all together. I don’t blame them. We only had one code repository for a big monolithic app. Instead, I blame merge conflicts.
When there are too many people working in the same source code repository, it could be a smell that you need to decouple your code. Divide your big application into smaller parts to avoid the suffering caused by merge conflicts. Some might call this going from monolith to microservices, although this doesn’t mean you’ll create a git repository for just two or three files. You can pack commonly used code.
You’ll Have No Consistency When Generating Artifacts
When you generate the artifacts that will be deployed in your environments, it’s recommended that you generate them only once. Building and packing should happen in the first stage of your workflow or delivery pipeline. That first stage is usually dev. You don’t want to get any surprises when releasing a change in a production environment. For that reason, it’s better if you deploy the same way everywhere, including production. A change in configuration is all that you’d expect.
Branching might sound good. Working this way, you can isolate changes that are ready to go from those that aren’t ready yet. But let’s think about a common scenario:
You have your “dev,” “master,” and “release-X” branches, and everyone is initially pushing their changes to the “dev” one. Everything that gets there is released in dev and any other test environment. When a change is ready to be released, you create a “release-bugfix” branch and generate the artifacts. After they’ve been released, you integrate changes to “master” because that’s where all stable code exists.
The problem is that everyone needs to update their branches from “master” and resolve any conflicts. A long time could have passed since their last update. But it’s not just that. They’d need to generate artifacts again, and they’d also need to repass the set of tests. It increases delivery time and risk, and you’ll lose consistency. The release process will begin to inspire a lack of confidence.
You Won’t Be Ready To Ship
Which feature branch should be released? Is there more than one? Which one should go first? These are the types of questions that you’ll face when working with feature branches. If you don’t know the answer, you’ll need to take some time to investigate. And it’s not only that. You’ll also need to coordinate with the teams that were working on certain branches to make sure no one deleted another’s code.
What about bug fixes? What if you need to push a change but you’re in the middle of a merge? Or if you have a branch in the middle of the pipeline that’s not ready to be released? Things are about to get complicated.
To make sure you don’t release anything unexpected, you need to roll back beforehand in order to have a clear path. Only then are you good to go by creating another branch for the fix.
It might sound too complicated, but I’ve seen this scenario many times. There are also some branching models like GitFlow that help you with this. But sadly, complexity doesn’t go away, and the chance of ruining things is still there. GitFlow can be harmful to others who work on the code. And the fact that the branching model needs to be this complicated is a sign that something needs to change in your workflow.
Release cadence is reduced by these types of problems. That’s something you need to take care of—the sooner the better. You shouldn’t be afraid of releasing new things.
Feedback and Collaboration Will Be Complicated
When you work with feature branches, the team won’t know what changed until the branch is merged. Feedback doesn’t come quickly. Collaboration is sacrificed because the group focuses only on the piece of code they changed in the feature branch. To avoid any delay, some might even decide not to update their branch until they’ve finished coding. Taking time to decide what code should get mixed in or deleted will also cause delays. All of this adds up to procrastination; people know that fixing a merge conflict will impact their delivery time, so they decide to leave that till the end.
Another downside here is that, at some point in time, there might be a common problem in two feature branches. Each team will solve it in a different way. And because of this, the code might start getting duplicated. More than one solution to the same problem might start to exist. Which one will you fix when a problem arises? Are you aware of all of them? Or just certain people’s? You’ll never know exactly.
That’s why pushing your changes to “master” will increase feedback and collaboration in the team. The feedback you receive is immediate. The team is forced to pull the latest changes more frequently. If there’s a conflict, they can easily review what just changed instead of needing to review a big list of changes when the branch is merged.
It becomes problematic when a long list of files need to be reviewed carefully in each merge request. It’s way better when a developer pushes the latest changes to the main branch and makes sure it didn’t affect anyone else’s work. But that needs to happen as soon as possible.
Knowing What Changed Will Be Difficult
Many things can happen in a feature branch. If more than one person was working on it, the most likely scenario is that there’s a branch from the feature branch for each one of those people. Most likely, many commit messages will be found when merging the feature branch. You could squash all your commits to make them appear as one, but if the code needs to be reviewed, the folks doing it will need to read an awful lot. And they’ll have questions…too many questions.
Even though there’s always a way that you can go back in history and see what changes were made, at the moment of deciding which code should be chosen when fixing a merge conflict, it’s better if you have a quick way to understand why that code was added, removed, or changed. Knowing the context will be key.
In Short, Avoid Using Long Feature Branches!
At this point, you might think, “OK. I understand that there are some pitfalls…but what do you propose then?” Well, I propose to work with feature flags and short-lived feature branches and to practice continuous integration. The most common reason people have for choosing to work with feature branches is that they want to minimize the risk of releasing incomplete or buggy code. But you shouldn’t be afraid of doing that. As long as a feature is turned off, you should be good to go. You can always toggle it when needed.
The pitfalls I described here are real, but this doesn’t mean you should go to the extreme of not using feature branches at all. They’re really helpful. But the key here is how much time they will live. The same thing happens when you don’t release frequently. Changes accumulate, and the risk is bigger and bigger the more time you wait. If you make the switch to working in small batches, you might keep using feature branches, but they will live a day at most. The difference is that feedback is amplified, and you can work on fixing things rapidly because there’s not too much to review. That’s the smart way to use feature branches!
Christian Meléndez is a technologist that started as a software developer and has more recently become a cloud architect focused on implementing continuous delivery pipelines with applications in several flavors, including .NET, Node.js, and Java, often using Docker containers.