At first, learning to write software often involves struggling with syntax. Once you’ve overcome that, the challenge becomes picking up problem solving patterns. Eventually, you start to think about software design from a higher level. If you’re ready to get there, I might suggest getting familiar with the two-layer approach to software design.
Table of Contents
- An Overview of the Two-Layer Approach
- Mapping the Two-Layer Approach to the Real World
- Leaky Abstractions
An Overview of the Two-Layer Approach
As is probably tiring to read at this point, I’m teaching a new class this semester. As a part of that class, I’ve had to refresh my knowledge of some deeper software design principles. One of which we teach in this new class is the idea of designing software with a two-layer approach (or rather “two-level thinking” as we teach it). I don’t know that this concept is broadly understood in the field, but it’s one that I’ve come to appreciate.
To start, the two-layer approach refers to a way of designing software that prioritizes your focus on two adjacent layers of abstraction at a time. Without really understanding this concept, you probably do this all the time in your own software development: you have some sort of behavior that you want to achieve (i.e., the client view), and you have some sort of implementation to achieve that behavior (i.e., the implementer view).
With the two-layer approach, you don’t concern yourself with the implementation details of the tools you use and your users don’t care about your implementation details. In general, we love this type of design because it allows us to abstract away messy details “under the hood.”
Mapping the Two-Layer Approach to the Real World
While this kind of software development is second nature to me, it’s not always easy for newer folks to understand. Instead, new folks tend to want to understand all of the internal details, and we really have to caution them away from these rabbit holes. One way I try to convey the idea of the two-layer approach is by drawing an analogy to the real world.
With that said, I learned my lesson last time that actions are probably not the best choice for exploring the concept of abstraction. Instead, this time I’ve opted to kick the conversation off with nouns, specifically professions.
To approach the concept, I ask students to think of a profession. To help, I share with the students an example profession, such as baker. Then, I sneak in the concept of the “client view” by asking the students to consider who the clients of that profession might be and what those clients might expect. For example, I like to imagine a client of a baker who only cares about getting their delicious treats. Knowing the expectations of the client, the students are then asked to consider what the client might not expect or care about. In the case of the baker, the client probably doesn’t care about how the delicious treats come to be (though they might care, which is a conversation we’ll have later).
At the same time, I also want students to consider the “implementer view”, so I ask them to consider what the practitioner (if that’s the correct word) cares about. At this point, I hope the students begin to discuss the implementation details of their profession of choice. In the baker example, I like to share the idea that a baker only cares about how they can use their equipment to produce delicious treats. They don’t necessarily care how their equipment works but rather that their equipment behaves in an expected way. On the flip side, they also don’t really care what the client does with the delicious treat. Their world is limited to two layers of abstraction: what the client wants and how to achieve that result.
Having conducted this activity three times in one day, I had some wonderfully hilarious discussions with students. Here are just a few of their responses to the prompts above:
- A lawyer has a duty to their client to win their case. The client does not care how this outcome is achieved (within reason), but the lawyer has to care about certain underlying details, such as the actual law, both in spirit and in letter. However, the lawyer doesn’t really have to care about how those laws came to be, only that they exist.
- An athlete has a duty to their audience to provide an entertaining on-ice, on-field, etc. product (and preferably win). The audience does not care how the entertainment comes to be. In other words, they don’t care how athletes practice or condition. However, athletes absolutely do care about improving themselves in a variety of ways, such as practicing, watching tape, and exercising. Athletes, of course, don’t have to care about the recording technology of their performances or how their gym equipment is manufactured.
- A hitman has a duty to their client to complete a “hit.” The client does not care how the “hit” occurs, just that it does. In contrast, the hitman has to consider the logistics of the “hit.” For example, who is their target, how much time do they have, etc. However, the hitman probably does not care about details like why the client called for the “hit.”
- A doctor has a duty to their patient to treat them. The client does not care how the treatment occurs. Meanwhile, the doctor must consider various details about their patient such as the condition as well as their demographics. The doctor must also use the appropriate equipment. However, they don’t necessarily care how their equipment works or how it’s manufactured.
Leaky Abstractions
One of the things I always stress in my teaching is that there are no hard rules in software development. Despite going through the effort to get students to think using this two-layer approach, I don’t want them to naively trust that tools will always work as they expect. As Joel Spolsky points out, implementation details will almost inevitably slip through the cracks.
An example of this that I love to share is courtesy of a tool that we use in our courses called CheckStyle. Normally, when you use this tool, it provides basic linting functionality based on a style guide. Interestingly, if you cut the internet connection from your computer, the next time you save your code CheckStyle will crash. Why in the world would a linting tool need an internet connection?
As it turns out, we use a custom style guide that requires a custom configuration file. That configuration file, for whatever reason, is stored as a URL. CheckStyle does not ever download a local copy of this file and instead pulls it from the URL every time you save. To a certain extent, I can understand this design. If you make changes to the remote configuration file, everyone will see them. However, as a user, this implementation detail haunts me regularly as it will seemingly crop up randomly throughout the semester. As a result, if I see a student having issues with CheckStyle, I always immediately ask if they’re connected to the internet.
Outside of CheckStyle, there are just so many great examples of leaky abstractions. For instance, it’s completely normal to a software developer to have to consider minimum and maximum values when dealing with numerical data types like integers. But that’s not really normal at all, right? The underlying bits are leaking through (and yet another reason for me to prefer Python which has unbounded numerical types).
Going back to some of our profession examples, there are certainly times where a practitioner would care about certain “implementation” details. For example, a baker might care if their ingredients are ethically sourced. Likewise, a client of a hitman probably prefers that the “hit” never makes its way back to them. From the doctor example, a patient might care if they’re going to get a lobotomy for a headache, even if it does in fact fix the headache.
All of that is to say that implementations details matter. Of course, you don’t want to develop software assuming certain implementation details. But, you have to be cognizant of these details because they’re certain to crop up in unexpected ways otherwise.
An example of unexpected behavior I somewhat jokingly share with students is the following: how long do you think it takes to remove the last digit of a natural number? The answer: it depends how the natural number is implemented. This seems like a simple answer at first, but I then task them with coming up with an implementation. Usually, they’ll pick some list data structure, which begs the question: how long do you think it takes to remove the last element of the list? Very quickly, students start to see that evaluating runtime of a particular method is nontrivial. That’s when I usually bring folks back to the real world and point them to the concept of performance testing.
And since we’re already to the self-promotion part of the article, we might as well call it a day! As usual, if you liked this piece and would like to read more like it, consider checking out any of the following:
- 5 Absurd Ways to Add Two Numbers in Python
- Design by Contract Hinges on Implication
- Maybe It’s Not Okay to Test Private Methods—at Least When Using Design by Contract
And you’re welcome to take you support to the next level by checking out my list of ways to grow the site. Otherwise, take care!
Recent Posts
Teaching at the collegiate level is a wonderful experience, but it's not always clear what's involved or how you get there. As a result, I figured I'd take a moment today to dump all my knowledge for...
It's been a weird week. I'm at the end of my degree program, but it's hard to celebrate. Let's talk about it.