00:00:09.599
I'm here today to tell you a story, and it might be a story you know.
00:00:15.280
You love your life, and you love Ruby, and you love Rails. You wrote an amazing app, and everyone thinks you're brilliant.
00:00:28.840
Then the customer asks for a little change. So you tunnel off and open up your text editor. You grab all the files and make one little change. Then you make another, and then a few more. You get all the tests running again, and you check it all in. You throw it proudly into production, and then they tell you they want to change their mind. They didn't really want this thing; they wanted that thing instead.
00:00:45.120
So you open up your text editor again, grab the files, and this time you make a bunch of changes. Then you make a whole bunch more. Frankly, this is not as much fun as you thought it would be, but you get everything running. You check it all back in and deploy it to production.
00:01:04.000
Then they ask for a new feature, and now it's a crisis. Everyone's hair is on fire. The app just was not designed to handle this. You do whatever it takes to make it work. You hack away at it with a machete until you get it running, and then you throw it back into production.
00:01:16.880
You start wondering if maybe there's a way to check code in without putting your name on it. Then one day, you realize that the app is a wreck, and that you hate Rails, and you hate Ruby, and you hate your life.
00:01:29.759
We all know that story. In the beginning, the app was a delight. You felt great, you were getting so much done, and that feeling of deep satisfaction came from how efficient, effective, and useful Rails felt.
00:01:42.799
Unfortunately, for many apps, this feeling peaks right about the time you release the first beta and then goes downhill ever after. Things get worse and worse. You were fast, now you're slow. You were happy, now you're sad. You were cost-effective, and now you're a money pit.
00:02:02.000
The app was amazing, and now it's just a mess. This does not mean that it's time to look for a new job. Amnesia won't help unless you change something. Every app you write, you'll end up hating. These applications are prisoners of their designs, and they've taken you hostage.
00:02:13.280
You can't outrun this problem; you have to do something different. You have to learn to understand the mess. The problem with messes is that they're messy. They're hard to understand; everything is tangled up together.
00:02:24.000
They're like big tapestries made up of many different threads where the warp and weft are woven over and under. You can't pull on any thread without dragging something along from a different part of the tapestry. In the beginning, everything is idyllic, but as soon as you start making changes, that illusion gets shattered.
00:02:32.640
Your app isn't a pastoral scene with cute furry little lambs. No, your app is trying to kill you. Things are so woven together that you can't pull on any single thread without breaking many things.
00:02:44.000
And this coupling, this mess, is because of knowledge. Your app is made of objects, and objects know things. They always know things about themselves, and sometimes they know things about others.
00:02:58.240
It's the knowledge of others that weaves objects together. When an object knows something outside itself, that's a dependency. We all know about dependencies: when something you depend on changes, you might be forced to change in turn.
00:03:07.440
You cannot avoid dependencies since objects have to collaborate. They always know things about one another. But there are many ways to arrange code to solve any problem, and you can easily arrange dependencies so that things turn out badly.
00:03:21.600
Controlling dependencies requires understanding the stability of the various bits of knowledge in your app. Everything any object knows can be ranked along a continuum from completely stable to completely unstable.
00:03:36.639
It doesn't actually matter how stable something is; it just needs to be more stable than the things that depend upon it. Stability is relative, and things that are wildly unstable in some circumstances, from some points of view, might be very stable from others.
00:03:48.960
Sometimes you might not even know if things are stable or not; you're confused about how stable a bit of knowledge is. This is where object-oriented design gives us hope. Object-oriented design is the ultimate dispenser of programming treats. It lets you write code that your future self will love, even in the face of confusion and instability.
00:04:01.599
It knows how to separate the stable from the unstable, showing you how to depend on the first and hide the second. It gives you a way to manage the mess so that you can write apps that are efficient and fun forever. It changes everything. Object-oriented design lets you stop worrying and learn to love the mess.
00:04:21.120
I'll show you how. It's easy. Three examples: the first example will set up the problem, the second one will complicate the code, and in the third example, a miracle occurs.
00:04:35.440
The arc of this talk and the arc of the examples are based on a simple premise: concrete code is easy to understand but costly to change. Abstract code is harder to understand at first glance, but it's much cheaper to change.
00:04:50.880
The principles of object-oriented design move your code toward more abstraction. It's not that there aren't costs involved; it's that the benefits outweigh the costs, as you'll see in the examples and throughout the talk.
00:05:04.000
Example number one: something bad just happened here, and I can fix it. Example number one is to label the knowledge. Imagine there's a game where people race bicycles. Players have to buy parts, and one of the parts they can buy is a shock.
00:05:18.480
Before buying that shock, a player wants to know what the shock is going to cost. Here's the code that's already written: Players on shot costs a game gets a new instance of shock and sends it the cost message. This method is a big, ugly, complicated mess; it's pretty much incomprehensible.
00:05:41.280
The customer told us they weren't really sure if it was right. They know it's going to change, but they need more information. Some time needs to pass until they figure out how it should work. Even though you can't see the code, assume it has these qualities: nothing inside this method can change that will force a change out in the app.
00:05:54.520
Nothing out in the app can change that will force a change inside this code. So this mess is due to those qualities. This is a special kind of mess, and I'm going to give it a name: an omega mess.
00:06:04.000
Omega messes are at the end of the line and have no dependencies. You can think of every kind of mess in your app as falling into one of two categories: it's either an omega mess or it's not. So the question is, what should we do about this code? It's heinous; it's really embarrassing. You would not want your friends to see it.
00:06:19.200
What will be the consequences of this code arrangement if you walk away right now? Let's postpone that question for a minute; hold on to it. We'll get back to that. Let's look at a picture first.
00:06:36.080
This is a picture of objects and the messages as they pass between them. It's a sequence diagram, and I love them. It's also UML, but trust me, it'll be okay.
00:06:50.240
Here's how sequence diagrams work: the boxes represent objects. The objects can be an instance of a class or a class itself, but they are just objects. We don't care. They repeat at the top and the bottom.
00:07:03.360
This vertical dashed line connects the boxes, that's two same boxes. The horizontal solid line represents a message being sent. The filled-in dark arrow is on the end of the receiver. This vertical gray bar tells you the game is busy in the shock cost method, and the result goes back on a dashed line.
00:07:12.560
Again, the filled-in arrow is on the side of the receiver. I drew all these pictures with web sequence diagrams. I took their name off the rest of the slides, but there's credit at the end.
00:07:27.040
I'm going to draw some pictures on the slide. I think you can probably see them. I'm going to put arrows that represent external dependencies and then dashed lines with zeros; they will represent internal knowledge.
00:07:36.480
Then I'm going to color code it for stability: very unstable things are red, down to really stable things being green.
00:07:43.520
So what this diagram shows is that the game depends on the name of the shot class. It also depends on the fact that the shot class knows the new message; it depends on the fact that it knows about the cost message, and it depends on knowing who the receiver of that cost message is.
00:07:55.760
Shock knows that it implements cost, and it knows how it does so. From the drawings on this diagram, you can see that shock has no external dependencies; it's at the end of the line, it depends on nothing else.
00:08:08.320
Game depends on a surprising number of things about shock. This sequence diagram is a much truer representation of the relative importance of things than the code itself. The big mess that dominates in the code might be 100 lines long; here it is represented by this little gray bar.
00:08:23.440
From outside of shock, all you need to know is that you can invoke it by sending this message, and the mess is therefore hidden behind the message. This view of reality can be deceiving because it feels like the amount of code ought to relate somehow to importance.
00:08:38.080
It might be true if we wrote perfect code, but that's often not the case. This implementation of cost is like a big, loud, boisterous guy at a party who dominates the conversation, surrounded by introverts who can't get a word in edgewise.
00:08:52.320
When you walk into the room and see him, it's easy to think he's the most important thing. But the stuff here that needs to be listened to is the introverts; they hold a lot more information.
00:09:03.760
Do not be misled; it's not about the code. It's not about those classes filled with characters that you typed into that file on disk. It's about the living, breathing, running application in memory. It's about the message.
00:09:19.040
Now, having seen a bit of code, let's step back and diagram it at ten thousand feet. We're going to plot it like it was a sentence.
00:09:32.800
So this is a plot. On the horizontal axis, we have stability. Really stable things go over on your left while very unstable things will be on this side.
00:09:41.120
Now, the vertical axis shows what the object knows—whether the information that the object has is within its purpose or outside of its purpose. You can think of that as being whether or not it's the object's job or responsibility.
00:09:54.560
Knowing which quadrant a bit of knowledge falls into tells you how to behave. The top left quadrant: stable things that are within your purpose are part of the public API.
00:10:06.559
Unstable things that are within your purpose are private behavior, and they should be hidden behind the public API. Anything that's below the line is a dependency.
00:10:18.720
You should expose your public API and hide private behavior behind it. Stable dependencies that are outside of your purpose you have to have in order to collaborate with other objects, but you should minimize them.
00:10:32.000
Here's the key: unstable dependencies that don't belong to you should be moved. If knowledge falls into this quadrant, something about your code needs to change.
00:10:44.000
On the sequence diagram, we saw that shock knew these two things: it knew the name of the cost method and the internal calculation.
00:10:57.680
Game also depended on the message, the cost message, the name of the shot class, and the cost receiver. We're going to dump game for now and just talk about shock.
00:11:11.520
The cost method is about the most stable thing shock knows; it's part of the public API. Other objects can safely depend upon this piece of information.
00:11:25.760
Its internal implementation is very unstable, which we know, but it does not matter because it's completely within shock's purpose. Shock is allowed to know this, but it ought to hide it behind that message.
00:11:37.440
So these plots tell us to do two things with that bit of knowledge, which is already true about the shock class. Example one is about the simplest bit of code on earth; I've been talking about it for 10 minutes.
00:11:53.600
So I ask you, what are the consequences of walking away? We all know the cost method is ugly and embarrassing, and we all know it's going to change.
00:12:05.440
At this point, design asks only one question about this code: it wants to know what is the future cost of doing nothing? Now, you will never know less than you know right now. If it does not cost you money to do so, you should wait.
00:12:21.040
The most cost-effective design strategy is often to do nothing; this might be such a case. Since the cost method isn't an omega mess and has no dependencies, there is no way that it can affect your app, or any changes in your app can affect it.
00:12:34.399
It is not connected in one of the threads on your tapestry. Having said that, it's okay to leave it. If this code were mine and I had 10 minutes, I would make a few changes. I would inject that dependency; it makes my testing a lot easier.
00:12:48.399
I would refactor this code into a bunch of little atomic methods with intention-revealing names. You might not; that's a judgment call. I do these things almost without exception because doing a refactoring that simplifies code is like buying a ticket in a lottery that's rigged in my favor.
00:13:01.760
It pays off so often that my best strategy is to play every chance I get. I don’t consider that refactoring design; it's just housekeeping, just cleanup—the last scan through a piece of email to make sure you don't have typos.
00:13:16.320
For now, we're just going to leave the code as is. So we're done with example one. Let's make it more interesting: the game's a big success, and now they want more shocks.
00:13:28.880
Each shock is going to have a different cost; there will be a different cost calculation with every shock. It's really nice to be in Colorado where I don't have to explain what a lefty shock is, while others might be confused by that.”
00:13:43.120
A well-intentioned programmer has come in and made the change, adding type to the game. The player picks a type, which gets passed into the shock cost method, and then passed on to shock in its initializer. The changes to shock reflect this: it now actually remembers that type, but it has a big case statement with different calculations.
00:14:02.960
Here’s the sequence diagram for this code. I told you before that sequence diagrams give a better representation of what’s going on in code, but they’re not perfect. There are coding sins you can commit that go unnoticed in sequence diagrams. Like, the types are being passed in, and I put a comment about the conditional messes.
00:14:17.920
But really, cost is still represented by just a little gray bar at the end of the line. I told you before that omega messes have neither dependents nor dependencies.
00:14:35.760
Is this code still an omega mess? Here’s a list of things that shock knew—it used to know just one cost calculation, and now it knows four. In addition to that, it knows the symbols for four shot cost types, and it knows the mappings between the two.
00:14:51.919
These things are known to but not owned by shock. What will happen if the application adds another shock? What will happen if any one of the cost calculations changes?
00:15:06.960
Suddenly, there are many ways in which the outside app can change, forcing you to change things inside the cost method. The case statement caused an explosion of dependencies. It's no longer an omega mess; it's something else entirely—it’s what I'm going to call meta-knowledge.
00:15:22.560
Meta-knowledge is knowledge about knowledge. Omega messes should be hidden inside the pieces of that puzzle, but meta-knowledge is about how the puzzle is put together.
00:15:36.080
Object-oriented design, and it's actually the domain of design. Let's move back up; we're going to use object-oriented design to rearrange the meta-knowledge to remove those dependencies.
00:15:52.479
We don't need to talk about the shock cost method; shock should still own that. Let's take a look at the shock cost calculations. When we had just one calculation, it belonged and was within shock's purpose.
00:16:05.439
But now that there are four calculations, it's incredibly unstable and probably should not be known by shock. This is not shock's job. The fact that there are four different types of shock is the same deal; it’s incredibly unstable.
00:16:19.680
You know it's going to change; it doesn't belong in this class. This is a dependency where some outside change is going to force a change in this method.
00:16:31.839
The mappings between what type gets what calculation are probably more stable, but they probably don’t belong here. These new dependencies are what make code costly to change.
00:16:45.360
This method used to be independent, and now it's woven into the rest of the app. It's not only costly to change but likely to change. Where you have four types, you might end up with three or five.
00:17:02.560
Adding that case statement ruined a perfectly good omega mess. This is unfortunate because object-oriented design has many techniques that will let you avoid this.
00:17:16.400
They're simple; it just takes a little bit of knowledge of object-oriented design. Here’s the guiding principle: I’d rather send a message than implement behavior.
00:17:29.920
I don't want to know things; I just want to send a message to some other object. Well, that's great, but it's really clear that what happened here is there was no one to send a message to.
00:17:43.600
That's how this happened. Shock is at the end of the line and already knew how to calculate one cost, so when we asked the question of where we should put this new code, the shock cost method seemed like the obvious place.
00:17:56.960
But that's the wrong question; it is not the question that object-oriented design asks. We got in this mess by asking, where should I put this code? We can get out of the mess by asking, what message should I send?
00:18:09.920
Then we'll make up an object if we have to, and we'll send that message. So let's look at object-oriented composition. I would explain it to you, but it is so simple, I'll just show you.
00:18:24.640
We have this ugly, dependent-laden code that we want to refactor; we would rather send a message than implement behavior. We don't want to know things; we just want to ask someone.
00:18:38.320
So, what message should I send? How about compute? We like compute; it’s a good message! Where should I send it? I don’t know; let's just make something up.
00:18:51.680
I'll create a new class and name it something related to shock cost. I'll move the mess down into its compute method, and now it’s hidden. This returns that mess to an omega mess.
00:19:05.760
I can now safely hide it behind the compute message. Since we now have someone to ask, I'll just change the original code to get an instance of that class and send it compute.
00:19:18.080
Rinse and repeat: you can hide all the messes by making a new class for everyone. Here’s where we are now: I promised you a miracle, but this doesn’t really seem like one, does it?
00:19:31.760
It's hard; it's hard for people to see this whole refactoring because they imagine going here and ending here. This does not feel like an improvement. As a matter of fact, the case statement still has a ton of dependencies.
00:19:50.080
I made up four new tiny classes; it’s easy to ask the question: is this a real improvement, or did I just complicate things in the name of design?
00:20:04.120
What did this accomplish? I used to have four shot cost calculations, and now I have four classes. I used to have the mapping between four types and four calculations, and now I have the mapping between four types and four classes.
00:20:18.399
There is an improvement here: every calculation has been removed from that case statement and put into a class of its own. They can now change independently; you can edit those files without breaking other code in your app.
00:20:31.840
However, there are still dependencies in this case statement. If you add or remove a shock, you still have to change code here, and I dislike that. Let’s fix that.
00:20:46.320
Notice the pattern: type, name, class name. This is like saying... Because we control these names, we can do a very Rails-like thing and create a convention.
00:21:01.279
It's easy to create a string that holds the name, the string of the class we want. If we can construct that string, we can use a tiny bit of meta programming to turn it into the class.
00:21:15.440
Now I can hear all the really good Ruby programmers freaking out because of eval. Like, you probably shouldn't do that.
00:21:30.240
This is like a JavaScript eval; it turns it into the object. To make Wayne happy, don’t do that; you can do this instead—Ruby has this list of constants where you can index in by the string name and get the class back.
00:21:44.160
Saying object.const_get(class_name) is exactly like saying foo shot cost. But if you're a Rails programmer, which you probably are since you're clearly using titleize, you don’t have to do that.
00:21:58.000
You can just send const_get to any string, and you get the class back—that’s an object. We’re object-oriented programmers.
00:22:10.080
This transition from making up a class name and turning it into the object in a class is something that it’s really useful to get comfortable with. So, we manufactured an object.
00:22:22.080
I use that word deliberately because this is a factory. Now, I know some of you break into a cold sweat when you hear that word because of PTSD from coming from a language where everything's hard.
00:22:38.079
But you're in Ruby now, and the pain is over. All right? We love factories. Not only do we love factories, but I want this word—go forth and claim it! This is a perfectly good word.
00:22:55.760
It means exactly what we want: an object that creates some other object is a factory; that's all it means. It doesn't mean it's hard; it doesn't mean you're going to hate yourself; it just means you use a bit of code to manufacture an object that you can use.
00:23:09.040
Now that I have that factory, I can remove the entire case statement. I no longer need to know the class names; I don't need to know the shock types, and I don't need to know the mappings between them.
00:23:22.960
I just need to know the convention. I can write code that breaks every time you change something in your app for this code, where if I want to add or remove a shock type, I just create or delete a class.
00:23:35.600
This is a huge win. One caveat: I probably wouldn’t leave it like this. I would extract the factory into some other class, but there’s no point in going into that; you get it, right?
00:23:50.799
The next example: if you can get like the problem very often with object-oriented design is that you need an object to which you can send a message, but you don’t have that object, and factories are the solution to that problem.
00:24:00.960
So that's object-oriented composition. Here's what it looks like: we had the shock class. It had a method cost that did a calculation that we crammed four different calculations into.
00:24:14.000
We fixed this problem by picking out a message, extracting each of the calculations into a class of its own, and then we defined the message and took the API.
00:24:27.520
We had shock send it, and every one of those guys implemented it. Once we arranged code that way, you can add any new shock coster by having it implement the interface.
00:24:42.960
If you create a new class, all you have to do is make it define that message. The message defines the API of the duct type, and it’s the API of the role.
00:24:55.280
Shock’s cost method doesn’t know or care what the class of the receiving object is; it expects every collaborator to implement this role. Think of collaborators as a kind of its role rather than a kind of its class.
00:25:08.720
When you do that, it changes everything. You can manufacture the right kind of thing, trust it to play the role that you expect. Then you can just send the message, and everything gets easier.
00:25:23.120
The dependencies disappear, and your app becomes a box of pluggable parts that are interchangeable, like Legos. Design happens from the message sender's point of view: we don’t send messages because we have objects; we have objects because we send messages.
00:25:38.240
Design is not in the business of dictating perfection; it knows there is no such thing. It only requests that you remove dependencies and the dependence from your messes and hide them behind stable interfaces.
00:25:54.560
It allows you to recognize meta-knowledge when you see it and tells you how to use the principles of design to rearrange code to remove dependencies.
00:26:04.800
So there you go: object-oriented design is just a set of tools. If you have the tools in your toolbox, you can pull them out and use them when the situation calls for it. The truth is, even if you don’t study object-oriented design, if you do enough object-oriented programming, it will teach you these rules.
00:26:19.920
For example, there was a time when I thought I invented the Law of Demeter. I swear I did; I was writing code in a cave and crafted bad, tangled, messed-up code.
00:26:33.920
First, I wrote it long enough to notice the pattern was bad. If I avoided it, my code got better. They say good judgment comes from experience, and unfortunately, experience often stems from bad judgment.
00:26:48.720
Now, I have plenty of experience; I walked into the headwind for a long time, not realizing an easier way existed. You might be special, but no matter how special you are, there's no need to suffer. We can learn from those who have gone before.
00:27:05.760
Object-oriented design says that stability is relative, and you should notice it. Depend on things that are more stable than you are. What you want is always more stable than what they do.
00:27:20.000
What they do is messy, uncertain, and changeable, but from the outside, from where I stand, it doesn’t matter what’s inside that house.
00:27:36.800
If you arrange code so that you cannot see the mess, it does not exist. We stand on the shoulders of giants; we truly do.
00:27:47.680
We are lucky to live in an age where the best thoughts of anyone are available to everyone. I'm not saying that we're all Isaac Newton, but you have to start somewhere.
00:28:00.240
We begin as infants of design, but you can grow and learn, and we can teach each other. It isn’t always easy; there’s no guarantee it will be fun every step of the way.
00:28:14.400
But if you persevere, a time will come when your applications will bring you nothing but joy. You will never outgrow the principles of design. If you learn them, you can use them, make them your own, and you can write applications that you'll love forever.
00:28:29.440
Thank you.
00:28:30.799
Is it time for questions? Yeah, we can do a few questions.
00:28:36.000
Wow, he knew the secret! Come on, Dan, what's your question?
00:28:43.600
Yeah, Dan, come on.
00:28:51.039
My question is, how come you don't use a type class instead of a car in that cost class? You’d have left shock dot cost.
00:29:06.720
You know, there are so many ways to solve this; that is a completely reasonable solution.
00:29:12.320
You could do this with inheritance! It depends on what the shock looks like, what else is in shock, which we don’t have a way to show here.
00:29:26.560
But, yeah, you could! I don’t care how you do it; all I want is to do whatever saves the most money.
00:29:38.559
That was a good point! So in your situation, that might be the perfect solution. I'm sorry; I gave him the preview of the fact that I was getting... I would have asked this question.
00:29:53.920
I have one copy of the book; he just got it!
00:30:05.440
Okay, next! Do we have any other questions?
00:30:17.760
Yeah, it’s right now! You can get it on Amazon if you buy through my link!
00:30:27.760
Yes, can you yell it out? We’ll repeat it!
00:30:39.600
Yes, absolutely.
00:30:42.128
Yeah, my goal is to remove the dependencies. I want to write code that can't be affected.
00:30:56.000
As soon as I get here—oh, sorry! She asked if the goal was to isolate those things as omega messes.
00:31:03.440
Right, to push each branch of the case statement out, so this is a classic object-oriented technique.
00:31:14.480
It actually has a name: replace conditionals with polymorphism! That’s a thrilling name, I know; you would have loved that talk!
00:31:30.560
So there’s a twofold goal here: one is to return every mess to a single omega mess with no dependencies so that I don’t have to change that code.
00:31:44.720
My goal is to hide it, but I also want to remove that meta knowledge—the knowledge of number and make my app not depend upon it.
00:31:58.640
I want to be able to change from three to six to ten shocks without affecting any code except the new code that has to be written to do that one thing.
00:32:14.640
The first refactoring returns the omega messes, and the second refactoring changes the case statement, removing the dependencies.
00:32:29.760
The case statement binds in a lot of knowledge, and replacing it with a factory places that knowledge within the factory, not the case statement.
00:32:43.920
More questions?
00:32:59.680
So, his question was, about using... Sorry, I'm going blind.
00:33:02.640
I used a convention that relied on assembling the name out of a string to generate the actual class name. If I had time, I would have done ten different factories.
00:33:19.760
Think of all the things you can do. You can write a hash that has some string or symbol in it where you look up the class name. You can write a hash where on the left side is a method that evaluates to determine whether to use the class name.
00:33:35.040
The number of ways you can use information to generate the name of the class is almost infinite, and different situations call for different ones.
00:33:52.160
You should use the one that's appropriate where you are. Whatever piece of information you have ought to be something you can look up dynamically to figure out the class of the object you want.
00:34:05.680
In a longer version of this talk, there was an example of Ford that used inheritance to solve this problem. If you read all the blurbs, everybody says to prefer composition over inheritance.
00:34:18.320
They say that because there are dependencies with inheritance that can drive you into a corner that’s hard to escape from. The decision between composition and inheritance is one of degree.
00:34:35.680
If the shock class does a lot of things and is composed of different kinds of costers and other types, you decide on a bunch of roles and plug other objects in behind those.
00:34:49.680
Perhaps composition is the right solution. If shocks are nearly all alike, the entire body of the shock class is almost identical, varying only in tiny specializations. In that case, I could use the template method pattern.”
00:35:05.040
For that specialization, inheritance would be a suitable solution. As a matter of fact, if I had this problem and my shock were large, I would probably use inheritance to address it. I know inheritance has a bad name, but I’m an old Smalltalk user; I like inheritance.
00:35:20.000
I probably overuse it a bit; I'm careful as I navigate the inheritance path not to go too far before I switch it back to composition if it proves not to be the right solution!
00:35:34.080
Thank you! Oh, wait! One more question.