Talks

SOLID 101: A Review for Rubyists

SOLID is an acronym that tries to capture the first 5 principles of object-oriented programming and design, as enumerated by Michael Feathers and Robert Martin. Most Rubyists are probably familiar with one or two, but do you know what the rest are? Let's review them, see them in action, and learn how they can help us create maintainable, extensible, and comprehensible software.

Ancient City Ruby 2016

00:00:00.800 Okay, can you all hear me? I just sold. I need to project quite a bit. Is that better? Higher? I could talk more. Oh, sweet! I'm a yeller, so this is going to be great. I have 54 slides and eight t-shirts, so let's go. For those of you who don't know me, my name is Kerri Miller. I work for a startup in San Francisco called Grit Hub. We turn pull requests into annoyances for committers. Now, I do work at GitHub. I'm a Level 5 Application Engineer, which means I can cast unlimited private repos three times per day, and I have shell access.
00:00:12.269 This is my dog. I guess it's tender love to put his dog or his cats in things. I can have my dog. My dog is actually a licensed jurisprudence doctor; he can answer all of your legal questions. His fees are quite reasonable. If you were here last year, I gave a talk about Shakespeare and Postgres. Does anyone here remember that talk? Did anyone actually see me talk last year? You're still in the room? Awesome! That's great! I'm not going to do any Shakespeare this year, but I did want to share some of my favorite Shakespearean insults with you. Is that okay?
00:01:18.840 Okay. I will bite my thumb at them, which is a disgrace if they bear it. That is Shakespeare's way of saying, "Come at me, bro." "Thou art to boil a plague sore and embossed carbuncle in my corrupted blood," which is Shakespearean for "Yo! Step off, you blood-sucking leech! You scallion! You rampallion! You fools-telling-arian! I'll tickle your catastrophe," which is basically Shakespeare saying "You suck! Everyone knows you suck, and I'm going to laugh at you when it all comes crashing down." Of course, one of my favorites is from Titus Andronicus; mom jokes are timeless.
00:02:04.130 I'm a moonbat. I went to Goddard College in Plainfield, Vermont, where I took such core classes such as Star Trek and The Existential Dilemma. I have nine credits in dishwashing—literally nine credits. Goddard is an experiential college where we do a lot of design-your-own-majors, including explorations such as the meaning of the joke. In the little college town where my college is located, there's a joke: 'Why did the Goddard student cross the road? To get credit.' When you get to design your own classes, you essentially find a local expert in the community or, nowadays online, to kind of mentor and guide you in your area of research. You also get to pick your own reading list, which leads to some really interesting bibliographies at times.
00:02:31.790 For a brief time, I was a performance production major. I wasn't actually a computer scientist, but for a brief period, I was a historian. That interest in history still lingers to this day. In fact, I did quite a bit of study into American history, and I'm following that up now with a life project of reading biographies of all the American presidents and the founding fathers, which is really super interesting. So, if you ever get stuck in one of those trivia game shows and you need to know about Martin Van Buren, call me! Call me! I'm your dial-a-friend. You want to hear some Martin Van Buren trivia? Interesting story: Martin Van Buren was from New York and the only American president whose first language was not English. He was born to Dutch parents and spoke Dutch until the age of 14. He's also the first president born within the United States. Every other president up to Martin Van Buren was born a British citizen.
00:03:40.690 Right? Our founding fathers are a really interesting bunch. You find out a lot of things about them over time that kind of burst your bubble about thinking of them as great men. Washington was kind of a crap general; he lost almost every single major pitched battle he fought against the British because of his poor technique. He thought, 'Wow, these American revolutionaries can go toe-to-toe with the best the British Army can hire.' Not so much. We hold these truths to be self-evident, that all men are created equal—not quite. He actually held his own children in bondage with Sally Hemings until his death, when he emancipated them. My favorite, though, is Ben Franklin. Have you ever read his autobiography? You find out, wow, he loved the ladies, and the ladies loved him!
00:04:37.420 This is actually the cover art of his autobiography, so if you go pick it up in a store, he was well-loved in the salons of France, and other founding fathers criticized his lascivious and lecherous ways. Which is funny because if you Google Ben Franklin, you get a lot of images like this that still pertain today, including cosplay. But you know, these founding fathers had a lot of really great ideas, and their inspirational quotes, for example, will often come down to us like this: 'I think like anything that anybody ever says'—it's put over a piece of beautiful nature photography—it’s kind of automatically something awesome. So, I gave it a try! You can retweet that, hashtag it, whatever you want! It'll be at a poster store at the mall near you.
00:05:12.340 This is really common, and I see it a lot. When you're at that store in the mall—what is it like? Notions? What's the name of that store that sells all the posters? I haven't been in a mall in forever because I'm the dirty hippie. Unfortunately, though, you can't always trust the things that people say, especially smart people, because a lot of times, we assign wisdom to people that we think are greater than ourselves and we especially assign wisdom to concepts that we don't quite understand. The truth is that people often disguise their discomfort with a concept by using big words or confusing language. I firmly believe that, especially in technology, a person's understanding of the problem is inversely proportional to the size of the words they have to use to explain it.
00:06:01.970 Most of the time in technology, there's a whole bunch of words that people use that I don't think they fully understand, like 'idempotent,' which comes up all the time in every computing conversation I ever hear, yet people can’t really define it. The one I want to discuss today, though, and that I’m a huge fan of, is actually the SOLID principles. SOLID is an acronym; if you can't guess, it stands for these five principles. These are the first five principles from a series of papers written on object-oriented programming that have been collected over the years, which sort of set out a manifesto or guideline. What I love about these principles is that each one builds on the one that came before it, and they serve as a rough guideline or suggestion about how you want to structure objects, the relationships between objects, and how they interact in your system.
00:07:10.210 These ideas are a roadmap; they are a guiding philosophy to follow when you don’t know what else to do because like everybody, we all want our software to function cohesively. You know, like everything has its individual life but also works together in harmony. So, object-oriented principles... I think it's really important to talk about what objects are to begin with. What do objects mean to you all? The first person to answer gets the t-shirt!
00:07:47.010 Sam says 'Objects are packages of state and behavior.' That's pretty good because you’re clearly an expert. Sam, what shirt size do you want? I can buy this audience a medium. Sorry, I just like closed my eyes on that one!
00:08:03.260 But you don’t have to be an expert. You don’t need a computer science degree to look at objects and their relationships—some of these concepts. Unfortunately, a lot of these papers are really dense, and it really helps if you have that kind of background. I've admitted that I'm not a computer scientist. Is anyone here a computer scientist? Or does anyone have that education? Like three people? Okay, that's fine. Well, there's a fourth here! You want the tank top? Okay, there's a tank top in here. Yeah, so classically, objects are representations of things. Think of them like as the noun; they don’t do anything. They’re the source, the methods that exist on that object are the verbs—these are the things that they can do. The properties—the variables—are sort of like the description of the thing; they’re adjectives, describing what state it’s currently in.
00:09:03.170 This is how I would explain it to my beginning programming classes. Some people have the modern approach which talks about behavior, but we'll skip right past that because I've got a whole bunch more slides. An object, as Sam said, is a collection of behaviors and the state of those behaviors. So let's just jump right through here because I don’t have a fun slide for the Single Responsibility Principle. It states, a class should have one and only one reason to change, meaning that a class should have only one job. This is the first of the five and is one of the most known principles among Ruby developers. If they don’t know it instinctively, this is the one we hear about all the time. Basically, your code shouldn’t try to pat its head and rub its belly at the same time!
00:09:49.160 That’s the same as doing too many things at once; you can do it, sure, but it’s not always that easy. You’re not going to do either thing terribly well, and it makes it really hard to test. We want our software to avoid falling over all the time, so one malfunction should not knock everything else over. That can happen if you start reaching responsibilities across objects. Very often, you'll see this in Active Record objects, which are a horrible mess to begin with. They access data but also define data. Typically, in a Rails application, we’re putting class methods and instance methods into the same object. We’re mixing all different types in a way of thinking about a user object. This leads to a situation similar to playing Jenga and trying to do speed dating at the same time—it’s just a mess.
00:10:34.390 So, if it's a negative discussion, and to claim that this is actually a technical talk, I put some code in here. If we're going to write up an app to help me run my kickball team, I might start with players. Players can define their stats; it's pretty simple. It's a question I would ask of them. Then we have some teams where we can look at the players and figure out how good each team is at kicking. When describing what an object does, pay attention to how many times you have to use the word 'and' or 'or.' Each of those clauses that are connected by those conjunctions indicates another responsibility this class has to cover. The more responsibilities you stack up, the more mixed-up tasks you end up having.
00:11:23.990 So let’s consider the team. The team could be thought as holding the state of all the players—it knows who its players are, which is probably an okay responsibility. It can also figure out the average and formats the average when it presents the output on the HTML. So a quick sort of single outcome refactoring you often see is taking out something like the formatting at the output so you can ask an object what its properties are—that is, what values it holds—without any control or knowledge of how that will be used. This object as soon as it’s in the URL space or any space needs to understand HTML or that.
00:12:15.560 The Open/Closed Principle states that objects and entities should be open for extension but closed for modification. Simple, right? You don’t have to perform brain surgery; just put on a hat. You should be able to add features or methods to a class without needing to change existing methods or code within that class. If we were dealing with players again, we might define their kicking order. We’d say, ‘If your position is first base, you go first; second base, you go second,’ and so on until we get to ninth base. I don’t know; anyone here major in kickball? I majored in ultimate because it was a hippie school.
00:12:55.890 So, basically, like, what if you want the bench warmer kid or the scorekeeper, like me, to come kick on your kickball team? What would have to happen there? Well, you'd have to modify the existing code to account for a new behavior or relationship within the system. One way around this is to define the kicking order on the players themselves. You can go a step further, using inheritance so it’s passed down to each class of player. For example, a first base person, in the generic sense, goes first. Our system just assumes that happens. This is ideal, a sort of canned code exercise example, but you can see this in action if you’re writing a billing system and want to accept payments—it’s about handling special cases.
00:13:54.590 Let’s talk about Liskov Substitution. Anyone understand this one? Very dear friend of mine teaches graduate-level math at Cambridge, and it’s kind of a weird humblebrag when you think about it. But I read her this description and asked, ‘You understand this, right? It makes sense to you?’ And she said, ‘Yes! Yes, of course, this is what I do all day long!’ She works with category theory and topology. Liskov often gets explained that subtypes will be substitutable for their base types. Anytime you deal with subclasses, a subclass should work in any situation that you would use the parent, and it should be seamless. This is about how a person interacts with the class itself.
00:14:35.870 To return to the kickball analogy, if we’re going to play frisbee and I throw you a frisbee, that works pretty well. I can also throw you an AeroBe. Do you know what an AeroBe is? I can’t believe I’m in this weird stratum of frisbee knowledge! An AeroBe is a single ring—a thing like a frisbee with a hole punched out is amazing! Unfortunately, they’re interchangeable within the context of playing a game of catch but not within the context of eating a piece of pie. They’re not interchangeable for what can go over your head, so Liskov tells us that as long as our relationship to it is limited to this one idea of how we interact, we should be able to swap in subtypes.
00:15:32.450 In the kickball idea, what if we add a third base person class argument for the consistency between our subclasses? We’re looking at a different subclass from the perspective of the client interacting. So if teachers are organizing the game and asking players for their positions, that’s great! If everybody returns it as a string, but now the third base returns a hash? Great job, third baseman! This would violate Liskov; they should at least be consistent, so we don’t have to do special casing around these different subtypes. The code that is interacting with these different subclasses should be able to assume how they’re performing.
00:16:29.630 Interface Segregation: this is one of the principles you will get different explanations about, depending on if you ask Java developers; they might have an opinion about interface segregation, often calling it factories. But Ruby devs tend to ignore it because the flexibility of duck typing and the dynamic nature of our language erases violations we might see around interface segregation. It’s often expressed as each object knowing as little as possible about the other objects it’s interacting with. When I explain things to students, I often say, ‘Let’s say that you and I go to lunch.'
00:17:19.670 When the bill comes due, I say, "Hey, sorry, what’s your name? Hey Nate, could you pay the bill?" That would be respecting our interface; that’s interface segregation. Not respecting it would be clubbing you on the head, reaching across into your wallet, pulling out your credit card and faking your name. Unfortunately, we do that all the time! We talk about it in terms of how many methods someone is using and how many classes are being skipped over. You’ll see this especially in a lot of long-standing Rails applications that don’t follow this principle, like saying user.billin.g.address.country to find out the user’s country instead of simply saying user.country.
00:18:07.830 When you leap across these boundaries and reach into somebody else's code to take something, as in the case of a kickball team, this is sort of the ugly problem we might encounter trying to calculate salaries. If we’re reaching into their salary structure, like, if you happen to be a player named Kerri, you love tuna fish sandwiches with potato chips on them. Should we do that when your kid's here? Oh, it's the best! Just know it’s a little dangerous, folks; play hard! Now finally, a question; we’re checking the salaries to see what’s in the cafeteria because if you can’t get tuna fish one day while Kerri isn’t getting paid. You get paid on the kickball team!
00:19:06.620 This example isn’t great, but consider this: what if you get it? A fourth-grader got held back a couple of years. They’d be a bit bigger than the others and have to give half their sandwiches! It just leads to this mess! If we take the team class away from its specific knowledge of all the various salaries the kids want to get paid, you will see an idea emerge. The notion is that a team shouldn’t need to know all the distinct salaries; it should be able to ask other classes, 'What do you need? What's your deal?'—and that leads us into Dependency Inversion.
00:20:08.200 Also known as Dependency Injection, with slight differences between the two, these concepts are interchangeable. It means that high-level modules must not depend on a low-level module; instead, they should depend on abstractions, not concretions. This idea results in coupling between classes. When we had the team, we realized before that the team was responsible for printing—you know or the actual output—so now, we have a roster printer class to pass the names of all our players, and it just prints them out! Easy peasy! We’ve separated the print concerns away from the team, so we’ve subconsciously implemented the Single Responsibility Principle.
00:21:18.440 But now we’ve created a situation where we bind the team to the Roster Printer class. If we break the Roster Printer, we’ll see errors in the Team’s tests when there’s nothing that has nothing to do with the Roster Printer. These things are dependent on each other—they're bound together. The way we can break it is by providing Dependency Injection, where we let it be a definable attribute on the method calls or in the classes. In this case, instead of just invoking the Roster Printer class directly when we say print roster, we define it as an abstraction that we can call.
00:22:07.050 This allows us to handle different types of printers. We could build an HTML printer that wraps it in HTML for a webpage, or we could create an XML printer class if we wanted to torture ourselves with something complex. This introduces the concept of thinking about things in abstraction rather than in concrete, specific objects. We do this quite a bit; any time you use a Rails application and access the database, you’re witnessing this. We don’t call the Postgres directly; we're just calling the DB connection because it is hidden behind this abstraction that we don’t need to worry about.
00:23:00.370 So, that was a super fast, high-level overview of the SOLID principles! I think SOLID is a really interesting model, and thank God it’s called SOLID, right? Because it tells us that if you use these ideas and apply these principles to your code, it will be rock solid! Acronyms are tremendously important.
00:23:43.000 Did anyone see this in the news recently? Yeah, they had to change the name of the Antonin Scalia School of Law! A bit unfortunate, but imagine if SOLID was not called that! What if it was called DOYLE? You know, we wouldn’t give attention. But because it’s called SOLID, it captures a certain amount of goodness.
00:24:17.610 The interesting thing about SOLID is not that humans came up with it, but it seems so self-evident once you start picking into it. Each of these principles plays on the one before it, and you get the most out of them by looking at all five concepts sort of at once rather than each one in isolation. Taken together, they support each other by interlocking; it’s like a Venn diagram with overlaps. As they play into the next, they describe higher-level relationships, like what this class does, all the way to how this class interacts with other classes within the system.
00:25:09.600 In a lot of ways, SOLID is designed to help us be more agile in our code. When you write SOLID code, at first, it seems like you’re doing extra work. You’re making subclasses, and as a Rubyist, you may not like them. You may dislike all these abstractions when you actually apply them; it allows us to be more flexible, letting our code resist change better, which makes less brittle code overall.
00:25:58.620 I don’t think I emphasized it sufficiently earlier, but when I use SOLID or any design principles, they serve as metrics rather than a restrictive set of rules. It isn’t about being forced to design a system that makes you feel claustrophobic, or to put on a straight jacket that hinders your expressive freedom in your code. Sometimes, yes, you do trade a little bit of time now—just the tiniest bit of time—to think about the relationship between your objects and how they're constructed.
00:26:25.670 You might not know what you will do in your code or if you will need to print a team roster in JSON for an API later on—but if you separate out the roster printer now, for example, you'll make life easier later. Sometimes, you have to break these rules accidentally because you can’t predict where your code will be next time. That’s okay! When you understand the design pattern for writing code that allows these patterns to emerge, you can see them later on when you return to the code, because you understand what the refactoring steps need to be.
00:27:03.370 So don’t feel guilty if you break the hallowed rules of object-oriented programming! There are six more rules beyond these first five that nobody talks about, and nobody talks about them! Slava Jejak said that for us to imagine ideology as a kind of filter or frame. Everything looks different when you view it through that frame, but in what sense? It’s not that the frame adds anything; it just opens the abyss to its suspicions, and I have been thinking a lot about this in terms of software.
00:27:32.790 The ideology that we bring to software is insufficient for the job at hand. We apply our concepts, our philosophy, our ideology to the next task or project for technology and can swap these ideas in and out like glasses. We might say today, ‘I’m going to strictly follow the Single Responsibility Principle.’ Or, ‘Today, I’ll focus on not breaking the Law of Demeter,’ or, 'I’m going to focus solely on this one thing.' Benjamin Franklin had these 13 rules on how to be a great person and live a good life, advised things like being humble, being dedicated, and going to bed early.
00:28:05.080 He believed in not engaging in drunken foolishness, which is a wonderful word! Every week, he would rotate to the next item on his list and spend the week practicing being humble or working on being diligent to become a better person. Four times a year, he cycled through this entire list. At the end of the year, he would look back and think: ‘Doing pretty good, doing pretty good, except I’m supposed to be humble this week.’ Thank you!
00:29:00.740 In a similar way, we can cycle through different engineering concepts or principles and bring them in, picking one metric tool to apply to your code for just one week. Don’t think of it as the new way of doing things! Consider repetition in your code and how things change. You can wear a different hat for just a while to see life and your code from a slightly different perspective.