What Developers Can Learn From UX Design
Good UX design sees the web experience from the point of view of internet visitors. In this blog, Gabrielle Gasse says good code UX design sees software from the point of view of users and strives to make it as intuitive to use as possible.
I was lucky enough to be introduced to programming best practices early in my career. One of my mentors organized hands-on sessions where we experimented with test-driven development and refactoring messy code. I have been part of (and led) book clubs that read and discussed software craftmanship and design patterns.
I’ve always liked the idea of User Experience (UX) – seeing software from the point of view of users and striving to make it as intuitive to use as possible. So, completely unrelated to software development (or so I thought at the time), I enrolled in a Graphic Design program to study the visual and interactive aspects of software products.
As you might expect, over the years, considering software from the point of view of users has helped me make better development decisions. But one of the greatest insights I took out of that Graphic Design program was to consider how other developers used my code – after all, they’re the ‘users’ of my code.
Over to you, Steve…
In his classic book on usability Don’t Make Me Think, Steve Krug states his three laws of usability for building and presenting website content:
- Don’t make me think.
- It doesn’t matter how many times I have to click, as long as each click is a mindless, unambiguous choice.
- Get rid of half the words on each page, then get rid of half of what is left.
I quickly realized that the intent behind Krug’s laws applied to other publication modalities, including source code. In fact, most programming best practices can be derived from Krug’s three usability guidelines with some simple tweaks:
- Don’t make me think.
- Finding my way around code should be trivial and unambiguous.
- Always look for ways to reduce the complexity of the code base.
Based on these principles, let’s look at the effect that good ‘code UX’ has on the maintainability of applications and team productivity.
“Programs are there to be read by people.”
Beck Design Rules
Developers often need to modify code without having a full understanding of the program itself. Code bases can change very rapidly, particularly when multiple engineering teams are actively developing it. Keeping up with all the changes as they happen while remaining productive is often impossible.
Given this reality, code should be made so that its intent and usage are self-evident… obvious… self-explanatory – as far as is humanly possible, anyway. In short, a developer without any experience with an application (even a junior developer) should be able to skim through a file and have a pretty good idea of what it does.
Every question we have while reading code adds to our cognitive workload, distracting attention from the task at hand, Krug explains. This is the key rationale behind best practices, design patterns, and coding conventions that have been proven to ease development. Additional practices, like following the SOLID principles and choosing meaningful names for namespaces, classes, methods, and variables go a long way towards making the code more expressive.
The five SOLID principles are:
- Single Responsibility Principle
- Open/Closed
- Liskov Substitution
- Interface Segregation
- Dependency Inversion
This approach aligns with keeping classes and methods concise to help with readability. Imagine reading an article that consists of a single massive paragraph. Without adequate separation – Krug would say “whitespace” – it becomes difficult to follow what is going on. It’s the same with code that requires double and triple readings to understand.
“Code should be made so that its intent and usage are self-evident… obvious… self-explanatory.”
In-code documentation is another tool we should consistently use to provide guidance to the next person reading our code. Modern IDEs display the header comment of classes and methods as we use them in different parts of our software. When they’re well written, these comments can help others avoid the need to navigate to different parts of the application to find out what happens.
Finding my way around code
Carefully chosen naming conventions and in-code comments not only help developers understand the intent of code, but they also act as signposts. They’re analogous to choosing meaningful headers and chapter names in books. When it’s well structured, skimming a book’s table of contents is typically enough to pinpoint a topic. We should be able to do the same with namespaces, classes and method names.
Removing repetitions in code is another technique we can use to make the code more navigable. Specifically, we should aim to apply the SPOT rule to have a single, unambiguous, and authoritative representation of the different pieces of knowledge in our application. This approach eliminates questions about when and where different representations are used and reduces the chances that bugs will be introduced because you’ve forgotten to keep all representations in sync.
Following the SPOT rule and the SOLID principles together helps eliminate side effects between different system components; they make the code more predictable and isolate the logic between tasks. Good separation of concerns should prevent the need to make changes across a whole system when making changes to a module.
Reducing complexity
We know that software complexity increases with each line of code we add to it. In turn, that complexity adds to our cognitive workload, so it’s a good practice to minimize software complexity.
In his article on YAGNI, Martin Fowler warns us against implementing functionalities too early in a system. It can be tempting to do so in anticipation of a feature that’s on the roadmap. However, this often adds complexity to our systems – complexity that must be maintained until the feature is fully developed and released. And, with ever-changing software requirements, it’s highly probable that the future feature will be abandoned or will change in scope such that what was initially implemented will require substantial modifications.
Putting it all together
Let’s assume that the relative effort to understand and use disorganized code is about the same as writing code that’s easy to understand. The latter is more rewarding because the time spent writing well-structured code is done once, whereas the time required to understand messy code is spent every time a programmer works on that software. That’s an expensive and frustrating way to develop software that’s relatively easy to avoid by ‘not making them think’ so that they can instead think about what matters: quality code.
See for yourself how xMatters gets messages through during incidents.