There are lots of approaches to delivering new versions of software. You can release big bundles of features all at the same time as a whole new version. Alternatively, you can release individual features one at a time, and not worry too much about version numbers. Or you can go even further by releasing features in multiple slices, iterating as you go. As with everything in software development, all of these approaches have pros and cons.
At Gearset, we favor the iteration approach - we try to slice our product work into small, vertical slices, releasing good now instead of perfect later. In this post, I'll explore why we prefer that approach, what we mean by "small, vertical slices", and how we achieve it in practice.
Back to basics
In the beginning, software was released on physical media. The only way to deliver a new version was to batch up all the improvements you wanted to make, do a lot of testing to make sure that you didn't have any show-stopping bugs, and release the whole new version at once. This approach is sometimes called a "Big Bang" release, and it's still often used today for some types of software, such as games and operating systems.
There are some advantages to the Big Bang model, but there are also a lot of downsides. For example, you might spend months perfecting a whole set of features, and then discover that your customers don't actually want that feature set, or they were imagining the features would work differently. It's hard to set up a tight feedback cycle between your development process and the users of your product if you're not getting new iterations in front of them very often.
In addition, it might be tricky to ship small bug fixes if your workflow isn't set up to make releasing easy and painless. If you do a release once every six months, you're unlikely to have spent the time and effort setting that workflow up. On the other end of the spectrum, if releasing multiple times per day is routine to your team, then getting a fix out to customers will be quick and easy.
As a result of these and other advantages, the general trend has been towards more frequent, smaller releases. This has also been driven by the rising popularity of web apps, app stores, and other distribution channels that allow for quick and easy upgrades to new versions of the software. At the same time, there's been a trend towards using "agile" methodologies within software workflows. At Gearset we don't follow any particular agile methodology down to the letter, but we do pick and choose the parts we like most from a lot of them. Our approach to breaking up development work is a great example of that.
Okay, so lots of people are moving to smaller releases. Here's where the "vertical slicing" comes in. A small release implies that not many changes are shipped in each release, but it doesn't imply anything about the shape and nature of those changes. At Gearset, we like to think about feature work in terms of "slices".
A slice is, essentially, a single deliverable chunk of a feature. Usually a developer will take a feature that they want to build, look at what the main components of it are, and decide what the smallest useful chunk of functionality is that they can implement to get towards the goal of having the whole feature implemented.
Ideally, a slice should be a single conceptual change or, if that's not possible, a logical grouping of changes. We avoid grouping changes when they don't need to be grouped. Let's say we have some alterations we want to make to a page - we want to change some of the behavior, and we also want to update the layout to be more consistent with some other pages. In this case, we'd probably deliver the work in two slices, one with the behavior change and one with the layout change, because they're not particularly dependent on each other.
Vertical vs horizontal slices
When we come to implement a feature and start thinking about slicing, we can often think of multiple ways to break up the work. So how do we decide which way of slicing the feature is best?
Let's take a step back to think about the benefits we're trying to achieve. We'll explore them below in more depth, but for the moment, you'll notice that I've split them into two sections:
Benefits of small slices:
- Limit the risk of each release - as each change is small, the risk of the release is lower
- Allow for more useful peer review - regularly reviewing smaller changes is more timely and effective
- Maintain momentum - it's more motivating to be constantly shipping improvements
- Encourage frequent releases - smaller slices mean work gets finished more often and there's almost always something to release
Added benefits of vertical slices:
- Deliver useful improvements to customers as soon as possible
- Get quick feedback from customers on new features
- Be able to pivot when priorities change and the thing you're working on is no longer the most valuable thing you could be doing
- Exercise code paths and expose bugs as early as possible
Okay, but what is a vertical slice?
The term "vertical" comes from the idea of slicing down the technology stack, rather than across one layer. In general, a new feature will require some changes to all levels of abstraction.
When delivering a feature in slices, we could make just the changes to one abstraction level at a time (so, just do the database changes, and then just the back end changes, and then just the UI changes, for example). This would be a horizontal slice.
Alternatively, we could make multiple changes that sliced vertically through the stack, each adding one small part of the functionality throughout all the levels of abstraction. This would be a vertical slice.
At a glance, vertical slicing seems more complicated and difficult - isn't it easier to just touch one level of abstraction at a time? In some cases, you'd be right. However, the problem with the horizontal approach is that the work you've done isn't really "released" in a true sense until all the layers (and so all the changes) are finished. If you do a release to change the database, and another to change the back end, none of that code will actually be executed until the UI is updated to make use of those changes. In addition, users won't get any benefits until the final slice is in.
In contrast, with a vertical slice, you aim to release usable functionality in each slice. That way, users get their hands on the cool work you've been doing even faster, and you get to talk to them to see if you're taking the feature in the right direction. In addition, at any point after a slice is released, you could stop and work on something else without having any work in a limbo state where it's released but isn't being used yet.
This ghost-code introduced by horizontal slicing is risky in multiple ways:
- If for some reason you decide not to finish the last slice, you've wasted all the effort in the previous slices, because you've released no changes to functionality.
- You've never executed that code in production, so it's more likely that it contains unknown bugs.
- If you take too long to develop and release the last slice, assumptions made in the rest of the code may have become invalid without you noticing (again, because it's not being executed regularly in production).
Exceptions that prove the rule
As with all rules, you shouldn't just blindly follow the "vertical slices are better" rule. There are a few cases where horizontal slices are more appropriate.
In the main, horizontal slices are better when you have operational considerations about how to release a change to production. For example, there are cases where we need to deploy a database change independently from any code changes to make sure things are forwards/backwards compatible, as part of our blue-green deployment process. Sometimes this is unavoidable - it's not that you wouldn't get the benefits from vertical slices here, but sometimes other considerations make it hard or impossible to follow the rule.
You might be able to get the best of both worlds by slicing each of your vertical slices horizontally to increase your granularity (in the diagram above you'd end up with a release 1a, 1b, 1c etc.) Whether that approach ends up with overcomplication and slicing your releases too small is something of a judgement call.
As usual with this sort of rule of thumb, life doesn't always fit neatly into the analogy. You won't always be able to categorize your slice neatly into "vertical" or "horizontal", and there isn't always a clear best way. It is, however, a useful framework for thinking about how to split up large chunks of work, and an idea that we have found very powerful at Gearset.