The conventional wisdom in engineering is that you have to make a trade-off between quality and speed. I believe that this is mistaken.
The point of solving technical debt is to improve maintainability and performance – so how could this be so diametrically opposed to speed? If you’re a high-performing team, quality and speed are fundamentally the same goal. It is impossible to sustain high performance in the long-term without a high-quality system to work in, and it is difficult to make the case for investing in quality to the stakeholders of teams that aren’t delivering business value.
How did we end up believing in this trade-off?
Most people in technology will have seen some variant of this diagram, which aligns with the argument that you can have any two of speed, quality, and cost. This comes from traditional approaches to project management, where you deliver a project involving fixed scope and a fixed deadline instead of working on a product. Waterfall is over, but this idea remains ingrained in software teams. In today’s world of incrementally building products instead of delivering projects as big-bang launches, it has become clear that you have to be implementing sustainable engineering practices, and that can only mean delivering quickly and achieving high standards of quality.
This trade-off of speed and quality is also fueled by teams having certain structures of responsibility. Teams often have a split between those responsible for the delivery of features and those responsible for technology. For example, in the Spotify model, functional groups of staff in ‘Chapters’ are responsible for technical standards, and the unit that delivers software is a ‘Squad’ responsible for a functional area. This can create the illusion that these are different things that must be balanced.
Most teams take an approach of splitting capacity between the goals of quality and speed: a ratio is planned between the two streams of work, and typically it’s lots of features and a little bit of quality. On the surface, this seems like a reasonable approach to the problem, however, most teams find themselves still struggling to do both. This typically results in making the argument to rebuild a system that is deemed beyond repair, hoping that the next attempt will be different.
Why are quality and speed inseparable?
There is a virtuous circle in quality and speed: teams that have high-quality, maintainable software are able to ship faster. And teams that are able to ship faster can invest more time in maintenance and quality, leading them to have codebases that are higher quality and more maintainable. However, this doesn’t happen if you’re not bringing speed and quality into every aspect of a team's work.
A team that focuses on speed but treats technical debt and code quality as an afterthought will soon grind to a halt. They will be left unable to meet their commitments and unable to make enough improvements to make a real difference to quality. A team that is focused on perfect code is often seen as too slow to achieve their commitments to the business, which leads to unhappy customers who aren’t getting the features they need and want.
Once you realize this, you can begin to see your responsibilities as an engineering leader in a different way: your responsibility is to ensure that you’re shipping valuable features to your customers today, but also next week, next month, and next year. To achieve this sustainable pace of delivery, you need to focus on improving the quality issues that are impacting speed the most.
Listen and learn from failures
One way to find out what has been going wrong is by asking your team questions during 1:1s. ‘What has slowed you down recently?’ ‘If you wanted to deliver a similar feature faster than this one, what would need to change?’
You could also hold blameless post-mortems when a feature has gone off track or when an incident or a critical bug has made it into production. All of which invariably cause a knock-on impact on time-to-deliver.
Five whys is a helpful technique to find the root cause of an issue. It also helps make sure that the actions the team comes up with are ones that will truly have a positive impact, rather than just surface-level improvements.
Pay off your highest interest debt first
Once you know about the root causes of the problems your teams are facing, spend time trying to work out which is the most pressing one and the impact that fixing it should have.
There are a few things you can do here to gain more context. Firstly, try to understand which areas of the codebase’s upcoming work will touch by talking to your product team. Then, you can try to find answers about how your teams are working with the codebase by asking the following questions:
- Are there modules in your code that are involved in a lot of features?
- Are there a lot of bugs reported in a particular area?
- Does a particular service have a large number of production incidents?
These questions can provide you with important context on what the most high-leverage areas to work on are.
If there are mountains of technical debt in parts of the system that are rarely touched and don’t cause many incidents, then they’re probably not problems until one of those elements changes.
Fix the problem
To get approval for spending time on problem-solving, you first need to build alignment between your stakeholders and your team on what the biggest problem is, and why fixing it before building another feature or fixing another problem is the right thing to do. The analysis you’ve done on problems previously will be helpful in this process.
Working out the right amount of time to spend can also be a challenge and depends a lot on the scale of the problem, but a rule of thumb is that a good solution implemented quickly is far better than a time-consuming perfect solution.
Once you’ve made improvements, it’s important to evaluate whether you have seen the benefits you expected, and use that information to power the next cycle. If the results have been disappointing, try to find out why, and think about what you wish you had been able to find out before focusing on a particular area.
Build quality into the process
Now you’ve focused on solving quality problems enough to start seeing some improvements in your speed, it’s a good time to start building in more quality. There are a few common causes of delays in shipping features: critical bugs that need to be solved urgently, production incidents, big gaps between deployments, and slow feedback loops between QA, product, and engineering. Most of these can be improved by bringing in more automation and enabling quality review earlier in the process.
To improve quality, I focus on what the book, Accelerate, calls ‘shifting left’. This means bringing more parts of the processes that happen after you finish writing code, earlier in the process. If you have a common user flow through your application, why does it need to wait for a human to test it? Could you deploy every pull request as a branch so you can get features signed off as part of the review process? Bringing in feature switches and feature toggles to avoid blocking deployment is also critical to removing bottlenecks in your attempts to ship quickly.
Attempting to achieve a split between speed and quality is an outdated approach that leads to poor performance in both areas. To support your teams in achieving their potential, you need to take an approach where you combine curiosity (to find the root causes of issues) with focus (to drive improvements in the systems). And remember, this process also needs experimentation to find what works for your team. By focusing the effort of your team on improving areas of the codebase and fixing quality issues that slow you down, you will create a high-performing team that is able to iterate quickly enough to support the needs of the business, while building a codebase that is maintainable and clean; because ultimately, it is not possible to have speed without quality.