Engineering

Keeping the codebase consistent with Pattern Parties

As a codebase evolves, it’s common to see some divergence in the design patterns within it. Most engineers have seen at least one legacy codebase with a mishmash of different styles to write code doing similar things.

💡 Design patterns are generic solutions to reoccurring problems in software design. Think of them like best-practice templates for how to do something specific, for example registering a subscriber or defining a form.

This happens as a function of time, as engineers find new and improved ways to solve problems. It’s also accelerated by the growth of the organization, with new engineers onboarding and splitting out into separate teams with distinct code ownership and approaches.

If the code works, why does it matter if it looks different?

Writing consistent code from the beginning sounds pretty sensible, but what about when you find yourself with diverging patterns already in the codebase? You could argue that it’s not worth spending the time refactoring existing code to meet design patterns rather than using that time to say, build new features.

There are quite a few benefits to investing that time in synchonizing the design patterns:

  • 🚢 Shipping is faster when you can copy-paste similar solutions from places where you know the design patterns are correct.
  • 🛫 New joiners onboarding is smoother into a codebase that looks and feels more consistent.
  • 🐛 Debugging issues is quicker as it’s easier to scan and parse similar code even if you haven’t seen it before.
  • 🤝 Teams collaborate better when engineers can switch fluidly into code owned by another team without it feeling unfamiliar.

At incident.io, we’ve introduced Pattern Parties as a way to keep our codebase consistent, our design pattern documentation up to date, and our engineers on top of the patterns.

What is a Pattern Party?

For a number of days, the engineering team is dedicated to the Pattern Party instead of their day-to-day product work. We choose a handful of related patterns that we think would be most impactful to implement across the entire codebase, and as a team we apply them as widely as possible.

Crucially, in any given party we’re not aiming to address every possible **pattern—it’s a tightly scoped set of changes that makes migrating a single service a manageable chunk of work.

At incident.io we hold these as two-day parties as part of a quarterly wiggle week (an idea inspired by a great Intercom blog post), alongside either a hackathon or bug bash for the remainder of the week.

Why not have a small group carry out the rewrite?

Instead of taking the whole team, we could have chosen a handful of people and asked them to rewrite the codebase into these patterns. There are pros to that approach - It would require less organization and have less impact on roadmap work. The small group would likely be faster and overall fewer engineering hours would be spent on it.

The major downside to that strategy is that while we get the same codebase uplift, the wider team doesn’t get a chance to contribute to, understand, and internalize the patterns, so they’re less likely to use them effectively going forward. It’s also quite a repetitive piece of work for the small group chosen.

With so many engineers working on the codebase at once, we do have to take steps to minimize merge conflicts and isn’t possible to avoid them altogether! Any refactors with wide-ranging imports eg. restructuring packages that are widely imported elsewhere should be done before the whole team starts porting services, and regular rebasing is essential.

Party prep

Like any great party, we start by choosing a theme. In the two parties we’ve held so far they’ve been pretty broad: the first one addressed only backend services, and the second one likewise tackled the frontend.

The team gets a chance to suggest patterns they’d like to see implemented globally, or highlight problems that aren’t addressed well by our current patterns.

A few engineers will then select a shortlist of the most impactful patterns within that theme, based on questions such as:

  • Which patterns do we use most frequently?
  • Where do we see the greatest divergences?
  • What do engineers get slowed down or stuck on?
  • How much is achievable during the party?

With patterns to address agreed within the small group, they’ll suggest what the right approach looks like and share it with the wider team for feedback.

Once consensus is reached, the small group can document the agreed patterns, and set out guidance for how to migrate code that doesn’t match the pattern over, including a migration checklist to make sure everything is covered.

Right before we begin the party, we’ll move all the existing affected services into a legacy folder, so we can easily see which ones have been migrated.

One service containing all the patterns that need to be updated will be moved out of legacy, and ported to the new patterns. It can then serve as an example for subsequent migrations.

Getting the party started

To begin, we run a session talking through the patterns we’re planning to address in this particular party, why they’re important, and why we’re suggesting certain approaches.

While we do prepare approaches ahead of time, it’s also an opportunity to refine them with the entire team present to reach consensus.

Running the party

With the patterns agreed, we launch into the party itself. The engineers are mixed up into different teams than their usual product work, and pair up to tackle their first service together.

We start by pairing up so that everyone has a chance to chat through the new patterns, think about how to apply them to their chosen service, and raise any questions or suggested improvements with the wider group.

Once the team is feeling comfortable and the patterns have been refined as necessary, engineers can work independently to get as many services as possible over the line.

By the end of the party, each engineer will have migrated at least one service out of the legacy folder and to the new patterns. They will now be familiar with them and ready to start writing new features ‘in-pattern’, as well as helping others via code review.

The afterparty

With the party wrapped up and (hopefully) a large proportion of the codebase ported to the new patterns, we’ll make sure all the new patterns are thoroughly documented for future use.

The remaining un-migrated code stays in the legacy folder for now, so that we know not to copy its patterns. When engineers go to build new features or fix bugs in those folders, it’s clear that this is code they should port to the new pattern while they’re there.

Raising the pace

Our number one priority in the incident.io engineering team is pace. So it may seem counterintuitive to take the whole team off shipping features and fixing bugs for a couple of days every few months.

Once the Pattern Party is over, having a consistent codebase and engineers who are all familiar with its design patterns raises the pace of every feature we build going forward, and accelerates the onboarding of every engineer who joins afterwards. We think the trade-offs are worth it.

Pattern Parties are just one of ways we build a great developer experience, why not check out a few more here?

Picture of Kelsey Mills
Kelsey Mills
Product Engineer

Move fast when you break things