(jazzy music)
Sandi:So you'd think that writing
object-oriented code was hard.
All you have to do is look at our apps, alright?
We mean well, and we write code that almost always
inevitably we eventually come to hate it.
And the more I think about this, these days,
somehow my job is to think about how to write better code.
And the more I think about it, the more I think that
all of the problems we cause have the same simple solution,
and that when people ask me now
how to write object-oriented code,
I give them one small piece of advice.
I say make smaller things, that's all it is.
Make smaller classes, make smaller methods,
and let them know as little about each other as possible.
And lately I've been on a quest.
I've had this obsession for the last couple of months,
and it's been about conditionals.
There's a lot of code out there
with nasty conditionals in it,
and I've been wondering, when should I replace
conditionals with small objects,
and how should I do this,
and what would happen to my code if I do?
And I was really confident in Miami in November
and I inflicted this obsession upon Jim Weirich,
whom some of you probably knew,
and he pointed me in the direction of the Gilded Rose.
Now this is a Kata, it's apparently
really well known, but I don't get out much,
so I had... (laughter)
never heard of it.
And so, it is so famous that you can just Google it,
and get an explanation of the problem, but I didn't do that.
I wanted to treat this problem as if
it was a real production problem,
and that my only source of information
was the test and the code.
And so I looked at his.
I checked it out of his [repo],
and I looked at the problem,
and I was so interested in it that it became the skeleton
around which I have hung the ideas for today's talk.
I have altered his code just a little bit,
but it's just to make it easier to talk about.
This really is the Gilded Rose Kata,
and here's how it goes.
There's a Gilded Rose class, and it's structured like this.
It has attributes for name, quality, and days remaining,
it sets those in an initializer,
and then there's a tick method.
Now here's the tick method, well actually, no,
that's just the first half of it, here's the rest.
Now, I know you can't read this.
Well, don't even try, even if you can, alright?
This is the whole method.
I just want you to get some sense
of the size and shape of it.
It's a 43-line "if" statement.
And this seems really, really hard to me,
but I am known to be Bullion-impaired.
(laughter)
So, I know that my subjective sense of how difficult this is
to understand is probably not correct,
and so instead I used some metrics.
I ran a complexity metric called "Flog" against it.
So Flog is a metric. OK, what's a metric?
A metric is a crowdsource idea about something.
Right? I have my own opinion about how complex this is,
but I can use this sort of wisdom-of-the-crowd metric,
the Flog metric, which scores...
It's an ABC metric, so it scores assignments,
branches, and conditionals.
It just counts things, and adds them up.
Higher scores are worse.
They indicate a more complex code,
a code that's going to be harder
to understand and reason about.
And so, Flog says the Gilded Rose class scored a 50,
and that one method tick scored a 45.
(moans) Yeah, just hurts, doesn't it?
So, Flog says it's complicated,
but before we go on I want to introduce
a very subjective metric about complexity.
So, I spend a lot of time these days
going to places and looking at code I know nothing about.
People call me up, and I go to their shop,
and I spend a few days.
And as you might imagine,
no one calls me if things are going well.
(laughter) Alright?
And when I get there, they don't ask me
to look at the code they're proud of.
They ask me to look at the most heinous bits of their apps,
the things that have sort of complex,
lengthy contexts in history.
Code that has just absolutely gotten out of hand.
And not only are the explanations long and confusing
because the problem is hard,
but they do that thing that we all do,
you know that thing you do when you have to
explain a bit of code that you wrote
that you're embarrassed about to someone?
You don't just tell them how it works.
You feel compelled to explain
all the reasons why it got that way. (laughter) Right?
You laugh. I do it. I know you do it, too, right?
It just hurts. We hate that.
And so, these explanations are long
and confusing and they have lots of
sort of sideways kind of information.
And there's a point in time, I really mean well,
but there's a point in time during every
explanation when I start feeling like that dog,
Ginger, in this Gary Larson's cartoon
where it starts turning into, blah, blah, blah,
Sandi, blah, blah, blah. (laughs) (laughter)
And then suddenly I get startled back
into awareness when I hear them say,
"So, what do you think we should do
"about this line of code?" (laughs) (laughter)
And it used to terrify me, right?
I felt like I had to understand everything
in order to help with anything.
But it turned out that after a few trips,
I realized that there was a really
simple thing I could do to help me
identify code that they could benefit from changing.
And I call this the "squint test". (laughter)
Here's how it works.
You squint your eyes, you lean back,
and you look at the code.
And we're looking for changes in shape, (laughter)
and changes in color.
Changes in shape mean you have nested conditionals
and they are always going to be hard to reason about.
Changes in color mean that your code is
at differing levels of abstraction,
and it means the story it tells
is going to be hard to follow.
Now, what is it about this code?
Well, it has 16 if statements,
some of those are not equal to them,
and connect something with an "&",
there are three magic strings, they're used all over,
and a number of magic numbers,
I don't even know how many. (laughter)
Now, at least it has tests.
Oh, I'm sorry, here are the magic strings.
These three things:
Brie, Sulfuras, and Backstage passes,
whatever that means.
And it does have tests, and they pass.
Now, there are six skipped tests, alright? (laughter)
So, I don't know what that's about.
And so, I pry open the code, I just look at this first test.
Oh, sorry, the tests cluster around the magic strings,
except for this set, which is for
something called "Normal",
which is never mentioned in the "if" statement. (laughter)
I suspect there's something in an [L's] branch
somewhere that matters here, alright?
So, I pry open the test and I look at it.
Here's one, they all look just like this.
I'm selling something, ok?
Given a Gilded Rose that has this name,
attribute, and quality, those are our three [add-a-readers],
When I tick, in this case, quality goes down by one,
days remaining goes down by one,
they both go down by one.
So, it's as if I'm selling milk,
or eggs, or cheese or something that has a sell-by date,
that's going to expire, where they go bad at some date.
OK, so, I'm still exploring around,
I don't even know what my job is yet,
and I look at the six skipped tests,
and there is something called "Conjured", (laughter)
and they all follow the same pattern,
all of the tests look like given that
when I tick, I see this change.
And at this point, I realize, holy crap,
I'm supposed to change this code. (laughter)
And so I tried, I tried,
very obediently, I tried, but I was a miserable failure.
I couldn't do it.
That 43 lines of statement defeated me.
Every time I went, I would like pry open a Conjured test
and I'd go make some change in that "if" statement
to make that test pass, it would break something else.
I spent hours on it.
Now, I am impaired, but really, it was hard.
It would be hard for you, too, I think.
And so, if changing that "if" statement
was so hard, you have to ask,
why was I trying? Why did I try to do,
what possessed me to try to alter
that incredibly complicated bit of code?
And the answer is, I felt like I was supposed to.
And here's what happens, right?
You write some code, someone asks for a change.
What do we do?
You go look around at the code base
for a code that's the closest thing
to the new thing you're trying to do,
and you put the new code there.
That's how we behave.
Novices especially, they're afraid to make new objects,
so they just go put more code in where they can
find a thing like the thing they're trying to add,
and if that place already has an "if" statement,
they just put another branch on it, right?
That's how it works.
And what happens is, so the natural tendency
of code is to grow bigger,
and bigger, and bigger.
And there comes a point, right?
It gets bigger, and bigger, and bigger.
And there comes a point where it tips,
and at that point it's so big that
you cannot imagine putting code anywhere else.
We have a bargain to follow the pattern,
and if the pattern is a good one, code gets better.
And if the pattern is a bad one, we exacerbate the problem.
Nobody adds a 10-line helper class to a
5000-line active record object.
They just get bigger.
Once they reach a certain size, they just get bigger.
And so, I could not follow the pattern.
I was not good enough to follow the pattern,
and so I decided I was going to make a new pattern,
That I was going to refactor this code.
Now, this is real refactoring according
to the definition of refactoring,
I'm going to refactor this code,
I'm going to change its arrangement
without altering its behavior.
I'm not going to try to add Conjured,
I'm going to try to move this code
around so that I can add Conjured.
And for refactoring, for refactoring
it's like this test with a wall at your back.
You've got to have tests,
or you don't know what you're doing.
And so, I'm just going to start at the top.
I'm going to start with these Normal tests,
and I've got this code.
This is what tick looks like.
Now this is a big, long procedure.
This is not object-oriented code.
In object-oriented code, you have lots of little objects,
and you send messages between them.
And those messages give you a level of
indirection so that you can substitute
different objects at the back.
Messages create seams so that you can do a different thing,
and there is no seam here because this is a procedure.
And so the first thing I have to do
if I want to refactor is I have to make a seam,
and I'm going to do that just by
tracking Normal and Bailing.
At this point, four tests should fail, and they do, alright?
And I am not about to add more code to the tick method,
so I'm just going to send a message to myself,
and four tests should still fail, and they do.
And so, now that I believe that I have
caught that execution path,
I'm going to just break open the first test
and I'm going to write the code and make it pass.
Quality goes down by one, that's easy enough.
I can write that code.
Days remaining goes down by one, and that test passes.
Alright? One down, three to go.
Here's the next test.
In this case, it looks like I'm out of time,
I'm on the sale-by-date, so now quality goes down by two.
So, I'll just make sure my old test keeps on passing,
and I'll write code to make this test pass.
And so, now I think two tests should pass,
so I should have two failures.
But something I just did made some test
I haven't looked at pass, and we love that.
I'm not even going to look at it. (laughter) Alright?
I don't need to understand it, I've got tests. (laughter)
OK, so I'm going to just go,
I'm going to make this one pass, alright?
I'll just open the last one.
So, this one says if the quality is already zero,
don't change it.
And so I'm just going to wrap this whole thing
in an "if" statement, and not do anything if quality is zero.
OK, so now I'm back to green.
That code was not smart or clever,
but that's the whole point.
Once I get to green, I can now refactor.
So my goal is to get to green as quickly as possible.
Red is not when you ponder the abstraction.
Red is when you scramble toward green.
You're trying to reach to look for the
lowest hanging green here,
and so I got there, I'm at green now,
and I confessed to you already that I am Bullion-impaired,
and I have written code that even
I at this moment do not understand,
but now I'm green, so I can refactor.
It looks to me that they always
subtract one from days remaining,
so I'm going to do that first.
I like that story better.
It looks to me like they don't do anything,
I can just bail if quality is zero,
so I can take that whole outer
nesting out of that "if" statement,
and now once I get to here,
I can ponder these two remaining cases.
Are there two cases here?
Is there a case where I subtract one from quality
and two from quality, or is this, I don't think so.
Now that I look at it this way,
I think I always subtract one from quality,
and there's a special case in which
I subtract another, if I'm past the sell-by date.
And so, I can just delete all that,
and now I have this, which is all the same
level of abstraction, and I can understand it.
I love the story this code tells.
It's very simple.
It was easy to get here, and now my test will pass.
Alright, so we're going to do this
over and over again, much more quickly than this one.
I'm going to just take you through a quick reprise here.
So, I create a seam, I send a message to myself,
I tracked all the execution paths into here,
I wrote some code, I hated it,
I got to green as quick as possible,
and then I used green to let me
refactor to code that was sensible,
and now Normal is done.
All the Normal tests passed.
So, now I'm just going to bust right
through all the other cases.
Here's Brie, there's a whole bunch of stuff.
I'm going to turn that into a case
statement so I can track Brie.
There are seven tests, and they're all failing now,
so I have confidence that I have caught them.
I'm going to write the code,
but you don't even need to look at it
because you can see how easy it is, right?
Now that I am only having to write code for
one test at a time, it's pretty simple to write the code.
What I end up with looks like this.
And now Brie is done.
And now a really interesting thing happens.
When this stuff was buried in the
43-line "if" statement,
I had no idea the ways in which Normal and Brie were like,
but now that I'm using this, they seem a lot alike to me.
Now, there's differences sort of in the driving data here,
but the algorithm, you can see the shape
of the algorithm here, and the algorithm is really the same.
And it is very tempting.
We've had the DRY rule browbeat into us so strongly,
it's very tempting at this point.
I'm on a road, I'm on a refactoring road.
It's very tempting now to go on a tangent
and try to clean this up.
Because we believe the greatest,
we've been taught like the greatest virtue is DRY.
And I will tell you that's the wrong idea here.
I'm about to get a lot more information
about what this algorithm looks like,
and I need to finish the refactoring I'm on
before I go on any tangents, so I'm going to notice
that similarity and keep the duplication,
and just keep on going down this path to see where it leads.
And this brings me to my first big point of this talk.
It is far cheaper to keep duplication
than it is to have to mess with the wrong abstraction.
The first rule we teach novices
is don't repeat yourself - DRY.
But have you ever thought about why we teach them that rule?
It's because they can't understand anything else. (laughter)
They don't know anything, but by God,
they can recognize duplication, and it's a good rule.
I'm not saying it's a bad rule,
but I'm saying that now you're grown up, you know more,
and you have enough experience
now to tolerate a little duplication
and wait on a better abstraction.
It's really hard to deal with the wrong abstraction.
I often make a "dup tag".
You know how you can make a "to do" tag?
Like people say, "Oh, I'm going to
"lose track of my duplication."
Well, fix that problem.
Make a "dup tag" and give every "dup" a number,
so if you have the same code in two or three places,
like if that's the sixth instance of duplication,
give it an ID like a database,
and put "dup six" in a bunch of places in your code.
You'll know.
You'll see the duplication if you change a part,
like fix the problem of not being able to find it,
rather than reaching too soon for an abstraction.
It's much easier to deal with a duplication.
Alright, so moving on.
Here's Sulfuras. There's three tests.
You would think if I put my shim in and put an empty method,
I would have three test failures. (laughter)
And yet they all pass.
So, what's this about?
Well, I look at the test and I realize
in all the tests [unintelligible] that
nothing happens if it's Sulfuras.
And again, I had no idea when I was
looking at that 43 lines of statement
that somehow it all asserted that nothing happened,
so it turns out this is the code that makes the tests pass. (laughter)
How nice is that? We love that, OK?
So here's Backstage, and there's a whole bunch of these,
and it looks like this.
That's the code that makes the tests pass.
And so now, we're totally back to green.
This looks exactly like it did when I started.
And this is what I got.
I put this case statement in the front,
and it tracked all the execution paths.
I have a bunch of methods that look like this
that I created and added to the Gilded Rose class,
and the rest of the tick methods still
contains that monstrous 43-line "if" statement,
which I don't understand, but I no longer need,
so I'm just going to delete it, it's gone.
Now if you don't have good tests, this may freak you out, (laughter)
but then if you feel freaked out by this
and you don't have good tests,
you're really making a choice here, right?
If you have code that you don't understand
and that you're afraid to change,
you can keep it forever, if you think that's a good idea,
or you can put some kind of test [harness)
around it so that you can refactor,
but keeping it forever is not really a good choice,
so you want to get to the point where
you have confidence that you can safely refactor,
and it means you never have to understand that code,
you can do characterization tests around the edges,
so that you'll have green,
you'll have a wall at your back for tests,
and then you can refactor your way,
to the point where you can delete
the code that you don't understand.
And the moral of this story is that
small methods are simple.
Here we have it.
This is the code we just wrote.
This is the squint test version, don't try to read it.
This is the code we just wrote on the right,
and this is how we start it on the left.
You notice that the shape is flat,
and the colors are starting to cluster.
Now, again I believe in metrics, because
I know that my personal notion of what
is simple or complex is just my opinion,
and I totally know that metrics are fallible,
but human opinion is no more precise.
And that metrics are kind of a crowdsource idea
of what a bunch of people thought a metric could be.
It is a useful data point for me to compare to my own.
The original class Flogged to 50,
and this new class Flogs to 40,
but that overstates its complexity
because now there's a bunch of methods,
and the most complex method is Backstage and if Flogs to 12.
This code is way simpler.
Well, this is great, and you'd think that
everyone would just do this.
And so, it's an interesting question why they don't.
Now, one of the things I already told you,
I already gave you one reason, right?
We do more of what's there.
And so, the tendency is to add more
to the "if" statement, if that's there,
but I think there's another reason
why we don't undertake these refactorings,
and it's because of this.
I'm just going to make the 50 smaller and move it over.
It took me 10 refactoring steps to get
from the big conditional to a bunch of small methods,
and here's the Flog score of all the intermediate steps.
All the intermediate refactorings
made code more complicated.
I know that I'm going to get to that 40.
I understand the principles of object-oriented design,
and I know the value of small methods,
and because of that, I believe in the refactorings,
and that lets me tolerate the intermediate complexity.
But if you don't know, if you haven't learned
about the value of small methods,
it's hard to undertake those intermediate steps.
They seem like academic things that
people will do that are for some pie-in-the-sky
principle that don't improve code,
but I can promise you that if you can see
far enough to see to the end,
this intermediate complexity leads to ultimate simplicity.
And, so now I'm going to circle back around my task,
now that I've done this refactoring,
I can circle back around my original task
which is to implement Conjure.
How should I do this?
Here's what I got, I've got this.
Should I do that?
It would be easy, it would be really easy.
The answer to that is "no", I should not do that.
That is not the way I should solve this problem,
and it's because this code is not open/closed.
It is not open for extension, and closed for modification.
Open/closed supplies the "O" in [solid], and it is,
and I'm going to say it right out loud,
it's a principle of object-oriented design.
It's one of the pieces of cumulative wisdom
created by folks who've written
a mountain of object-oriented code,
and they have experienced every
possible kind of programming pain.
And over time, they have noticed some principles,
and they developed a style guide
about how to organize code.
That's what object-oriented design is.
That's what the rules of object-oriented design are.
It's a style guide about how to organize code
with all the obvious tradeoffs,
all the places where you can make your own decisions.
In this case, you can feel free to ignore their discoveries,
in which case you'll get to experience
all that pain over again for yourself.
That's what will happen.
On the macro level, this style guide says
it's best to arrange code so that adding new behavior
does not require that you edit existing code.
I know that seems impossible.
I'm going to say it again, right?
Open/closed says you ought to be able to add new behavior
without editing existing code.
Now, forget about how impossible that seems for a minute.
I just want you to imagine something for me.
Imagine the world, imagine your apps, if that is true.
Imagine that you can add new behavior
without editing existing code.
Think about what that means.
It means you always have green tests,
it means you are always safe,
it means you never cause some
distant and unrelated side-effect.
That is a sweet, sweet world,
if your code is open/closed.
And so, on the macro level, we are trying to get
to the point where we can add new behavior,
without editing existing code.
And on the micro level, what that means here,
right now, in this code,
is that when we see methods that have
a repeating prefix or repeating suffix,
there is a tortured object in there
that's trying to get out. (laughter)
Right here, in this place,
you're about to make a decision
that's going to have consequences
that echo through your code base forever.
Are you going to write procedures,
or are you going to trust objects?
If you insist on having all the logic visible,
right here where you can see it,
you are insisting really on knowing
both the condition on which you switch,
and the thing that you do,
the action that you take when that switch happens.
If you're uncomfortable, and unless
you know both those things at once in this file,
under your eyes, in this code, then you're going to
be forced to add a new method right here.
You have to put that conjured tick method right here.
But if you don't, if you're OK with that,
you can listen to object-oriented design.
It says that when you have differing prefixes,
and common suffixes, then what you really have
is a normal class that ought to have a method tick,
and a Gilded Rose ought to be holding on
to an instance of it.
And it is real easy to right that code.
If you can think of that thing, thinking of the thing
is far harder than writing the code.
Here's how the code looks.
I've got this Normal tick method,
I'm just going to call it "tick",
I'm going to put in a Normal class,
I'm going to throw a cruft in there to get
the initialization and the attributes defined,
I'm going to go back into Gilded Rose
and the Normal tick method there,
I'll get an instance of my new class,
and I'll forward this message - boom, that's it.
Alright, well, so I've got this,
so Normal is an object, but nothing else is,
and I'm about to go back on the path where
I have to increase intermediate complexity,
because look what just happened, alright?
My new Normal tick method looks like this.
It uses this item class.
But Brie, the Brie tick method is still
calculated inside the Gilded Rose.
The quality and days remaining are part
of the public API for Gilded Rose,
and so now I have to say, well,
if I have an item, go get the item's quality,
otherwise, get the one I know about.
And I have to do the same thing for days remaining, alright?
It looks messy, but it's short-term, it will go away.
And so, let's just walk through all the other objects.
Now that you understand this pattern,
it's really easy, right?
Class Brie, move method tick,
put the cruft in there, forward the message.
Easy enough.
This is really interesting.
Now some trust is coming into play, right?
I have an empty method, look what I have to do.
Make a new class, put the method in,
pry this method open, get an instance
of that class, forward the message.
You can be forgiven for being suspicious about this,
but if you trust the refactorings,
you have confidence that this is
going to turn out well in the end.
I'm not going to diverge, I'm not taking a detour,
I'm going to go all the way down
this path and finish this refactoring.
Backstage, I'll make the tick method,
I'll make that stuff, I'll do the forwarding.
Alright, so now they're all objects and I've got this.
And I'm back here.
So now, in the beginning, I moved logic
into methods of their own,
because I didn't want to put a bunch
of code in this case statement,
but now that I have objects, everything is simpler,
and I'm going to just start rewinding my decisions.
I'm just going to delete the method,
and shove the code that used to be in the method
back up in the branches of the case statement.
We'll do that.
Now, earlier I said that duplication was
cheaper than the wrong abstraction,
but now we're starting to see abstractions,
and I'm just going to go and abstract away some duplication.
I'm going to put the cruft back up in here
so in Gilded Rose, I no longer need that.
What I really need to do is be able to get an item.
And the way I need to get an item is that.
That's how I'm going to get it.
If I just knew the class name,
I could send that message to it, and it would work.
And it's actually really easy to figure out the class name.
The code's already here.
It's this, there it is.
That will get me the class name back,
so if I just give that a name,
I can send that message to myself,
and now I have the right kind of item.
I don't need a name anymore, so that got simpler.
So now, I have separated the reason I'm switching
from the thing I do when I switch.
And I can just forget about what's
inside that class [form] method.
I don't really care anymore.
It just answers the right class.
It's going to work.
It's going to hand back a thing that can
answer the message I'm going to send to it.
And now tick looks like that and these now,
so I'm rewinding the complexity,
I don't need this anymore.
I have items in every case, so I'll get rid of all that.
And now here's the whole body of code that I have.
I'm holding an instance of the correct item object,
and I just sent it the tick method.
so, we have four different, down at the bottom there,
you can see we have four different kinds of item classes,
but from Gilded Rose's point of view, item is a role.
It doesn't think of it like this, it thinks of it like that.
You just need someone in there that can answer that API,
that knows those messages, it's a [duck] type, if you will.
And if you look at the code I have now,
the message passing works like this.
All these messages get forwarded,
and if you had a Foo that had a Gilded Rose
that sent those messages, it would look like this.
And so, now I'm in a situation like this.
When an object's only purpose is to
forward messages somewhere else,
you have to wonder if it justifies its existence.
This actually is a code [unintelligible] that has a name,
and its name is Middleman.
So, if that's all the Gilded Rose does,
it probably shouldn't exist, but it turns out
it still does something important.
Given a string like Normal, it can figure out what
item class, what class plays the appropriate item role.
And so now, I'm going to use another word
that you should love, you should love this word.
Gilded Rose, the only thing that
Gilded Rose is is an item factory.
I just need to figure out how to get the right object,
and then I can send it a message.
We've simplified our problem by
separating the thing I'm switching on
from the thing I do when I switch.
We've divided those things in half,
so I can make the code [unintelligible] less,
and we can do smaller things.
I don't need to know what they do,
I just need to know how to get the right one.
And so, I'm going to change this code to reflect the reality.
I'm going to make Gilded Rose a module.
I'm going to say four, some people put new.
They make a new method on module,
and I just can't bear that,
but it's OK with me if you do it that way.
So, I have to make it a class method
because I'm calling it.
I'm no longer keeping an instance of anything,
so I don't need an [add-a-reader].
All these Middleman messages now,
since you're really going to talk to the item
that you get back when you call four,
all these messages, they just go away.
So now, this is what we have.
And the way you use it is you send four
to Gilded Rose, and it gives back an item,
and it's the item that you talked to.
And so, now that we've fixed the Gilded Rose,
I'm going to turn my attention to the
classes that play the item role.
There's a lot of duplication here that
we've been tolerating for a long time.
They all have this in them.
And I'm going to create an inheritance hierarchy,
and clean that up.
I'm going to make a little item class,
push all that stuff up to it, then all these guys,
I can delete that code from all these guys,
and make them subclasses of item.
Now, despite what you may have heard,
inheritance is not evil,
and I can tell you exactly when it's safe to use it.
Now here's what you want.
You want a shallow, narrow hierarchy,
you don't want it to be deep,
and you don't want it to be wide, alright?
Shallow and narrow, you would like the subclasses to be,
OK, I will say this twice.
You would like the subclasses to be
at the leaf nodes of your object graph, right?
So, you have objects, and you've got other objects,
and you've got other objects,
and down at the end of your sort of tree,
there are objects that don't know about any other things.
Right? So we want the subclasses to be
the leaf nodes of the object we have to be at the edge,
and we want all the subclasses to use
all the codes in the superclass.
Now, I'm going to repeat that again.
Shallow, narrow, subclasses at the leaf nodes,
and subclasses use all the behavior in the superclass.
If that is the problem that you have,
there is no better solution than inheritance,
and you are free to use it.
So, however, although I love inheritance,
I use it in appropriate ways, and it is not evil,
but sometimes we are. (laughter)
You might be.
And it's easy to get inheritance wrong,
and this tree has a little problem,
and it's this, I don't like this.
The public API of item is quality of days remaining,
and the public API of those four subclasses
contains one additional method tick.
And I think that superclass ought to play the item role,
which means to me it's got to implement tick.
And the question then becomes,
what is the appropriate implementation
of tick to put in the superclass?
You could define tick and have it raise an error
that says subclasses have to implement tick.
You could do that, I do that sometimes,
but here I think there's a default implementation
that's appropriate, and it's this.
Do nothing.
It's perfectly OK with me, tick to do nothing.
And now, I did that because the
inheritance heirarchy bothered me,
and I'm just removing code now.
Now that I've done that, you might notice something
about Sulfuras' implementation of tick.
It overrides item, it subclasses item to
override tick to do exactly what the superclass does.
And what that means is that here
it would be equally correct to say this,
which means that this class is not necessary
and all the intermediate complexity that I created
as I was following this refactoring just went away.
There is no more Sulfuras class.
So, I'm going to do one last thing.
We're almost finished here.
So, this case statement contains
two different types of information.
It contains a set of string to class mappings,
and it contains the algorithm to hook them up.
And I contend to you that case statements
are meant for business logic,
and this doesn't really feel like business logic.
This feels like configuration information.
And so, I'm just going to extract configuration data here.
I'm going to make a hash, and then I'm going to change
the algorithm to just be the algorithm that uses that hash.
Now, in real life, this would probably go
through some transitions where now the hash
can change independently of the algorithm
that matches these things up,
and if you find the hash changing a lot,
you might be tempted to maybe make it a Yamo file,
and if you find the Yamo file changing a lot,
you might be tempted to put in the database.
Now I can vary that data independently of
this rule about how they get hooked up together.
And so, that's it, that's the whole refactoring.
We've got a bunch of small objects
now instead of small methods.
Here's the whole code.
In the Gilded Rose module, there's an item class,
and then there's three item subclasses,
each of which contains a tick method.
There's a set of configuration information
that's used by this algorithm to decide
what item class is appropriate for what string.
Here's the squint testable version of small objects,
and this is it compared to the original big conditional.
Now that's interesting that in the small objects string,
it looks like it's nested too deep, but it's not.
I just have the classes inside the modules.
Right? So, that is only really one level of indenting.
The more interesting comparison here is
the squint test between the intermediate solution,
the small method solution, and the small object solution.
Notice that small objects is a little bit longer,
but the colors are clustered more tightly together.
So we have really distilled the things
that change together in single places.
Here's the Flog scores that we used to have.
So, OK, I have time to make you guess.
I made a bunch of small objects...what's the Flog score?
Male:Well, 15.
Sandi:But you know what? OK, here.
What's in the intermediate, we'll come back to that.
I like that 15 guess, that was an excellent guess,
and you'll know why in a minute.
Here's the intermediate complexity scores.
Alright, so I've got this.
That 33 vastly overstates the complexity
of the final solution, and it's because of this.
When you have the first [string] was one class,
the whole Gilded Rose class,
and you gotta kind of know all about that class.
And the second solution of the small methods
it was the Gilded Rose version two, right?
It was a single class, and you gotta kind of know,
you've gotta hold that class in your head.
This third solution is a bunch of small classes,
it's a bunch of different classes,
and you don't need to reason about all of them at once.
As a matter of fact, you really only need to
read the most complicated object in there,
and the most complicated class is a backstage class
and it Flogs to 12, close to the 15,
and the average complexity of the set of
classes in that final solution is seven.
And so, I contend to you, the complexity
has fallen by 75% because I made many small objects.
And so now I'm going to circle back around
to my task, implement Conjured.
Take a minute and imagine how to do it.
There's a code that makes all the tests pass.
Here's how to use it.
And now we're done.
Alright, so, summary.
When you are new at this, they told you DRY.
Right? Don't repeat yourself.
And I'm not saying it's bad,
and I'm not saying that duplication is good,
but I'm telling you that if your choice
is between duplication and the wrong abstraction,
you should choose duplication.
Trying to fix a problem by increasing
the complexity of the wrong abstraction
is like chasing a beach ball in the outgoing tide.
Every time you take a stroke, it recedes ahead of you,
and pretty soon, you're out way over your head.
It's very hard to fix those problems.
Next, don't try to get to the future.
Open/closed, the right code that can
adapt to the future when it arrives.
New requirements, this requirement to implement Conjured
was the impetus to make a change.
It gives you the information you need about how
to make a choice about how to rearrange your code now,
so that you can do the next thing.
Kent Beck has a wonderfully succinct way to put this.
He says, "Make the change easy,
"and then make the easy change."
He actually put it a little bit longer.
He said, "Make the change easy, this might be hard,
"and then make the easy change."
And so, we spent 99% of this talk making the change easy,
and then it took one slide to make the easy change.
At the core of this, at the underpinnings of all this
is the idea of making small objects,
making objects that had a single responsibility.
And finally, trust the principles of object-oriented design.
They let you predict the consequences
of your code arrangement choices,
and learning something about what those consequences are,
is going to let you raise your game.
Metrics are useful, but they're fallible,
but opinions are no more precise,
so use metrics to give you another body of information
about how complicated your code is,
and then learn the rules of object-oriented design
so that you can choose which direction you want to go in.
Intermediate refactorings often make code more complicated,
but if you know the rules, you can
trust yourself to work through complexity,
and finally reach more open/closed code that's simpler,
and smaller, and that lets you have straightforward,
changeable, beautiful code.
I'm Sandi Metz, I wrote this book,
I'm writing this book, (laughter)
It'll be in the slide deck.
I'm teaching in London.
There's a public course in London coming up in June or July,
in case you're from over there.
Thanks to you all, and thanks to Jim Weirich
who gave me this Kata.
(applause)
(jazzy music)