The risks and team dynamics of code merges
Code merges are an unavoidable step in the development process despite the fact that they carry significant risk. But while code merges themselves might be unavoidable, it is possible to manage the risk they present.
The best way to manage the risk of code merges is to fully understand where this risk comes from and identify practices to help alleviate it. Let’s take a look at exactly what that entails, including how practicing Continuous Integration and Continuous Delivery can help.
Code merging basics: what happens and why it’s necessary
Today’s development process typically works something like GitHub Flow:
- Every developer has a working branch for each development effort. i.e. “my-feature-branch”. Developers make changes in files and then check a version of those files into source control on their working branch.
- Assuming no issues come back, the files get merged into the integration branch “main”
- Once those changes are merged into the integration branch, all other developers must then merge those upstream changes into their existing working branches
One of the most common issues that can arise in this process is two or more developers trying to modify the same parts of a file in their working branches. When this happens, they will get a code conflict when they try to merge those changes into the integration branch since source control will not allow either set of changes to automatically merge upstream. In this case, the developers attempting to submit code to the integration branch will be required to manually merge the lines of code together. This happens because the system doesn’t know how to merge the conflicting changes together or determine which should take priority, so it needs human intervention to reconcile that issue.
Identifying the risks of code merges
It is absolutely required for developers to merge any upstream changes from the integration branch into their own working branches in order to eventually ship their code to the integration branch without issue. However, this is where several risks come into play.
Fundamentally, what makes merging other developers' code into your own code so dangerous is a lack of context compared to the original code author. Without this context, it becomes much easier to unintentionally change new functionality or introduce a bug without knowing what changed elsewhere in the software when merging code together (since someone else wrote that code and has the domain knowledge for the changes).
This risk increases alongside the complexity and number of upstream changes from the integration branch. As those increase, developers are more likely to make a mistake merging those changes into their working branches.
Given this situation, the age of the working branch is the best metric to track how risky code is to merge into the integration branch. Branches that live for one day are not very risky and are unlikely to create bugs, while branches that live for an entire month are much more likely to contain bugs from merging in upstream changes during that time. The reason for this is two-fold: First, the shorter-lived the working branch, the less likely it is to have code conflicts to begin with, so developers won’t have to handle as many merges. Second, when developers do have to merge code, it is usually a smaller amount of changes when the working branch is shorter-lived, which makes the intent of the changes much clearer and avoids developers having to ascertain that context on their own.
How to manage against the risks of code merges
The best practice to manage against the risks of code merges is to keep working branches as short-lived as possible. If you are an engineering manager, you can help achieve this goal by introducing several activities to help your team move faster and with more confidence. These activities include:
- Prioritizing merges for the longest-lived branches: Quite simply, you can view pull requests by age to pinpoint the oldest ones. Then you can focus on merging those older requests into the integration branch first.
- Eliminating blockers that create bottlenecks: Many teams have institutionalized blockers that require intervention and create bottlenecks, such as processes that require certain people to review specific sections of code. You can empower your team and remove these blockers by bringing everyone to the same skill level so that the entire team knows how to review code and can therefore do so more efficiently.
- Improving planning by creating smaller changes, particularly through Continuous Integration and Continuous Delivery: Every person on the delivery team can help in the planning process by identifying smaller, more granular versions of stories. This leads to a better shared understanding across the team of what others are working on and creates shorter-lived working branches. Essentially, it keeps changes small and simple so that they are easier to understand and process in parallel across the team.
Start de-risking your code merges today
Developers often prefer large stories that implement fuller features because it’s more psychologically satisfying to ship big changes. But it’s important to remain cognizant that our sheer delight for big stories should be counterbalanced by the fact that these big-bang changes decrease collaboration and increase the chance of anxiety and delays for the rest of the team.
Ultimately, the more granular changes your team plans, the more work you can ship through the whole development process to production – with less anxiety and delays thanks to reduced risk around code merges.
Critically, the best way to make these smaller changes a reality is to start practicing Continuous Integration and Continuous Delivery. That’s where Spaceship comes into play. Contact us today to learn more about how we can help your team get started.