I originally published this article on my substack.
Big changes carry more risk than small changes.
Ambitious, “big bang” style changes often fail, or generate more organizational ill-will than they do business value. There’s a reason just whispering the word refactor can send a Product Manager into a fugue state.
Merging and deploying months worth of work is much riskier than merging in a days worth; a 5 line PR is less risky than a 500 line PR.
Does that mean that a bad 5 line PR can’t do more damage than a 500 line one? Of course not. But the human brain is much better at handling small changes—at being able to contextualize and model one thing at a time.
We just understand small changes better. This is why software teams are incredibly intuitive and accurate when estimating in the short term, and terrible with anything more than a week or two out.
Dealing with large and/or long term changes is more fragile, because it involves more prediction. Prediction is fragile. Prediction tries to deal in the probability of a particular outcome, and can never account for Black Swan events swimming in the tails of the bell curve. Hell, it doesn’t even take Black Swans, just sustained mild volatility will break many predictive models.
Antifragility views prediction as a distraction at best, and instead focuses on building options so that no matter what, you have good choices.
By focusing on making our changes small, we become antifrAGILE, and able to change quickly.
Continuous Integration (CI) is a practice to reduce risk by making changes small and continuous.
CI is a practice first introduced by Extreme Programming. Over the past decade, the term CI has begun to be misused to refer to build servers and services that form an important part of CI. But CI is not something you can buy, CI is a practice you must develop.
What does it mean to “integrate?” Integrate means integrate your work with the work of everyone else on your team. Continuous means, in this context, frequent. You must be merging everyone’s code frequently.
The Extreme Programming practice of Continuous Integration encourages all members of a development team to integrate their work daily, instead of developing features in isolation for days or weeks.” — Martin Fowler
Jez Humble, author and influential member of the DevOps movement, likes to ask folks who believe they are doing CI three questions:
If you cannot answer yes to all of these questions, you are not practicing Continuous Integration (even if you own a CircleCI license and you run all your integration tests on every commit to your feature branch 😢).¹
If you’re like me a year and a half ago, this revelation feels radical—wrong somehow. Merge conflicts are awful, I’d like to deal with that less, so I’ll wait longer to merge, thank you very much. Also, how can I just merge into main? What about my bugs?! Where does QA fit in? What if it’s not done?
The intuition is that merging daily will lead to smaller, but more frequent merge conflicts, resulting in the same amount of work—just perhaps distributed into more manageable amounts. The reality is that the relationship here is non-linear. Volatility, randomness, time, chaos, change—they all compound. Counterintuitively, more frequent integration will result in not only smaller merge conflicts, but also fewer merge conflicts.
One of Martin Fowler’s favorite phrases is “If it hurts, do it more often.” He gives three reasons to do it more often: First, many tasks become more difficult to do as the amount of work to do increases. Second, we need feedback. We cannot receive feedback if we are not doing the thing. Third, we need practice. Often practice will refine our process and help us really understand.
Imagine you need to collaborate on a presentation. The most frustrating way to do this is to set up an email chain where everyone keeps emailing new versions of the document and you need to integrate everyone else’s changes while also incorporating your own.
The simplest and most collaborative way for me to do this is use a Google Doc where I can see everyone editing in real time. I rarely have conflicts, and if I do I know before I make the changes and I am communicating with someone who recently made the conflicting change. This conflict is a conceptual conflict—a departure in ideas or direction—not an editing conflict. The live nature of the document has helped bring the conflict to the forefront where we can discuss it before moving on.
The more continuously you integrate, the more you are turning your main branch into a Google Doc.
“Ok, Jason. But I work at X Big-Co. Maybe that works for small teams, but we have 1000 engineers. We can’t all be editing the same Google Doc at the same time!”
Honestly, I haven’t experienced CI at this scale. But as I understand, Google, Facebook, and many very large codebases work this way. It takes some more tooling, but also… can you imagine the massive game of car-chicken over merging pull-requests between 1000 engineers? Hopefully at this point you have developed some good boundaries if you are still developing in a monorepo.
Fast Feedback Loops
This is the real goal of CI/CD -- to shrink the software lead time to an interval short enough that human learning systems (dopamine, adrenaline) can usefully map to the feedback loop of writing and shipping code.— Charity Majors (@mipsytipsy) December 15, 2020
(and then of course there's @darklang, the hot stove of software) https://t.co/vdCVIYGS8W
(Don’t get me started on how rad darklang is philosophically. A newsletter for another time.)
One of the keys to any successful agile practice is feedback loops. How you you know when to zig if you aren’t paying attention, let alone zag?
Feature Branches receive certain types of feedback. Perhaps there was already feedback collected by UX research before implementation even began. Perhaps I got a code review at the end—or even review or a pair of eyes along the way. This is all great feedback. But one very important bit of feedback is missing: does it blend?
Until I’ve integrated my code with the work that everyone else is doing, I don’t know if my code works. My code might work perfectly and pass QA on my feature branch, but I have no clue how it will interact one I merge with trunk, let alone the feature branches of my colleagues.² (Does your QA vet the merged artifact?!)
Or perhaps, like in the Google Doc analogy, there’s been an important misalignment in direction or intent of an interface or service, and we’ve spent weeks or worse pulling in different directions. Integrating early and frequently provides feedback that brings these conceptual conflicts to the surface sooner.
Finally, forcing ourselves to start to think small leads to smaller experiments. As Charity pointed out in her tweet: we shrink the lead time to get something into product to be so quick that we can go from idea, to implementation, to running in production and observing the results of our product hypothesis in no time.
It may go without saying, but Continuous Integration is probably not a very useful idea for solo development—there aren’t many things to integrate with.
But what this means it that CI is a practice specifically designed for teams.
If you're reading this and thinking "does this imply feature branching is a fool's errand" - the answer is yes. Branching optimises for the individual. Trunk Based Development optimises for the team, and flow. Sometimes branching is needed, but it doesn't have to be the default https://t.co/t43ddR9jhm— Steve Smith (@SteveSmith_Tech) December 6, 2020
(Trunk Based Development is a key requirement for CI)
It feels pretty smooth to throw on a headset and go heads-down for a week after Monday planning meetings, surfacing here and there for a morning standup. You knock out your tickets, pat yourself on the back, and throw it over the wall for QA to test in some sort of temporary staging environment. You pick up your next story, only to be interrupted by QA when it’s approved and you’re ready to merge. Hopefully there’s no merge conflicts. Or oops, QA found a bug and now you gotta context switch back to where you were a week ago. Either way, minor inconvenience for you.
Now multiply this by the number of people on your team/in your code base. The cost is worse than that, because again with volatility, the response is non-linear. We want practices that optimize for the flow of the product, not for individual productivity. (Optimization for individual flow is one reason Cal Newport has declared the fall of GTD.
One teamwork analogy I heard growing up was about yokes and oxen. Imagine a covered Conestoga wagon being pulled by two oxen. Those oxen probably are not the same strength—it’s very likely that one could be pulling faster. But it couldn’t be pulling more without its partner. Even though the yoke slows down one of the oxen, they can pull more together.
Teams are the same way. You may be a “rock star”/ninja programmer and you can code way faster on a feature branch and if you merge first maybe you avoid conflicts. But you can’t out-code your whole team. Continuous Integration optimizes for team flow.
One of the big concerns with most folks learning about the Truth of Continuous Integration is quality. This is good. This means you are a good engineer—you want to deliver quality software that your customers want to pay for.
The concern is that Continuous Integration bypasses many of the common quality assurance steps we may be used to. If we are all merging daily into trunk, how do we make sure we have something reliable to deploy?
Remember that one of the three keys of practicing CI is that every commit to the trunk triggers an automated build and test. Your automated tests should be a good signal that your existing features are working as intended.
But even with tests, there are some things that having passing tests, but are not done. They are not ready to be released! And if everyone is doing this all the time, how do we ever end up with an artifact that we can release? A code freeze??
Alas, it is 11PM the night before the Thursday morning this newsletter will go out and it is past my bedtime. This got a lot longer than I expected. I was hoping to get into the next topic to answer this question, but it will have to wait! I will give you a hint though, it starts with Continuous and ends with elivery.