Article | Back to articles
Our technical debt is showing in our user interface. What can we do?
25/05/2021 by Benoit Rajalu
It's not easy to age gracefully, I'm told. I wouldn't know, I'm a surprisingly young old person. It's still particularly true for software UI. Our applications' scope will grow with time and they are going to meet one of two fates: either they will look globally outdated or their oldest parts will noticeably lack homogeneity.
The team in charge of product design changes. Some people move on. Market trends change as well, sometimes yearly. We can see each of these events as layers on our UI. Each time the direction shifted, people meant for it to consume the whole product, new features and old ones alike...but there was no time, no budget. The vision was looking forwards, not backwards.
We've heard that story before. When our code is outdated or compromised, we've learned to see it as "technical debt". Now we stand and face UI debt, the wrinkles on the face of our app and we wonder: what can we do? Is design homogeneity the privilege of youth, of the hip new app that comes out?
It should not. It certainly requires more effort and planning to get there, but there are ways "senior" products can eventually move towards the same homogeneity.
Before we address the technical tools available, there's an important product management mindset we need to have.
It's always hard to accept that some of our teams' productivity will go towards working on things that are already "done". Where's the value in that? The budget for that page / feature / website has been spent! Clients are using it, it won't bring in more revenue if we put more time into it.
Isn't that a shortsighted look at it though? Clients may find the "finished" product increasingly hard to use with time. The core interactive precepts the legacy features rely on may no longer be "taught" by the other parts of our UI (think of icons for instance, or colour-codes). What if the design we chose 10 years ago is now so laughably out-dated our competitors use it in their "before vs after" shots? That would suck.
We know of budgeting for technical debt in our sprints (do we?), but that budget should also account for the visual debt, as long as it's there.
Luckily both can be closely tied.
Our technical and visual debts usually intersect with our styling and markup tools.
On the web, the way we handle styles has never been more diverse. Gone are the days where everybody loaded a single CSS stylesheet and called it a day.
Preprocessors (SASS, LESS, Post-CSS), CSS modules or CSS-in-JS in their many different shapes have all up-ended expectations and made it harder to deliver a single, homogenised way to style UIs across the board.
If you've never heard of design tokens, you are going to love them. They are abstract (variables) representing standalone design atoms. Colours, icons, font styles, even spacing, the core principles of your designs can be reduced to a collection of design tokens. We can even automatize their extraction from design assets. This article isn't about them per se, but Nathan Curtis has got us covered for a deeper dive.
The concept of tokens is great but we rarely think of the past when we produce them. We export styles as SASS, or LESS, or JS strings variables because that's what our current stack consumes. We export icons as React components for the same reason. It's a choice, we prioritize the current needs and those are often tied to new features running on our latest tech.
Those tokens however are our single best opportunity to pay our UI debt. Imagine our colour language has changed. Updating our token library would automatically bring the product up to speed...as long as our legacy products also depend on them.
If our legacy product can't consume the tokens, we're missing out. It's therefore very valuable to ensure our token libraries "think of their elders" when we compile them. SASS variables can be turned into CSS variables for instance, which are more easily consumed regardless of preprocessor choices. Doing that, we've made it possible for our legacy products to share the same core CSS principles as our latest features!
It's harder for the icons (or illustrations) we turned into components but as long as they are simple wrappers around hosted images...well we can expose those raw URLs as well. This enables us to update legacy icons, albeit with more effort.
It's not a miracle solution though. We have our tokens, but what should we do with them?
Making our tokens backwards-compatible is a great first step but it's worthless if nobody goes into the legacy to put them there. That's the expensive thing.
We may have to deal with ancient dependency managers, with templating languages long-thought to be dead. That's tough but manageable.
It'll be harder when we will come across past choices that contradict current UI patterns, choices that we can't simply cancel. There will be whole components or behaviours that are no longer part of "what we do". Basic style principles are tokenizable in an agnostic way, but markup and structure... These are much harder to reduce to variables and they are often deeply tied to technical choices.
Each time we land on one such hurdle, we've found precious documentation material. We have an opportunity to chart the waters of our company's UI debt.
We can make a list of what's wrong, why, and why we're not able to fix the issue right now. We can offer options that would enable us to fix the issue. That list is a tool as well, it becomes our roadmap to UI homogeneity.
This roadmap, like all of them, is worthless on its own. We need to act on it, and we need to prioritize, to refine its content. Which parts of these "debt hurdles" are the biggest obstacles to complete product homogeneity? Can they be tackled atomically (switch out the legacy component for a new one) or do they need heavy refactoring (rewrite a complete feature with the current technical stack)?
That's very valuable UI work, we're lifting the "fog-of-war", but it's only documentation. We need to go back to our earlier "long task" budget and see what fits in there. What can we do in a given amount of time?
There will be humongous tasks. We will find entire crucial features that we can't touch without spending months on painful rewriting, often spending considerable resources understanding old code. We've fallen right back into our technical debt and how it hinders progress. It's a well-known issue with software development, as described by Martin Fowler:
Progress is rapid initially, but as time goes on it gets harder to add new features. Even small changes require programmers to understand large areas of code, code that's difficult to understand. When they make changes, unexpected breakages occur, leading to long test times and defects that need to be fixed.https://martinfowler.com/articles/is-quality-worth-cost.html
To address the necessary efforts, we need to talk with everybody involved, PMs and managers, to figure out our timelines. The small, isolated tasks can be done in batches, delivering complete components replacement sprint after sprint.
The larger pieces though? These will require a longer scope. With all the stakeholders around and our estimates, we can pick the most valuable one to refactor and get to it, bit by bit, with shared responsibilities. Given a year and a sizable chunk of the debt budget, few legacy features can't be made anew.
That opportunity benefits in turns more than pure UI concerns. Such work improves the overall codebase homogeneity, it'll make it easier to iterate on "lost" features, or simply highlight which ones are no longer relevant and can be cut. It's a chance to tackle product feedback as well. Down the line, everybody wins.
This kind of deep product analysis and cataloguing is not easy. Nobody likes digging through the trash, except of course that it's not trash. It's our garden and it needs tending to.
Debt is like weeds. It's fine to let them grow but we can't act surprised when there's no more space for the actual garden. To stay on the garden analogy, we've got tools by our side. Planning is one.
A Design System is an obvious other. Design tokens are a perfect fit for a DS but it does not stop there. Its role is to document what our UI should be and what's our vision for UI homogeneity. It provides teams with clear guidelines and battle-tested components we can easily use to pay back some of our debt. It organises our tokens, our easily-swappable components and our decision-making processes within a team that can support long-term efforts to deal with our weeds.
It's a powerful tool but it's still not enough on its own. The real enemy is bad habits.
We mean well, but we often fail to act on that. It can easily be identified as part of our company's culture, and it's one worth fighting against...for that same company's sake.
Illustrations are made by Greg Dlubacz over at Whoooa.