On every team I’ve been on so far in my career, a tug-of-war has existed between engineers and product managers over quality and feature work.
From the engineering perspective, we want clean abstractions. We want well-thought-through technical designs and implementations. We want to keep our code DRY and well organized. We also want tooling and automation to ensure our quality bar is consistent.
On the flip side, within software, we’re usually building a product to help a business meet specific objectives. Investments are made to help the business earn or save money. These investments aren’t infinite, so we’re given deadlines for when to ship or add certain functionality to the product.
These two aspects of software engineering tend to fall on opposite sides of how we plan to use our time. Engineers sometimes need to sacrifice ideal implementation in order to meet deadlines. When this happens, the team assumes technical debt. Technical debt adds up over time in the code base, the deployment infrastructure. Cutting features occurs when the team chooses to maintain quality or pay off technical debt.
Looming deadlines and necessary feature work cause large increases in technical debt. Work items for technical improvements get buried in the backlog, the number of bugs increase, and making changes to the application takes longer because the code is so intertwined. Similarly, too much focus on quality causes issues. Teams miss deadlines, cut features the customer thinks are critical, and end up shipping a product the customer doesn’t want. Spending so much time prematurely optimizing for quality means there isn’t a focus on shipping the product.
So how can we build software in a culture where we’re expected to ship features to customers on an established timeline without our quality work items getting lost in the backlog? I’ve grappled with this very problem throughout my career and tried many approaches to get alignment on quality improvements with mixed results. I didn’t start to have consistent success until I changed my perspective on quality work and how it relates to feature work.
Quality work and feature work are a circle, not a balance. Two parts of a single whole: the customer experience. Central to any software is the user. Whether it’s a web application being used directly by customers or software generating reports for the organization, software is all about the ultimate end-user of the code.
As I was looking to improve the culture of quality in my own teams, I thought about this circle. I wanted to ensure we were correctly balancing and prioritizing important quality work items against our feature work and tight deadlines. I realized I needed to change how I defined quality work.
Expressing work in terms of customer impact
As a software engineer, the outcomes of a given workstream tend to be obvious. Engineers know that optimizing a SQL query will reduce the latency for a given service. They also know that adding unit tests helps prevent the introduction of regressions into the product. Similarly, finding the right layer of abstraction makes it easier to add new features or modify current behaviors. But for non-technical people, the value of these work items may not be as readily apparent, which leads to them deprioritizing this work for other work items with a more visible customer impact.
I’ve worked on a lot of items where we skipped writing automated tests to hit a deadline. We’d create an item in the backlog to go back and write those tests later, but inevitably, the item sank into the backlog and didn’t resurface again for months. By then, I’d completely lost track of what tests I wanted to write and why those tests were even important. Suffice it to say, I was always nervous to make changes to those modules.
Why didn’t my tests get prioritized? Because I lost sight of the customer impact for doing the work. I literally titled my work item, ‘Write unit tests for feature’, instead of writing it like a user story: ‘As a <type of customer>, I want to <perform an action> so I can <accomplish some goal>.’ For me, this was as simple as: ‘As a customer, I don’t want this feature to break in future releases.’ Reframing the ticket in this light helped me hold myself accountable for providing a better customer experience and also encouraged the team to hold me accountable for finishing the work.
When defining quality work, it’s critical that engineers are able to express work items in these terms. Here’s another example. Instead of creating a work ticket that says, ‘Optimize the customer preferences SQL query,’ express it as a customer outcome instead: ‘As a customer, I want my preferences view to load within one second.’ This work item is now focused on how it impacts our customers, and we can compare it like-for-like with other feature work items in the backlog. This then enables a discussion on how to prioritize the work appropriately.
Thinking of how best practices – such as automated testing – impact our customers helps us justify taking the time to follow those practices. And when we assume technical debt by not following them, defining our work by its customer impact allows us to justify coming back to resolve that technical debt rather than leaving it to fester.
Using a customer-centric attitude as a gate
There’s another benefit to expressing technical work items in terms of customer impact. They can be used as a gate check to help engineering teams ensure they’re working on the right items at the right times.
Shortly after launching a product I’d been working on, my team had time to do a retrospective on our product and what items we should work on to polish the application. We’d chosen a Rich Text Editor library that worked fine for our use cases, but the APIs felt dated and the way we had to interact with the library was different from patterns used in the rest of the application. Because it was so clunky and difficult to work with, I created a ticket to migrate to a different library, but there was no real customer impact for making this change since it wouldn’t alter the product in any way. Given that there were other items on the backlog that did have customer impact, justifying the time it would take to do this work didn’t make sense and it naturally sank to the bottom of the backlog before falling off entirely.
I didn’t feel frustrated that the team didn’t do this work because I was able to put the work item in context with the other things my team needed to do. Compared to the feature work we still needed to implement and the quality issues our application had, it didn’t make sense to work on something that didn’t offer the customer any tangible benefits. Eventually, this shift in perspective caused the team to drop this initiative entirely so we could focus on work that really mattered.
Making the customer the focus
Instead of focusing on ‘move fast and break things,’ we should instead ‘always do what’s right for the customer,’ even if that means taking on workstreams the customer might not even see. As we view the customer experience as central to all our workstreams, a few things will start to happen: we will hold our code to higher standards, we will justify the time it takes to write code the right way, we will prioritize the right workstreams to maximize customer impact, we will improve quality and ship better software, and we will have a common language for communicating with technical and non-technical members of the team. With that common language, we will develop both a common goal and mutual passion for the success of our customers. If we do this, we can obtain feature and quality harmony, truly creating a culture of quality.