00:00:11.599
Hello, this talk is called "The Intro to Abstraction I Wish I'd Received."
00:00:14.400
A little bit about me: my name is Stephanie Minn, and I'm a developer at Tandem,
00:00:18.400
a small software consultancy based in Chicago, Illinois.
00:00:31.039
I wanted to give this talk because, as I transitioned from early career to mid-level,
00:00:34.239
I noticed I had internalized a lot of concepts in software development without ever really receiving an explicit education in them.
00:00:39.680
One day, I was pairing with one of our apprentice engineers at work, hunting down the source of a bug.
00:00:46.879
He seemed to struggle with reading the existing code, spending time in lines that I knew weren't relevant to our bug.
00:00:51.199
He felt the need to understand every line of every function, even when the function's name spoke for itself.
00:00:55.520
I wanted to respect his process, and I recognized that knowing how to parse out what's important from what's not comes with experience.
00:01:06.799
When I tried to coach him on why we didn't need to go through every line, I found it impossible to explain.
00:01:12.480
I didn't have the right words. It turns out I couldn't explain my intuition because I didn't have the language for abstraction.
00:01:23.040
I didn't even really understand it myself. I could tell you about a time I used duck typing, but not why I thought I understood the design principles of SOLID.
00:01:30.720
Just because I had read a blog post didn't mean I fully comprehended how to use them. Virtually all of software development boils down to this intangible concept of abstraction.
00:01:45.759
So what is it, and what if you don't pick the right one? Whether you're looking to dip your toe into design patterns or to refresh your memory on this topic,
00:01:50.280
we'll go over exactly what abstraction is in both life and software development. I'll also tell you about my experience fighting a troublesome abstraction and realizing I needed to refactor.
00:02:11.200
At the end, we'll explore a few ways your abstraction might not be quite right and how to identify when you've reached that point.
00:02:31.200
I'm still on my journey in learning how to write a good abstraction, and I won't necessarily be able to tell you the magic when you need to make your code work.
00:02:37.200
But at the end of this talk, I hope you'll be inspired to imagine new abstractions for your code to take shape instead of feeling confined to the limitations of your current software design.
00:02:46.400
Let's get into that very first question: what is abstraction? At a basic level, it's the act of generalizing things into ideas, qualities, or characteristics.
00:02:52.640
According to Wikipedia, conceptual abstractions may be formed by filtering the information of a concept or an observable phenomenon, selecting only the aspects which are relevant for a particular subjectively valued purpose.
00:03:10.000
Artist Christoph Niemann says, "Abstraction, for me, is this idea of getting rid of everything that's not essential to making a point." We'll learn more about him and his work a bit later.
00:03:22.080
So to recap: abstractions deal with general qualities or characteristics, they filter information for some purpose, and they're used to make a point.
00:03:53.200
A map is a great example of abstraction. It's a general representation of a place that exists in reality. It provides relevant information about significant landmarks like mountain ranges or rivers to help orient you.
00:04:11.680
But it also filters information; it wouldn't depict every single rock or tree in the area. You don't need all those details to get to where you want to go with the map.
00:04:26.479
Rather, it would probably be mental overload if everything were included, and the map would get outdated quickly. Some information has been included while other information has been purposely omitted.
00:04:51.680
Humans developed this idea of abstraction to create, understand, and manage complex systems. In fact, abstraction is used as a tool in all of these disciplines: linguistics, music, neurology, math, and computer science.
00:05:06.240
Let's talk about one of those disciplines—art—, which I think is a great entry point into this concept for a lot of folks.
00:05:10.560
While sometimes abstract art is a little too over my head, it's safe to say that artists have spent a lot of time thinking about abstraction in order to create representations of the world that speak to us as human beings.
00:05:27.360
One of those people is Christoph Niemann, who I quoted earlier. He's a German illustrator and designer who for years contributed to The New York Times blog 'Abstract Sunday.' He's featured in the Netflix documentary 'Abstract: The Art of Design,' where he says about his art, "Every idea requires a very specific amount of information."
00:05:48.480
Sometimes it's a lot—a lot of details, a lot of realism. Sometimes it's really just the one line, the one pixel. He also created this graphic to explain his process of developing the ideal visual abstraction, where he represents love.
00:06:13.280
On the left, we have a very realistic depiction of the human heart with blood and veins. It's maybe a little too gruesome for representing a concept that's supposed to make us feel warm and fuzzy inside.
00:06:23.120
On the right, we have a red square; it could mean anything: is it a brick? Is it a stop sign? And in the middle, we have the symbol of love that we all know—the symbol of the heart, the graphic shape that's two humps at the top connecting to a point at the bottom.
00:06:40.800
Enough about art! How is abstraction used in technology? Pretty much everywhere. At a high level, we might have a user interface, which is really an interactive abstraction of code for us to navigate.
00:06:54.480
Take a button on a website: the user doesn't need to know that when they click that button, a request is sent to the backend and a new record is created in the database. They simply need to know that when they click 'Sign Up,' they'll have created an account.
00:07:18.720
As developers, the languages we work in are abstractions as well. Ruby is a high-level language with a lot of it built in, such as its library methods.
00:07:28.720
When I need to check if an array is empty, I can call .empty on it instead of having to write out the full logic that checks its size to be zero.
00:07:37.680
Even C, the lower-level language that Ruby is written in, is abstracted. We can all thank abstraction for not having to write code in binary.
00:07:55.360
One of the things I'm sure many of us enjoy about Ruby is the simplicity of it. Abstraction helps us manage complexity by stripping away the things we don't need to care about, making things easy to work with.
00:08:01.599
It also allows us to generalize and make code more flexible. Naturally, the job of software development is ripe with abstractions; every object we create in code serves to represent something.
00:08:20.240
What's amazing about our job as programmers is that we get to make these things up. So I mentioned the Ruby array method empty earlier; let's dig into why a function is an example of abstraction.
00:08:37.680
At the hyper-local level, when we create one, we have to name it something that explains what it does while hiding its internal mechanisms. When you call a function, you don't necessarily need to know every line of its implementation.
00:08:54.960
Its name, inputs, and outputs should give you enough information about how to use it. We also try to make our functions reusable by generalizing their behavior.
00:09:10.000
But of course, we've all encountered a function that doesn't behave as expected, where its name is confusing, ambiguous, or just plain wrong. Turns out the struggle of naming a function is also a struggle of abstraction.
00:09:24.000
The same goes for developing classes and interfaces, both explicit and implicit. Have you ever started on a new feature without any conceptual existence in the code base, created a new file, and just stared at it blankly for a while?
00:09:42.720
Most of the time, we have enough intuition and information that our abstractions aren't blatantly wrong or totally off base, but like the abstract meter, we might create representations that are too hot or too cold.
00:09:58.000
Sometimes, you won't get it right on the first try, and that's okay. Creating the perfect abstraction might be impossible because to do that, you'd have to be able to predict the future and know exactly what it holds for your application and code.
00:10:10.240
Instead, we have to deal with uncertainty as part of life and as part of software development.
00:10:25.600
Now, let me tell you about my experience having written an abstraction that didn't end up working for me. Last year, I was working on a client project for a public energy company.
00:10:46.320
We were building a custom enterprise web app in Rails to notify their customers ahead of a planned power outage. The app hooked into a third-party system, kind of like Twilio, that notified customers through call, text, and email.
00:11:09.440
Additionally, the application had to create a report with details about exactly who received communication and if they acknowledged receipt. The report also broke down the data to answer questions like how many hospitals and schools were affected and how many customers in a given zip code were notified.
00:11:31.200
Up until then, I could confidently write code to fulfill an obvious task at hand, but I had less experience with more ambiguous work.
00:11:56.160
When I was tasked with developing the reporting feature that returned an Excel file with multiple sheets of data, my instinct at the time told me to make a service class for each sheet.
00:12:24.520
Service classes, or service objects, are plain old Ruby objects used to execute a single action within your domain. They're typically used in a Rails app to extract procedural code from a controller that doesn't quite fit into a model.
00:12:54.080
At the time, I was comfortable with this level of abstraction; it made sense to me, and frankly, I couldn't really imagine doing it any other way.
00:13:09.760
Only later would I realize that these sheets were more related than they were different. So I had a bunch of these classes that I dubbed 'sheet generators,' and they each did slight variations of the same five steps.
00:13:42.480
First, it took in the results from the third-party notification system that made phone calls to customers. Then it also queried the database for additional information about those customers, like their account number.
00:14:03.040
Then it did some munging and filtering according to the requirements of the specific sheet and wrote the final data to the report along with styling and formatting for the sheet.
00:14:30.560
For several months, this code was fine; it did the job. But soon enough, someone other than me had to touch the code, and it was like they were looking at a bunch of Rube Goldberg machines.
00:14:44.320
When you looked at the invocation of these service classes, it was a bit of a black box. What does a sheet generator really do behind the curtain?
00:15:04.880
Naturally, it was a mess. When something went wrong, you'd have to parse through the whole thing to figure out what broke.
00:15:32.000
Let’s say the report was missing some information: was there a piece of data that was nil, or did something get overwritten in formatting?
00:15:49.439
The bug reports and change requests started rolling in, and even I started to dread working in this code. Once the complexity of our sheet generators came back to bite us at least three times in a row, we decided to take the time to refactor.
00:16:05.440
When we embarked on this refactor, we focused on rethinking the code structure. Instead of Rube Goldberg machines, we imagined if our system looked more like an assembly line.
00:16:26.280
This diagram is not exactly a technical representation of the code we ended up with, but it serves to visualize— you guessed it—additional layers of abstraction.
00:16:45.680
The first step of our assembly line is processing the data into something usable. In our case, it turned out that the data ingested by all of our sheets was the same, which is great.
00:17:05.040
We were able to dry it up a bit by creating a new report data class with a more meaningful structure and then sharing an instance of that class with all the sheets.
00:17:28.240
Now, the source can be swapped out should we use a different third-party notification system in the future. Once we had a better object housing the original data, we pulled the layer of domain logic that figures out what to do with it for the specs of each sheet.
00:17:44.960
Here's where we started to have variation in implementation details, but our abstraction shares the same idea of returning something processed according to business requirements.
00:18:03.040
The third and final layer was presentational, with no more mixing of sheet styling with detailed specifics to the customer model.
00:18:18.560
One thing I personally didn't realize at the time was how frequently those reporting requirements would change. Our client had to submit these reports to state regulatory agencies, whose laws were created in response to the ongoing wildfire and climate crisis.
00:18:35.200
So of course, these regulations were changing constantly. No wonder the client came to us with multiple requests for new data to add to the report.
00:18:53.680
I found myself repeatedly trying to wrangle that original code to do something it wasn't built to support. Remember we talked about how abstractions should simplify, generalize, and make code flexible to change?
00:19:15.360
I think our new approach got us closer to those goals. Like I prefaced at the beginning of this talk, I can't tell you how to come up with the perfect abstraction.
00:19:46.400
They're very much situational, and as with most things in software development, there's no one-size-fits-all solution. But let's talk about some of the things that might be causing you trouble.
00:20:01.360
First of all, your abstraction could be doing too many things. This point was illuminated pretty clearly in my client project anecdote.
00:20:29.160
I recognize that this wasn't obvious to me when I originally wrote the code, but the idea of a sheet generator seemed like a single responsibility in my early career brain.
00:20:42.000
Once I and others had to contend with it, though, it became clear that it was doing too much. It can also reveal too much; remember the abstract meter and the gory bloody heart?
00:21:03.760
This creates room for complexity. Sandy Metz writes in 'Practical Object-Oriented Design in Ruby': "When each object exposes too much of itself and knows too much about its neighbors, the excess of knowledge results in objects that are finely explicitly and disastrously tuned to do only the things they do right now.
00:21:27.840
To reuse any of them or to change one thing, you must change everything." What revealing too much means in this context is exposing too many methods that could eventually be misused by yourself or other developers extending the existing code.
00:21:40.480
Without some guidance built into the abstraction, objects can easily become a tangled mess of communication.
00:22:04.000
In this diagram, the circles represent objects that invoke each other without abandon, to the point where they no longer have the benefits of being standalone components.
00:22:33.600
To replace one of them would mean revisiting each of its neighbors to ensure that all the dependencies still work. On the other hand, sometimes an abstraction can reveal too little.
00:23:00.080
An easy example here would be using an API that doesn't provide enough options for you to customize to your needs. Even after you dig deep inside the library's GitHub repo for more information.
00:23:18.040
In the reporting feature I mentioned earlier, our project used a Ruby gem called Spreadsheet for Excel file generation.
00:23:38.720
The main abstraction in this gem was the idea of rows in a spreadsheet; it implemented a row class that subclassed Ruby's Array, and it was the main way to insert or update data.
00:23:55.760
One thing that I found missing was a way to insert data at the column level because our client specifically requested data organized into columns on the sheet.
00:24:07.440
Unfortunately, the gem didn't support the idea of insertion within the column classes' interface. Perhaps this was an intentional part of the abstraction to encapsulate data manipulation to rows.
00:24:23.760
I was able to find a wonky way around it, but still, as a user of this API, I wished for an easier way to do what I wanted.
00:24:38.880
Worse still is when an abstraction does something that it's not clear about doing.
00:25:00.160
You might be following along some external documentation, but when your code actually calls the API, you encounter additional behavior that's completely unexpected.
00:25:27.680
And it's so obscured that you only discover the issue when you stumble upon a random line of code in the stack trace.
00:25:54.640
You can also have too many abstractions. Sometimes when you create them to dry up code, you also create indirection that leads someone unfamiliar with the app to hop around the code base.
00:26:17.280
This complexity makes it difficult to get a holistic understanding of the application.
00:26:42.400
Until I started to properly learn about abstraction, I didn't know that you could just change the way the code existed to better suit your needs.
00:27:01.920
What I did know deep in my bones was the frustration of working with code that wouldn't do what I wanted it to.
00:27:18.880
And now I'm going to share how to identify some of those pain points.
00:27:39.680
Having a hard time explaining communication patterns between your objects could look like passing around too many arguments—especially ones that are tenuously related—or having to go find where a function is invoked to get context.
00:27:54.560
When you look at the definition, you don't know what the arguments mean. Second, you might have a hard time figuring out what a method does because of extensive manipulation or obscure side effects.
00:28:07.280
Or it’s just not clear how objects relate to each other; either you can't figure out how something is used, or some objects are so coupled that you can't untangle them from each other when explaining the system.
00:28:24.640
In general, anytime you're experiencing pain writing tests, that's also usually a sign your code might be poorly designed.
00:28:49.840
You might be spending a lot of time and space in your tests on setup—think things like fixtures that require a lot of associations for certain conditions.
00:29:10.240
Similarly, you might be frustrated with all the responsibilities that need to be mocked in your tests, especially side effects separate from your inputs and outputs.
00:29:24.080
Speaking of mocks, I want to shout out my friend Emily Giulio, who's speaking at RubyConf about this very topic, so be sure to check out her talk to learn more.
00:29:41.200
And lastly, you might struggle with unit testing if you find yourself writing complex test cases with long sentences describing what they do; there's probably opportunities to simplify.
00:30:02.640
At this point, you've probably been fighting with your abstraction because you're trying to do exactly this.
00:30:12.840
Sometimes, this looks like going back and forth between many files or getting lost following a process just to find where to make a code change.
00:30:27.680
You might also find yourself changing files all over the place, especially hard-coded values or parameter lists.
00:30:37.520
Similarly, a change might break tests in many unexpected places.
00:30:52.560
While our test suite is meant to catch these things, a lot of failing tests might be a sign that your code has too many dependencies.
00:31:11.440
Finally, feeling like the code wasn't built for the ask, if your gut reaction to a new request is stress, that's probably the ultimate red flag that your abstraction isn't quite right for the direction your application is headed.
00:31:24.240
When you feel these symptoms, I'd encourage you to revisit your abstraction and ask yourself how it might not be working for you anymore.
00:31:41.040
What is complex about it, or what could be missing? That said, you don't have to completely rethink everything; even adding a single layer of abstraction can ease some of that friction.
00:31:58.560
What would it look like for that service class to be more object-oriented? What if a giant hash of data had a more intentional interface?
00:32:19.360
Earlier, we talked about how artists use abstraction in their work. Like them, you get to use it as a tool alongside your imagination.
00:32:36.480
You get to create abstractions that only your code can.
00:32:54.960
I want to end on a note about gratitude and self-compassion. We should have these feelings for our previous code; if it served you well for any amount of time, then it did its job.
00:33:20.240
Code is constantly being rewritten because of the changing nature of the world we write it for, and frankly, I wouldn't have been able to come up with better abstractions unless I had gained an understanding of what my previous ones lacked.
00:33:43.040
Maybe next time, I'll better follow those SOLID principles, but until then, I can keep an open mind to new abstractions for my code and be content with where I am in my journey.
00:34:06.720
Here's a list of resources for things I referenced in my talk. I'll be dropping my slides in the Discord for you to explore more.
00:34:36.160
And that's it! Please feel free to find me in the chat; I'd love to know your experience understanding abstraction for the first time,
00:34:42.960
as well as any other tools or ideas for how to teach this concept to others. Thanks for listening, and I hope you enjoy the rest of RubyConf!