RubyConf 2021

The Intro to Abstraction I Wish I'd Received

The Intro to Abstraction I Wish I'd Received

by Stephanie Minn

The video titled "The Intro to Abstraction I Wish I'd Received" presented by Stephanie Minn at RubyConf 2021 explores the fundamental concept of abstraction in software development. Minn shares her personal journey of understanding abstraction and how it significantly impacts coding, particularly when transitioning from an early to a mid-level developer. She emphasizes the essential role of abstraction in simplifying complexities in programming and highlights its presence across various disciplines, including technology and art.

Key Points Discussed:

- Definition of Abstraction:

- Abstraction is the process of generalizing concepts into manageable ideas, aligning with the notion that it filters information to focus on what’s essential for a specific purpose.

- Examples of Abstraction:

- Practical examples include maps and user interfaces that simplify complex underlying systems, allowing users to interact without needing to understand every detail.

- The Importance of Naming and Structure:

- Effective naming of functions is crucial for abstraction as it helps encapsulate detailed functionality while hiding complexities.

- Personal Anecdote on Struggling with Abstraction:

- Minn recounts her experience on a client project for a public energy company, where she initially misapplied the concept of abstraction by overcomplicating the code structure with multiple service classes for report generation.

- Refactoring Experience:

- The necessity of refactoring was highlighted when the initial codebase became too complicated for other developers to navigate, leading to an "assembly line" approach that streamlined processes and improved clarity.

- Common Issues with Abstraction:

- Overly complex abstractions can reveal too much or too little information, making code harder to understand or use, leading to increased dependencies and coupling.

Conclusions and Takeaways:

- Abstraction should simplify, generalize, and make code flexible, and developers need to be mindful of when their abstractions no longer serve their purpose.

- Reflecting on past code positively can foster an environment of growth and understanding, encouraging developers to continuously improve their abstraction strategies.

- The process of developing and adjusting abstractions is ongoing, and developers should remember it's a learning journey fueled by communication and collaboration.

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!