Death by a thousand paper cuts — User Experience and complexity
Written version of my presentation given at Ruhr.js on Oct 15, 2017
Hello! I am so excited to be here today! Like… REALLY excited. It’s fantastic to see what the team has pulled off, and that I am lucky enough to be spaeking here today. Because, you know, I’ve come home.
I was born and raised in the Ruhr area, in the beautiful city of Duisburg, where it never got dark, and where we could watch romantic skies every day at around 6 in the evening.
Duisburg by the way, lies West of Bochum. A fact that confused me to no end in 1984.
I now live in Oslo which is pretty much the opposite of Duisburg, as you can see here. I am still terrible at skiing though.
I have more than ten years of experience working professionally on the web, and I’ve spent many, many more tinkering, and contributing to open source projects, namely KDE and Rails Girls Summer of Code.
On November 1st, will be joining VIBBIO as VP of Product where I will be working on their platform for video production on demand. (We’re hiring by the way. If you’re thinking about changing jobs, and you’d like to come work with me in Oslo or remote, find me during the break.)
But enough about me, let’s talk about complexity.
When I started my initial research for this talk, I wanted to focus on ways how we who are working on the front-end, could tackle the design challenges that are inherent in complex applications. But the more I thought and read about the topic, and the more I learned in my job, the more obvious it became that I had to look beyond that.
So this is a design-ish presentation that talks about scope and product development.
My career in complexity
I have spent much of my career as a user experience and interaction designer on products that solve problems related to or rooted in complexity.
First at CFEngine, the predecessor of Puppet and Chef, and the mother of all configuration management systems.
In a nutshell, configuration management systems are responsible for automatically keeping your infrastructure correctly configured. CFEngine does that via agents that are deployed onto your servers or other hosts, and which autonomously watch for changes to system files to either change them back or to update your system.
And then, up until recently I led all design efforts at Ardoq, a Norwegian startup bringing a data-first approach to governance and compliance for large enterprises. It provides a tool to collect and analyze information and knowledge spread across an organization about people, processes, and systems, and helps to make sense of all of their interdependencies.
If you’re working in an organization trying to get to grips with the upcoming General Data Protection Regulation, this application might be for you.
In both cases, I was hired as the first full-time designer after the teams had been hard at work for a couple of years. And both products cover a wide range of different usage patterns because there is no single way of approaching those tasks, and because their audience appreciates a generalized solution to their problems.
Also in both cases, we were tackling complex problems to begin with, but I soon realized that also seemingly simple applications pack a punch when it comes to complexity. So I started to take a closer look at the applications that I use every day, to learn, and ultimately, to be able to do a better job, and I read a lot of articles.
“Complex things will require complexity. It is the job of the designer to manage that complexity with skill and grace.” – Don Norman
This is a quote from one of the first articles I read. No pressure.
😱
Given that not all of us work on configuration management systems, how do we get from something that feels straight forward and manageable to something that has grown into something we hardly recognize any more? How can we reverse this, and ideally, what can we do to avoid it altogether?
Complexity is relative
Complexity is a qualitative concept, it has to be always understood in context. What appears to be complex in one context may not be complex in another. There can never be a general right or wrong way of dealing with it.
What’s more, complex tasks remain inherently complex, no matter how we approach them. A lot of what we do every day consists of a range of complex actions, sometimes even spanning several tools and applications. Think of the last time you tried to coordinate an outing with a group of people. How many tools did you use? Three? Four?
Doodle, Slack, Email, Calendar… Still, this does not usually feel particularly complicated or clunky to us because we want to accomplish a task, and all of these tools cover one part of a whole range of actions.
Magic vs full control
To avoid exposing complexity to our users, we may choose to move functionality away from them and automate actions. One example here is the “clarify” function in most photo apps. I personally do not have the slightest idea what it does but it seems to work most of the time, and I suspect that the same result can be achieved by manually adjusting all properties – if you know how to do it.
In other cases, we may decide that it is more important to give people full control – or the feeling of full control – and therefore make them to go through the full range of actions. I imagine being limited by automatic enhancement is incredibly frustrating for anyone who does more serious photo editing. There is a reason Photoshop exists.
Complexity means $$$
And last but not least, products that appear complex because of their more extensive feature set, are usually perceived as superior and therefore more valuable, even though it often is counter intuitive. This is especially true for enterprise software, but we all follow this logic more often than not.
My washer for example has a large dial with I don’t know how many different programs. The fact that I could run quick cycles, and that it would do something different for jeans than for normal fabrics played its role in the purchase process. It felt like I got more value for my money.
💅
We cannot – nor should we really – avoid a certain level of complexity in our products within our specific context. But we should be aware of the trade-offs we’re making along the way to be able to somehow reasonably anticipate their impact.
But there are a couple of things we can do to manage complexity, and to avoid that whatever we’re building gradually turns into a monster while we’re busy building features.
We ❤ consistency
Possibly the most obvious area, and the one we as designers and developers arguably have the most direct control over, is how consistent the product behaves and looks.
Consistency is important because it makes our products predictable in their behavior. The moment we have learned to use an element, we can rely on it following the same pattern even if we encounter it in a different context.
This in turn reduces cognitive load, which is a fancy way of saying we do not have to think as much about what all these things are we see on the screen. We recognize them and consequently are able to use them correctly without much effort.
Build a car
And when executed really well, consistency makes the tool transparent to the task which basically means it will get out of the way. Once we are familiar with how to use a product, we no longer have to spend any mental energy it, and can focus all our efforts on what we want to achieve. A lot of editors do this really well but also tools like Jira if configured in a good way. As well as games. And cars.
Reflect for a moment about how little you think about operating the car when driving as opposed to when you had just started learning.
Maintaining consistency is hard work
But consistency can be hard to achieve or uphold if several teams work on the product in parallel, or if the code base is large and has gone through several stages of design without ever having been fully rewritten.
For these cases, we have pattern libraries, style guides, and design systems about which Sarah is going to talk about later this afternoon, I believe.
But there is one more thing that might jeopardize even the most diligent efforts for consistency: features that are added in isolation. Sometimes, they were initially thought of as standalone modules, sometimes they seemingly solve a purely technical problem, and sometimes they are the result of a glitch in the process. Either way, they feel alien to product, sometimes despite being consistent in look and feel with the rest.
Often this is because they do not really fit anywhere in the information architecture, or because they break a flow and make a part of the user journey feel bumpy. We’ll get to that in a moment.
Take a holistic approach
“What’s the most ignored paradox in software development? Every time you add something you take something away.” – Jason Fried
Every change we make to our products has an effect on everything else. Every addition removes something else.
In his article, Fried argues that we’re constantly forced to make trade-offs between within the tension of these two extremes, and that we need to be diligent about them. Otherwise we might end up in a place where we do no longer recognize what we’ve created.
Adding anything is of course not per se a bad thing. We need to grow and evolve our products.
Sometimes even adding extra functionality to a feature in favor of increased clarity and transparency is a wise choice. At Ardoq we decided to add a manual save button on all forms even though we had auto-saving functionality in place that we used elsewhere in the application. What we thought was more convenient for our customers – saving their input automatically – ultimately turned out to be very confusing. Although it wasn’t strictly necessary, adding this extra functionality made our application easier to use because it behaved in a more predictable way.
Modular code base vs unified interface
While it is often possible, and smart, to decouple functionality and to organize our code base in a modular way, our UIs have to be seen as a whole. Almost all tasks are embedded into a larger journey that spans across several areas of a product, and making changes to any step might break the flow for our users. The larger the application is, and the more we wish for modularity, the more diligent we need to be.
We can make our own lives easier when dealing with the inevitable internal complexity. In almost all cases, we can prioritize flows, and optimize a manageable amount of happy paths. That way we allow our users to efficiently complete their main tasks without us having to constantly worry about everything.
Unforunately, task flows are often interconnected in some form of network rather than behave like linear progressions. This means that in a larger application, paths through the application converge and diverge again at different stages.
In some cases, we inevitably have to trade improved maintainability and testability against some bumpy areas for a small group of people trying to progress through a rare combination of actions. Obviously, we should evaluate these trade offs on an ongoing basis or we risk that the mentioned small group turns into a large one.
Making the easy things easy, and the hard things possible
Trolltech, the original creators of Qt, the C++ framework, used this as one of their guiding principles for API design. APIs obviously expose a different kind of interface to their users but the principle applies to the design of most products that have grown beyond a very narrow scope.
Decisions as constraints
The internal complexity of any application has a direct link to the decisions that went into creating it.
Early decisions set the constraints for all following iterations. This is the case for technical decisions as well as design decisions. Every change, every addition to the product has to fit with what is already there if we do not want to rewrite our applications over and over again.
This is the main reason why it is so much more fun to build something relatively new than taking over something that has grown and lived for a while.
“Features that are simple in isolation can become a contagion.” – Jon Bolt
The more decisions we have taken early on, especially those where we haven’t holistically considered their implications, the less room we have to maneuver. Something that felt like a simple, straight-forward solution to a narrow problem may suddenly set tight boundaries for how we can solve a more intricate problems somewhere else.
This is almost impossible to avoid when we operate in a space where we do not see clearly for more than 6 months ahead. We will have to accept that we need to go back from time to time to “retrofit” earlier work to make it play nicely with later iterations.
To make this less common and painful, we need to start talking about scope.
Thoroughly understand the problem
Getting to the bottom of it all
This is something that has taken me a long time to really understand: Scope and complexity are in many cases two sides of the same coin. Scope ranges from defining the boundaries of your product down to the details of every single feature. There is however no scoping without understanding the problem first.
We tend to be very familiar with the fact that we have to be sure we fully understand a technical problem before we start defining a solution. The same applies to our users’ problems. They often appear clear just to reveal layers upon layers of hidden needs once we start researching. This is why the infamous 5-whys-technique is so popular.
“[…] a solution can only be as good as your understanding of the problem you’re addressing.” – Paul Adams
Intercom recently published an article describing their product development process. In this article, Adams stresses how much time his PM and design teams obsess (his words!) over their customers’ problems. Every problem goes through several iterations until they are confident they really nailed it. Sometimes that means pulling features from beta back into another round of research.
This. research and design phase is where they spend the bulk – around 80% apparently – of their time. Let that sink in for a moment. It makes total sense once you start thinking about it but it’s something that we easily underestimate. Especially, if we primarily look at our work from a perspective of execution and delivery where technical teams rightly focus their efforts.
But ultimately, it’s this hard work that allows us scope and prioritize reliably in the first place.
Ruthless prioritization
Once we understand what this idea really means for the way we work, it becomes increasingly clear that what drives prioritization is not our solutions but the problems we have defined and researched. Because ultimately, we cannot make good, sustainable decisions when we do not know what we’re really trying to solve for our users.
“Any feature that doesn’t help solve problems for your audience should be thought of as a distraction, an unnecessary obstacle that undermines the value of your product.” – Jon Bolt
This is a bold statement calling for some kind of absolute purity. The reality is unfortunately, in my experience, a lot more messy than this.
Maintain a clear vision for your product
I can see clearly now
One of the reasons for it to be more messy, and I had to learn the hard way, is that we have a fuzzy idea of what our product really is supposed to be. If complexity increases with scope, and scope is what sets our product’s boundaries based on our understanding of our users’ problems, then how do we know which problems are within the scope of our work? We cannot possibly solve all problems for everyone in our audience.
The answer is vision. There is no way we can scope work, prioritize problems, or manage complexity without a clear vision for where we want to go with our product. Because only then can we decide how to get there.
But… the client…
Usually, because we’re at a Javascript conference here, we’re in a position where developing and maintaining a vision is someone else’s job. Sometimes it’s a product manager or product owner somewhere in the organization, and sometimes it’s a client.
In either case, I’d like to encourage you to ask about the vision or motivation for the project you’re assigned to. Understanding it will improve the quality of your decision making and consequently of your work, and your life a lot easier, I promise. Because, and who am I telling this, a lot of what we all do all day is based on guesswork.
Conclusion
- Complexity is a qualitative concept that depends on its context
- Complex products are generally perceived as superior and therefore more valuable
- Consistency helps mitigate complexity in interfaces
- Unlike our code, experiences can only be designed as a whole
- Complexity and scope are two sides of the same coin
- Just like scope, complexity needs to be actively managed on all levels, from the inherent complexity of a feature to the complexity of the entire (brand) experience
- We cannot sensibly scope or design for an acceptable level of complexity without a clear product vision
“That’s an exciting design challenge: make it look powerful while also making it easy to use. And attractive. And affordable. And functional. And environmentally appropriate. Accessible to all.” – Don Norman
Resources
https://signalvnoise.com/posts/2917-flashback-every-time-you-add-something-you-take-something-away
https://blog.intercom.com/great-product-managers-dont-spend-time-on-solutions/
http://www.jnd.org/dn.mss/simplicity_is_highly.html
http://www.jnd.org/dn.mss/why_is_37signals_so_1.html
http://boxesandarrows.com/complexity-and-user-experience/
http://feltpresence.com/articles/4-uncertainty-and-scope
http://uxmag.com/articles/the-dirtiest-word-in-ux-complexity