Abstractions and Models
Two common words we use in software development are abstractions and models.
A search of the company Slack returns countless matches:
“It turns out that our abstraction for verification checks was right and consistent regardless of what verification type it is.”
“Doesn’t matter to me as long as we’re settled on polling state as the right abstraction.”
“Are we missing a model between inquiries/verifications/cases and accounts?”
Abstraction has a specific definition within software engineering. In object-oriented programming, abstraction is one of three central principles (along with encapsulation and inheritance). Its main goal is to handle complexity by hiding unnecessary details from the user — a tool I as a product manager quickly adopted.
When you’re building something, you start with the big picture and fill in the details. Abstractions and models allow us to represent important parts of the software as more general ideas and illustrate ambiguous parts of software in other ways that we already understand. Put simply, they help us understand the big picture and reduce the complexity of details.
When used correctly, abstractions and models help us align and build faster. But when used incorrectly, it slows us down and creates confusion.
As an aside, the two terms are used interchangeably even though they shouldn’t be. We’ll unpack those nuances another time!
To illustrate how we use abstractions and models, let’s build some software that allows customers to create online surveys.
The following diagrams are NOT meant to be actual mockups of what a user sees — they’re illustrations of how the sum of the parts make up the product.
To start, we know nothing about how our product works, which we represent as a literal black box.
First, we realize the need for some “thing” where users can fill out the survey, and another “thing” where customers can see all the survey results. Let’s call them interface A and B, respectively. We don’t know what each interface will look like or how it works, but we have a general understanding of these interfaces and can start filling in details from these abstractions.
Next, we break down the two interfaces.
For interface A, we know a survey is made up of a variable number of questions. We’ve also heard from customers that the customizability of the survey’s appearance is important to increase survey completion rates, so the interface also needs to reflect the visual design choices of our customers as a theme.
For interface B, we know customers want a view that shows each users’ responses to each question. We also know that some customers will want to filter the responses in different ways that we can’t support in our MVP, so we’ll have an export that dumps all the response data into a CSV file.
There’s almost a recognizable product here — just have a couple more things to flesh out!
Our surveys need to collect all kinds of information, so customers need to add different types of questions (e.g. multiple choice, short answer, file upload).
There are also a lot of different ways customers could potentially customize the appearance of the survey, but let’s start with text color and font family.
Now that we know more about how our product works, we can revisit the abstractions known as interfaces A and B.
Interface A collects information from users through a set of questions, which models a similar pattern of how we fill out physical documents, so we call it forms.
Interface B displays all the information that was collected by the above forms, so we call it responses. Another option is “answers”, but that more closely models questions rather than forms.
We excitedly start building the new product, but soon realize there’s a missing abstraction. Customers need another builder interface where they can actually create the surveys!
The builder interacts with questions and themes of the form, but also introduces potential new abstractions like templates that enable customers to pick pre-built surveys (e.g. event registration) instead of creating them from scratch.
As we see in this example, the use of abstractions represents new ideas that are loosely defined. We can talk about these concepts in generalities without getting bogged down in specifics that aren’t helpful until we’re aligned on the higher-level assumptions.
So where does it go wrong? Unsurprisingly, we tend to get in trouble when we’re not specific enough.
More general = more open to interpretation
Words and phrases have multiple definitions that also depend on the context in which they’re used and who’s doing the interpreting (e.g. a product manager should understand the nuances of their product better than a salesperson).
When we hear the same word used in a sentence, I may use one definition while you use another. And when we specifically use the words “abstraction” or “model”, we need to perform two transformations:
- What is the concrete concept that’s being referred to as the abstraction?
- Are there multiple definitions? If so, which one is it?
Hopefully in most cases, we ask each other and confirm our understanding is the same, but that’s not always the case. Deviations in our understanding of complex ideas lead to very different outcomes over the course of many months of back-and-forth.
To avoid this problem, I’m constantly asking for clarification of concepts in conversations with engineers, designers, and other product managers!
The audience needs the details
While it’s common for technical folks to get way too in the weeds with non-technical audiences, it’s just as easy for non-technical folks to talk in extreme generalities with technical folks.
Most developers I’ve worked with prefer a bottoms-up approach, in which they explore something in a lot of detail before they start to understand the higher-level abstraction. It makes sense when I think about how code has to handle every explicit detail — the computer doesn’t infer anything.
A placeholder that never gets defined
Abstractions can end up as placeholders to be updated at a later time but never do. The developer has enough context to understand the intent of the placeholder, but the user doesn’t. There’s always a next layer to the abstraction that you can peel back to make the product easier to understand.
When I write product specs, I specifically avoid using the words abstraction or model, and include as many examples and different explanations/analogies for core concepts as possible. I want to make sure everyone who reads the spec has the same understanding of the problem and solution afterwards.
Whenever I hear the actual words abstractions or models — sometimes from yours truly — my spidey senses tingle. If I don’t interject immediately for clarification then, I’ll write a note to follow-up later.
More often, we generalize concepts with different terms and we should ask ourselves:
Is there a more specific description we can use here? If not now, when do we revisit it?
Who’s the audience? Do they need more or less detail?
Using abstractions and models is an extremely powerful tool in software development, but we need to be mindful about how we wield it.