I often like to look at other fields for ideas that can improve how I approach my work. Different fields often emphasise aspects of a problem that receive less attention in our own, and learning from them can broaden our perspective. Product design is one example. It offers useful ways to think about the experience of people using what we create: which actions are available, how people discover them, and whether things behave as expected.

These questions matter in software engineering, too. Even as someone that works primarly on backend systems, I design interfaces that other engineers need to understand, use, and extend. Considering their experience helps me build software that is easier to work with and maintain. In this article, I’ll introduce a three product design concepts: affordances, signifiers and consistency; and explore how we can apply them to software engineering. This is only a brief introduction, but I hope it gives you a useful starting point and encourages you to explore the field further.

Affordances describe the actions an object makes possible for someone interacting with it. This concept comes from psychologist James J. Gibson, but Don Norman helped popularise it among designers through his book The Design of Everyday Things.

For example, a door handle affords gripping and pulling.

affordances An affordance is the quality or property of an object or environment that defines its possible uses and signals how a someone can interact with it.

In software, we can think of an interface in a similar way, in that it determines what another developer can do. An interface that exposes only read operations lets developers inspect data without modifying it through that interface. Adding an operation to update the data creates another possibility for interaction. Designing an interface means choosing which actions to make available.

Making an action possible, however, does not necessarily make its intended use clear. Signifiers provide the cues that communicate where and how to act. A door handle’s shape and placement can suggest pulling, while a flat plate signals where to push. If a door needs a “Push” sign because its handle invites pulling, the sign is compensating for a misleading design.

norman-door A Norman door is a door whose design makes it unclear how to open it. Here, the “Push” sign compensates for a handle that suggests pulling. Much like code comments that explain what clearer naming, structure, or design could make obvious.

Names and type information serve a similar purpose in code. An operation named save_file communicates more intent than one named process. A parameter named timeout_in_seconds makes its unit explicit. These cues help engineers understand how to use an interface without having to inspect its implementation.

Question

Beyond naming, what other cues help developers understand how to use an interface? Can you think of examples from familiar software design patterns or other techniques?

Consistency helps people build a mental model they can apply across a product. When similar controls behave in similar ways, users can draw on familiar interactions to understand unfamiliar ones. These consistent patterns help communicate a coherent conceptual model: an explanation of how the system works that helps people predict its behaviour.

We build mental models of codebases in much the same way. If similar search operations handle missing results consistently, we can learn that convention once and use it to predict how another operation will behave. If some raise an error while others return an empty result without a clear reason for the difference, we need to investigate each operation separately. Consistent naming, behaviour, and design patterns help us understand unfamiliar code using what we already know.

Affordances, signifiers, and consistency are just a few examples of how product design concepts can help inform software engineering. They encourage us to consider which actions our interfaces make possible, how clearly we communicate their intended use, and whether developers can carry their understanding from one part of a system to another.

Product design concepts won’t always translate perfectly to software engineering, but I believe some of the perspectives they offer is valuable. Instead of just making our software work, we should continuely ask ourselves how we can shape the systems and codebases in such a way that other engineers can discover its capabilities, understand its behaviour, and make changes confidently. Making those questions a bigger part of our software design process can help us become better engineers.

If you’re deciding what to learn next, I’d highly recommend spending a bit of time exploring product design. It has definitely changed how I approach my everyday work and helped me do better engineering. It might just do the same for you.

Taking Concepts Further

Software engineers do draw on product design to create intuitive user interfaces. I find it interesting that we seem less likely to bring the same thinking to the systems and code we create, or at least to discuss our systems and code using the same language. To me, that feels like a missed opportunity since many of our existing engineering practices already reflect these ideas.

KISS - Keep It, Simple, Stupid, for example, shares product design’s emphasis on simplicity, encouraging us to avoid unnecessary complexity that makes code harder to understand or use. Encapsulation can support that goal by hiding implementation details, reducing the cognitive load on engineers using a component. At the same time, the operations it exposes shape the component’s affordances, determining what those engineers can do with it.

Using the language of product design can help us make these connections explicit and apply familiar engineering principles with greater attention to the experience of engineers working with our code.