Transcript
(Editor's note: transcripts don't do talks justice.
This transcript is useful for searching and reference, but we recommend watching the video rather than reading the transcript alone!
For a reader of typical speed, reading this will take 15% less time than watching the video, but you'll miss out on body language and the speaker's slides!)
Thank you. [AUDIENCE APPLAUDS]
So, yes. OK. He told me there was plenty of time, so I'm going to gab for a minute. Well, first of all, all right, I suck at event organization, which means, if it were up to me, we would never have a conference. And so I just thank you for the folks who did this.
The next thing is, if you get a chance, you should sneak your way up here. It is so cool. This is a real stage and there's an amazing backstage. It's like I feel completely professional.
But I do want somebody to sit up in one of those chairs before this is over, like in the balcony boxes. Go find that. All right, that's enough. End of gabbing.
I'm Sandi Metz. I'm a woman of a certain age. What that means is I had a day job for longer than many of you have been alive, where I went to my desk every day and I wrote code.
And then I accidentally wrote a book. And I won't tell you the origin, story of that book, but I really didn't mean to, and it was like a bomb went off in my life.
I started speaking at conferences, and it turned out I was not at my day job enough for them. And people kept asking me on the strength of that book to come to their shops, to visit them. And so, eventually, I quit my day job and I started doing that. And now, I basically, when I get paid it's because I have traveled somewhere and I'll teach a short course in object-oriented design.
And so what that means is, where I used to go work on big apps and write code all day long every day, now what I do is, eight, or 10, or 12, or 15 times a year, I go to someplace where there's a bunch of people I don't know and a huge big app I don't know, and I look at their code and teach them about OO.
Over the years, as I've done this-- look, I used to think everybody was unhappy in their own way, like every app sucked in its own different way. But then, over time, what happened was I started feeling as if there was a larger abstraction of the problem that people were having, and this is with OO.
So I kept seeing a repeating kind of pattern of wrongness in people's object-oriented applications. But I was suspicious of that because I have a skewed sample, like people don't call me if things are going well. And so I started asking around. I'd go to conferences and ask people if they had this kind of a problem, and they were like, oh yeah. Every app we work on sucks in that same way.
And so I started trying to figure out what was it, what was causing it, and I wanted to know how we could fix it. And now I kind of think I know. I have an idea, let me at least say that. And so today's talk is about how object-oriented applications go wrong and what kind of mindset we can use to fix them, to do better. I hope by the end of this talk I'll have convinced you to write code in a different way, and I have turned you into an evangelical convert, where you will go and spread the word.
All right. So the talk's in four parts. I'm going to tell you some things, then I'm going to show you some code. And then I'm going to tell you some more things, and I'm going to fix that code.
The first set of things, this is-- I'm going to start off with an explanation of the problem, how we got there. It really involves four different plots. The first one is Martin Fowler's design stamina hypothesis.
Some of you have probably seen this chart. It's a thing where, as you go up, as points plot higher, it means you got more done, more functionality was created. Time runs from left to right.
He put two lines on this. This is the good design line. And basically this chart is making an argument. It s that it is a waste of money to do design early, that you can't possibly know enough. It's just a waste of time.
But it also says that a time will come in your application where, if you don't start doing design, it's going to cost you money. Many of the places I go, for apps that are long lived-- and it basically means businesses that have succeeded. They're still in business five, or eight, or 10 years later. What happens is their velocity is slowing down over time and they're very concerned about it.
What Fowler says, really, is that you should stay here. The best way to behave is to just throw code at the wall for a while early on until you understand what you have. And then, after that, you should switch horses and start doing some design. And what that means is that you have to recognize when you cross, when those lines cross, when you're at this point.
But the problem is, in a fog of code, you just can't see it. And I can tell you that nobody successfully switches too early. That is not the problem. The problem is one day you wake up and you're out here. OK, so that's ideal 1.
Here's idea 2. This is supported by nothing other than my opinion. Here's another plot. And on the vertical axis is how hard it is to change things. On the horizontal axis is how hard it is to understand things. And I'm trying to contrast procedural versus object-oriented code.
So the bottom left-hand corner is where easy to change and easy to understand code appears. and that would be a simple procedure. By simple, I mean short and without conditionals. OO falls to the right of that, the same OO that would solve that same problem.
So I am happy to concede that OO is more complicated than procedural code. And it's because OO depends on messages, and messages add a level of indirection, and indirection always makes things harder to understand.
So if you can solve a problem with a simple procedure, you probably should. Just write it down. Make a list of things and do them.
However, the problem with procedures is that they do not scale. And if you have a procedure and you stick with it, what happens is it gets bigger and bigger and full of conditionals, and you end up there. And that is hard. It's way up in the hard to understand and hard to change.
The only thing you can do worse is to get OO wrong. So here's a thing I see a lot when I go out in the world. I like to call it faux OO. So people get out, they get way out in the bushes with these super complex procedures, and they can't fix them. They don't know how.
But they try to dress them up in a little OO, which basically means hiding parts of the code to make things appear to be smaller. So they take what's a complex procedure and they add levels of indirection to punish future maintainers. There's a lot of code out there that looks just like that.
You're laughing because why? Yes, because you have it at home.
So next, this is the third idea. Michael Feathers wrote a blog post a number of years ago that used this chart. It's another plotting space where complexity is on the vertical axis. Things that are more complicated are higher. Churn, which is the number of times a file changes over time, is on the horizontal axis. Things that change more are more to the right-hand side.
I added that green line. I contend to you that, in a well-designed application, if you plot churn versus complexity, what you'll find is the classes in your app cluster around that line. What this means is that super complex things don't change very much, and code that changes a lot is pretty simple. And that's the sweet spot of OO.
That is where you do not want to be. This means that you have super complex code that changes a lot.
All right, the last thing. You guys maybe know Code Climate. It's a tool that, among other things, will look at complexity. It will do static analysis of code bases.
And one of the things you can get out of Code Climate is this chart that they label churn versus quality. You can think of this as Michael Feathers' churn versus complexity chart.
I went to GitHub-- so Code Climate does all the open source projects for free, so you can look around at their Code Climate scores. Code Climate gives projects an overall score, which they call GPA, so it's like letter grades. It's a number that, really, corresponds to an A, or B, or C, or something.
So I picked three that are in-- I'm going to show you three graphs in increasing level of good quality. This is Angular. It got a 1.8, which is sort of like a borderline C. But notice that the marks, the classes that are plotted here, kind of fit that green curve, except they have that.
And just to prove to you that it's not a matter of low quality, here's Discourse. At the point in time I extracted this graph, it was like a good solid C plus. And it fits pretty well, but they have that.
And here's the GitLab HQ, which is a B. This is a really good score. Bryan Helmkamp, the guy that started Cold Climate, thinks that B is-- it's a little crazy, it's a little too obsessed to try to get a better score than a B on Code Climate, is what he says. So there you go. But look at that.
And I can tell you something. I can predict, having-- this is true. I can tell you that what I'm about to say is true because I looked at the code. But even without going to look at that code, I can tell you what those classes look like.
If I looked at the churn versus complexity chart for your own app, I can tell you what's up in that right-hand corner. It's a class that's big, much bigger than average, than the average size of the classes in your system. It's complicated. It has a bunch of conditionals in it. And it's about something that's super important to your domain. It's a core-- it's user. If you're doing contracts, it's contract.
I think if I went out in the audience and asked individuals to tell me what class was up there, you all know. You know what class is there on your churn versus complexity chart.
So this is universal. Everywhere I go, code looks like this. And the question is, how does it happen? Why is it that we're all doing this?
And this is a thing that I think I know. Think we all started out writing simple procedures, just like Martin told us. And then time passed and we just kept on doing it.
We're all doing Agile. I love Agile. I have nothing bad to say about Agile, but it's not perfect. There's a down side to Agile and it's this. You grab a thing off the backlog, and you go look at code and you do more like what's there.
And as time passes, as time continues to pass, what happens is the stuff that's important to your app changes faster than the stuff that's not important. Which means that, as complexity increases, it's asymmetrical. Things that you really care about get worse faster.
And then, when the day comes that you notice that you've crossed that line on Martin Fowler's design chart, the code that isn't very important to you, you kind of still understand it. And so you can go in those things and change them and make pretty good OO out of them. But that thing that's in the upper right-hand corner of the churn versus complexity chart is out of control, and you don't know how to fix it.
Let's do it. I got code. Now, it's Ruby. I think most of you know Ruby, but if you don't, don't worry about it. I'm really just going to buzz through this. You can just ignore the code and listen to what I'm saying if the code doesn't make any sense to you.
Imagine that you're writing a book. And it's a technical book, so you're going to have text through the book, and you're also going to have source code. And you want to include source in the book, and you can't just type the source in the book. That won't work. It will be full of typos and errors.
So you have some mechanism that will allow you to read source code at some place on disk and include it in the text of your book. This would be Ruby code that would allow you to do part of that. It allows you to pick out the source of this code. It will read a file and produce an array of a list of lines. So you write that code.
And then, right away, you realize that it is insufficient for your needs, because maybe you have code that's not laying right on a file, but it's down in a git repo with a tag. And so you want to do both those things. Sometimes you do one, sometimes you do the other.
The git show command is really pretty handy for doing that. This is a Ruby show method. It's got some stuff, and it's complicated enough, so you'd probably put it in a class.
And so, once you have this object, you can go write a conditional. You can do this-- what you used to do-- or the other thing. And now, once I get to the point where there's a conditional, probably you're going to make a class. You're going to wrap it in a class, and you're going to take all these arguments, and you're going to remember them, and you're going to have [INAUDIBLE]. Of course.
And this works. It's fine. Probably should walk away. Who cares? So like it all fits on a slide on a 44 point font. Like we probably need do no more right now.
But then, of course, something else changes. And it turns out that the listings have to be complete. They have to be full, testable code. But you don't want to include the entire listing every time you get some of that code and put it in the book. You want just little snippets. So now we need some syntax, a comma-separated syntax, where I can specify which line numbers get plucked out of that source code and included in the book.
Rich Higgie said this, right? And this is what happens in code. People go do more of what's there. And what that means here is we'll just like-- let's just pass that list, then. We'll just remember it. What could possibly go wrong, right? And so I got this.
Now we're going to start doing faux OO. I need to put a new conditional right there. And so I want to hide some of the complexity, so let's just-- like I'll extract that and make a method and do that. Now look, that's so much better.
[AUDIENCE LAUGHS]
And I got to remember it, right? I got to cache that. And so now we have this. And this is an interesting form of this conditional. It makes it look like there's only one thing. There's no else branch here.
And I had to write that method, which-- there's the syntax up there. So here's what I have to do. I have to get the string. I have to break it up on commas. And I have to figure out what's in there and include that in.
I don't know. You're probably better at this than me. But every time I have to do something that involves something like that, the code just sucks. I look at that code and I'm like, what does that do? But I don't know. It probably doesn't matter. Like, don't look.
[AUDIENCE LAUGHS]
Right? It's not that bad. As long as it never changes, it's really probably OK. And so, of course, the next thing that happens is you need to change it. Because when you pluck just sections of code out of a listing, when a user reads them, they don't really make a lot of sense without some indication that code is missing.
And it's a little bit awkward to get that in a book, so you need some syntax where you can say, put a comment that has four leading spaces so it will indent. And put a comment that has six leading spaces. So I need that.
And so now, what that means is that changed, the input changed. And so now I need another new conditional right there. And I've already forgotten how that code works there.
But I don't know. Despite the fact that I don't really understand this any more, it's pretty easy to put a new conditional in there. I'll just check for that pound sign and write some more code. Then we're OK.
All right, one more thing. One more thing happens. So the book is unaccountably a success, and people want copies of it in their own programming language. Right now, all the examples are in Ruby.
And this is when you find out, unaccountably, that every programming culture does not agree that two spaces is the right level of indentation. I'm looking at you, Python. And so if you reach in too deep into a code sample and pluck out a bit, it could be like 20 spaces over on the page if you just have that part. And so you need some way to left-justify a block in the code that's included.
So, yes, let's just inject a Boolean. How could that possibly go wrong? I got to remember that. And now, here's what happens. I don't have room to put all this on a slide, like I got to do that, which means I got to push that up.
Now, the if line numbers needs a false branch there, because I got to catch that and save it. And then I'll just write another conditional in that same shape down here. And I'll soon justify. And that means I write a whole bunch of other string mashing code that I don't really understand.
So let's go over what just happened. So first, I was just reading a file. And then I had this change where I needed to do two things. That created a file. Pretty soon you're not going to be able to read it, but don't worry. We're just looking at shapes here.
I had 29 lines of code. There are two execution paths through that code. Then I needed another change. Some of the lines are old lines. That was 50 lines of code. And there are four execution paths.
And I needed code or comments. That didn't increase the lines of code very much, but it put another conditional in there. So now I went to eight, because the number of execution paths through your code is the Cartesian product of the branches of your conditionals.
And now here we go. 83. That added a bunch more code, but what's worse is that now I have 16 execution paths. And I can promise you, you do not have tests for them all. I promise you. You know it's true. You just give up on testing that stuff because it's too insane.
So now what I have is this and that. And I promise you, I use the flamy animation intentionally here. We're way out there in what's right field on these charts.
This code is bad and it's going to get worse. And it's code that's going to-- it's important to my domain. It's going to change more often. It's out of control already because it sets an example of how to write code. People will do more of that. And every time you change it, something's going to break.
So here's the big lesson here. What worked is going to hurt you later. And what I see is people double down on what worked to begin with after it starts dooming them to failure. A time has to come in the life of your application when you do a different thing. Again, I would never say procedures are wrong, but they will not last forever.
And so now that I've thoroughly discouraged you, let's talk about doorknobs. So this is a doorknob. It has a use Its use is revealed by the object. It's meant to be grasped and turned, and you can push or pull it to open the door.
Here's a different kind of doorknob. I have these in my house at home. They are levers. You can use them if you don't have hands. If you can't grasp, they still work. Or if your hands are full. If you do have hands but they happen to be full, you can push them with your elbow and knock the door open with your hip.
Now, in both of these cases, what you cannot tell, even though they afford pushing and pulling, there's no indication of which action will actually open the door. And we've all had that-- you've all experienced that phenomenon where you push and nothing happens, right? You didn't look at the hinges.
And so, very often, people overcome the failure of affordances in the design of this object by adding additional instructions. And that's what pulled us here.
Here's three more. These don't control latching. They just control opening and closing. This one is clearly meant to be pulled. The object contained in it. Instructions for its use.
This one is insulting. Duh, right? And then we have this one, which is clearly what ought to be on the opposite side of the door from the handle that you pull.
Now, it is really common. I actually was at a place recently where I taught a class in this fishbowl conference room that was all glass. And they had glass doors, where-- I guess the architects, in an excessive desire for symmetry, had put the pull handle on both sides of the door.
And so then, of course, people are always crashing into it, because they would pull it and nothing would happen. And so there was a sign on that door on the inside of the conference room that said push so you would know what to do.
So just like objects in the real world have affordances, so do languages. Language designers mean for you to think about the worlds that you're going to create in certain ways.
I think I know what OO affords. I'm going to make a list and then we'll talk about each things. OO want anthropomorphic, polymorphic, loosely coupled, role-playing, factory-created message-sending objects.
Anthropomorphism is that thing where you act like things that are non-human have human thoughts. I can't figure out whether he's excited or scared. I have an emotional reaction to that picture even though it's a block of concrete.
OO means for you to think. It is not a sterile language where you write algorithms and wrap them in namespaces. That is not what OO is. OO is a play where you create living beings and make a world where action happens.
Polymorphism. So we stole this from biology. It's about having forms. This is a monarch butterfly, and this is a viceroy butterfly. I'll Put them side-by-side.
It turns out monarchs taste bad, apparently. And viceroys have evolved a strategy, a survival strategy that involves mimicking the monarch. So predators don't eat them because they look like monarchs. So this is a case of mimicry in forms.
We use the term polymorphism. It's really hard. I don't know why polymorphism makes so little sense. It didn't make much sense to me for a long time.
But now, here's how I understand polymorphism. It's that quality where different kinds of objects can respond to the same message. That's what it's about. They share a common form at the message response level.
Coupling. We want to not be coupled to other things. Being tightly coupled to stuff means that, if it changes, you might have to change too.
This-- notice how strongly-- they are loosely coupled in a really tight way. I think the owners are probably holding on to the other end of that tug toy. Either one of them could let go and they could substitute something else. And so, despite the fact that they're making use of this toy in a really strong way, they're loosely coupled to it.
Role playing. We think of objects as players of the roles rather than instances of their types. Role playing is just the English way to-- it's an anthropomorphic way to say polymorphism. Players of a role are substitutable.
And, in terms of the role they're playing, these women are all playing the serve-receive role at a volleyball game. There are substitutable in terms of that. But you can have differences in these objects, like here, for instance, in the shoewear. I don't know where you get those shoes. I kind of want a pair, despite the fact that I don't really wear shoes.
Factory created. Factories are where you hide the rules for picking the right player of a role. I am not talking about this kind of factory, not big, heavy, polluting things that your scarred past from other languages may lead you to imagine. We're talking about really simple, lightweight, easily changeable offloading of the conditionals that will pick objects. That's what we're talking about.
And finally, we have message sending. So messages let me know what I want without knowing how you behave. So they provide a level of indirection which gives me sub-- it's a seam where I can have substitutability on the other side. What they primarily do is give me ignorance, and in OO languages, we're striving to be ignorant about what other objects do.
So here it is. This is what OO wants. This is how it wants you to behave. Let's fix that code and make it more object oriented.
So I have this. Oh, yes, here. Sometimes my clicker doesn't-- oh, but wait. Stop. That was a delayed click.
So right now, I have not too much code. It has two by two by two by two execution paths through it. I want to have a little rant about this bit of code right now. So here's what I'd say if you showed me this code.
You injected a dependency. Someone gave me that get underscore command. You injected a dependency and it was too stupid to behave for me. I had to look at it and make a test, and in one situation do one thing, and in another situation do other.
I don't want that. Don't give me that. If you know enough to pass this, you know enough to send a smarter thing. So let's make it.
The first thing we have to do is decide what role these two things play. I think it's the source. And so if I have a source role, I need to pick an API. Let's say lines.
And then there's this cloud where all the role players of source fit in there. Right now I have two. I could have a million. It doesn't matter. I should pretend I can't even see in there. I don't care. They all play the role of source. The code for this is super simple. I just polymorphically create an API, and then I copy the code from one branch in here and the code from the other branch in there.
Once I have-- oh wait, sorry. This is what happens when I stand out here instead of standing back at my presenter notes. So here's the next rule, right. I'm trying to isolate the things I want to vary. This is OO. Variances ought to be isolated so that, then, I can inject them and get substitutability across that boundary.
Here, this is the code we have. If you look at it, that much code is involved in making the decision, and I can delete it all. If I just inject a smarter thing, then I can change this conditional into that message. And that cuts the execution paths in half. So the big wins are early on when you do this, when you're out at the ends of the by twos.
Now-- oh wait. So the conditional is gone, and I'm well aware that you might be wondering where it went. And this is a problem people have. Like this composition is what you would call. This is object-oriented composition, and I kicked the can of choosing what to do down the road.
And here, I really want to push the rules. I'm going to push them back the stack. I want to send conditionals back as far as I can, back to the first place someone could have had enough information to pick the right object. We'll talk more about conditionals in a minute. Let's just do the rest of these.
So what is that one? I'm going to call it subset. I don't need line numbers. What I want is an object that has the behavior I want.
Now, this one is kind of interesting, because look at this class. I just added a five-line class to replace what was effectively no code in the original stuff. And people complain about OO because of this kind of thing, but here's what I would tell you. I have to be able to treat everything the same, or else I am doomed to the conditional.
And it is perfectly appropriate. There is a subset of lines that contains everything on the list. And that's what this is. The idea was there. It was just hidden from us. We couldn't think of it.
This is like zero in the number set. You gotta have it, or else things don't work. So that's pretty simple. I just made that up. This, I just copy the method over, but I need to polymorphically implement lines.
Now, I am aware that there is a conditional right here, and I promise you we will get back to it. That doesn't have anything to do with subset. That's some other concept in there.
And so this much code was in the original class related to that, and I can throw it all away. I don't really-- I'm uncomfortable with my verby name there, but I can't make myself stop using it. So do that. And then that output from that goes into here, so I got that. And the execution paths went down to four.
There are so many fights, especially in the Ruby community, about whether or not dependency injection is a good idea. And I see people having arguments online, where they inject a stupid thing and then use it to pick a class. And they say, I don't want to use a dependency injection.
And I look at the examples and I'm like, well, you already are. Like you injected something and then had to make the decision here. I believe that you should be using dependency injection a lot more than you are, but you should inject smart things. Like every place you're injecting a Boolean and checking it for true or false and thus applying behavior, you should have a smarter object in that place.
So let's just do the last one. I'm going to skip number three for a minute. So four works just like all the other ones. I'm going to call it-- I can see that these objects are perilously small, and the method names are perilously echo chambers, but it's in service of having an example I can talk about in 30 minutes in a talk.
Notice that the non-justifier is like the everything subset. You have to have this object or you can't do composition. You're doomed to the conditional otherwise. And so that, I'll just inject a smarter thing. Blah blah blah, blah blah blah, blah blah blah, blah blah blah, blah blah blah. And that's the code.
Now, let's go back to the conditional that we still have, which is in the code line or common. It's right here in this code. And so now, the interesting thing about this-- well, first of all, let's name it. Names are hard. I call that clump. If you had a better idea, you should tell me afterwards.
And it's interesting. Here, you notice that the dependency didn't get injected. Like I'm iterating over a list and finding it, so I can't really go get a clump. Nobody can pass me a clump from outside in this. But I can take the true and the false branches of this conditional and make clump objects.
So let's call it lines. I need a comma. This is just-- it's exactly what you've been seeing. It polymorphically plays the roles. I just copied the code over. Here, these things need data. They need some data. They need the spec that they're on in the input, that they have the subset that they're looking at. And so I arranged this a little bit. There's a clump class that I put the initialization and I made these things a subclass, made these things inherit from it.
So this code, back in the subset thing, now that those two role players of clump exist, I can delete that code and use those classes. I can just put them here. I could put the object instead of the behavior, instead of knowing the behavior.
And this is a little bit of improvement, right? You can see that these things polymorphically play a role. So they polymorphically play a role, but the conditional is still here. Like I know the rule in this place, and that means I'm tightly coupled to all the stuff.
When you have code like this in your applications, when you're checking for something and supplying either behavior or the class that knows the behavior, it's really common for these conditionals to be duplicated in many places. You're saying, if you're dealing with real estate, if it's an apartment or if it's a standalone home. If you're dealing with contracts, it's like a business contract or a private contract.
What you find is the shape of the conditional-- even though the context that you're in is different and the behavior they're supplying is different-- the shape of the conditional is often in many places. I want to get rid of this conditional right here, and that means I need another kind of object to do that. And that's where factories come in.
The factory contains just that exact code. It uses the data that I had to pick the object that I want. And here it's going to manufacture an instance of that object and pass it back.
Now, some of your style guides may explicitly prohibit this. But I know that you don't hesitate to send dot to the ending curly brace of a block. I use this syntax for two reasons. One is I want you to start thinking about blocks as returning objects, to which you can send messages. I think part of the OO mindset says you have to do that.
The other thing here is it encourages me to make the initialization API for all the objects that I'm manufacturing the same. And I want them to be the same because I don't really want to know here the difference, if they're different. Like here, they could be different. Comment only needs to know the pound sign. It doesn't need the list of possibilities.
But I don't care. Look, I'm striving for ignorance in every way. I will happily use more CPU cycles to do an operation whose output I already know in order to be able to treat things interchangeably.
So once we're there, I actually made a helper method called lines here, a convenient method that does the whole thing that other people can call. And so, once you get that, if you go back in this code, you can just invoke clump lines and ask it. And so this is a situation where, instead of someone injecting mu-- the object I'm calling-- out someplace, I'm invoking the factory to get the object.
Almost done. You've been very patient with the code. Thank you.
So here's what the first half looked like. This is what I got with procedural code. I ended up with 83 lines of code and 16 execution paths. The OO version looked like this. It had a lot of little things. And then the factories.
Now, ignoring the factories for a minute, if we just look at the rest of this body of things, what we have is 134 lines of code-- a lot more code-- and one execution path. The fact that there's more code overstates the complexity, because there are actually nine classes here, and the largest class is 18 lines of code.
This is OO. It is easy to understand the parts, but you have to mentally assemble the operation of the whole. That's what it means to do OO.
The factories are where the conditionals go. And if you don't like them, don't look. They have a tiny amount of code, and they know the rules for switching and the object to choose to get the behavior. That's what we're doing here, right?
And once you make a factory to produce a player of a role, you have to use that factory everywhere you need the role. You must forget. You cannot reference the names of these classes any place else in your application. You call clump-- you call clump four and it gives you back something that plays a clump role, and you just send it lines and it works.
So what we ended up with, here's what we built. We made this. Listing. And it's only responsibility is to coordinate the collaboration of injected objects in order to accomplish its own goal. The information that we once used to choose variants, now someone else uses to choose objects that supply that variation.
Those collaborating objects bring their own behavior. We don't know it here. There are many parts. Each part is simple. They're easy to change. And it's simple to create new ones.
I think this is what OO affords. And I think you should be writing code like this. This is how OO wants to be used.
Yet, it is true, when I show this example to people, examples like this to people, very often here's what they say. They say, but now I don't know how everything works. And my snarky self-- my snarky self never gets on stage. This Sandi doesn't come out much. I'm like, yes, duh. Like that's the whole point.
But in my more generous heart, I know why they're saying that. These are people who work on applications where the most important object that they deal with is a red dot in the top right-hand corner of the churn versus complexity chart. These people are working on applications that say they're written in OO languages, but they are not actually object oriented.
And so they are suffering tremendous amounts of pain because they're dealing with objects that are-- they got faux OO, right? The objects are completely untrustworthy. Every time you send a message, you have to put a conditional on the thing that comes back in order to know how to behave or how to talk to it.
It's difficult to change anything. You don't have good test suites. It's impossible to understand it, and everything you touch breaks something. That's the world they're living in. And it makes me feel as if they are justified in their fears that if they can't see all in one place what everything does, that they'll be worse off.
However, life can be better. If you use the strengths of whatever language you have-- like, these are the affordances of OO. I totally understand that functional languages are slightly different, but it doesn't really matter. You should use the language for how it wants to be used. And if you feel like you don't know that yet, you should go learn it.
What OO-- you can turn your application-- like, if you pick the right mindset, if you get in the mindset, you can have an application where, for OO, you would have polymorphic role playing objects that are selected by factories, and they are injected into collaborators who communicate with them by sending messages.
OO gives you the opportunity to maximize the ignorance of every object. And if you take advantage of that opportunity, you can make your entire application smarter. Thank you.
[AUDIENCE APPLAUDS]
How about, how about this? Katrina is going to talk tomorrow. What else? Oh, that's me. I forgot to bring my stickers. They're in the hotel room, but I do have stickers. Ask me for one tomorrow. Thank you.
[AUDIENCE APPLAUDS]