Continuous Integration (CI) is a development practice where developers integrate code into a shared repository frequently, preferably several times a day. If you are following this, or other DevOps practices, chances are good that you are also utilizing a CI/CD pipeline to automate your builds and releases.
If you are, then each integration can then be verified by an automated build and automated tests. While automated testing is not strictly part of CI it is typically implied and is a very powerful capability of a CI/CD pipeline.
The Shared Mainline
A “shared mainline” is the branch that all developers are committing to, whether it be a trunk or some
feature branch. As discussed in my podcast episode, “Trunk-Based Development Is Too Risky! | Source code management, CI/CD, and software development anti-patterns“, “trunk based development” doesn’t necessarily mean everyone commits their changes directly to the trunk.
There is a version of this development model sometimes called “scaled trunk based development” where short lived features branches are used to build code on and are quickly merged back to the trunk. These features branches should exist no longer than a couple of days.
By committing to this shared space daily, developers are encouraged to break their work into smaller working increments, and integrate them with everyone else at a more regular and smaller interval. This is important because smaller changes mean less risk. More frequent integrations reduce risk because less change can occur between each integration point.
Many people get hung up on what it means to check in code that “works”, so it’s important to clarify that checking in a function or method that isn’t used anywhere yet is a perfectly fine practice. Dormant code is not being actively used anywhere and can’t cause any problems.
Change is Hard
Unfortunately, many companies and their leaders are afraid of trunk based development. They feel like it increases risk despite research and practice proving the opposite is true. Let’s face it, the switch from feelings-based release management to data-driven decision making is a difficult one to make because it can sometimes go against human nature.
However, developers are highly skilled professionals and need to be trusted to check in code to a shared repository as often as possible. If something breaks, it can easily reverted because of the nature of source control. With appropriate test automation in place, code reviews, and management of technical debt, frequent integrations are objectively less risky than larger, infrequent integrations.
Research has also shown that a high software delivery performance can be correlated with short lived branches (less than a day old) and daily merging and integrations to the trunk. This is the case because long-lived branches actually discourage refactoring and intrateam communication, instead of promoting stability as one may assume.
Automated Tests on Every Commit
Code working locally is not sufficient, as that code can be behind or ahead of the current production baseline along with any additional customizations a developer may have made to their local environment. The true evaluation of a code’s build and test is when it is run from a central, neutral, and standardized location.
This also ensures that everyone’s changes work together, meaning if two people commit to the same branch two things can happen:
1. Both commits go into the same build
2. Each commit results in different builds
The second case is far more likely, and is what is generally desired. This is because if a build and/or test is broken we can easily nail it down to a single commit. If something is broken it is also easy to identify and easy to fix, whether reverting the change (rolling back) or just fixing it (rolling forward). Again, this supports the research of short, frequent integrations to the trunk being better than long-lived feature or integration branches.
We are guaranteed that builds and tests will fail from time to time. However, it is important that a broken build get fixed as quickly as possible when using a frequent integration pattern or trunk-based development. The golden standard is that when a build or test fails, that it gets fixed within 10 minutes.
Broken code changes are counter productive because when a build is broken, all integration stops. No other developers can reasonably integrate their changes, because if they do the build is still broken and we now can’t be sure if these new changes (to the broken build) even work. Every time more changes are made to a broken build, we dig the hole deeper and the harder it becomes to get back to a working state.
This is why it is paramount that a broken build be fixed immediately. When it doubt, rollback whatever changes were made since it was last working back. Changes are not lost, because source control keeps track of them.
If you’re using a CI/CD pipeline, it’s time to start following a trunk based development model and leveraging automated tests to ensure you’re producing high quality code. It will speed up your ability to:
- Produce code rapidly
- Reduce the risk of merges
- Ensure you are producing high quality software
Why would you not want those things!