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