(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)