First 3 refactoring methods I learn:
- Decomposing Conditionals
- tbd
- tbd
Get a copy if Refactoring: Ruby Edition
type out the classes listed on page 2, 3, and 4.
Add them as a single long file, like so:
class Movie
end
class Rental
end
class Customer
# other stuff
def statement
# other stuff
end
end
It'll take me just a few minutes. You've already spent more time flipping through this book than the time you'll spend doing:
mkdir refactoring_in_ruby
cd ref[tab]
git init
hub create
$CODE_EDITOR_OF_CHOICE ch_01.rb
Boom. I use ZSH, and if I do cmd+shift+e
, it toggles time-stamps on every terminal command you ever run.
IF YOU USE ZSH! ONLY IF YOU USE ZSH! try that keyboard combination, see if it works. NOW:
Time how long it takes from when you run the mkdir
command to now, _after typing out the classes contained in this book.
why must I type them, Josh
We're trying to get quick at slinging good code around here, ideally at the speed we can type, for so many reasons.
So, I'm doing this right along with you, as a bit of a spirit-guide and 'proof of concept'. Some of you might think I "know what I'm doing", or "have an idea how to write good code" or "am a minimally competant software developer" or "know fractionally more than an almost complete novice", and in many practical ways, the answer is "you're so wrong, so if I'm ever at a spot that you feel confident saying
I want to be there too
you should listen to what I say"
anyway, that tells me I am at
and making the commit with the sha
of:
6eb02c1
[...]
oh yeah, did you already decide how you'll test this? I was thinking of something like:
movie = Movie.new(args)
rental = Rental.new(args)
customer = Customer.new(args)
customer.statement
# return big long statement here
That's it, yeah?
So, NEXT! after writing all this shit in our code editor, we'll pick up this readme, and chapter 2.
psst:
# pry -r ./ch_01.rb
movie = Movie.new("Jaws 2", "price_code")
=> #<Movie:0x000000010fb45428 @price_code="price_code", @title="Jaws 2">
rental = Rental.new(movie, 3)
=> #<Rental:0x000000010fb9b4b8 @days_rented=3, @movie=#<Movie:0x000000010fb45428 @price_code="price_code", @title="Jaws 2">>
customer = Customer.new("Josh Thompson")
=> #<Customer:0x000000010fbcafb0 @name="Josh Thompson", @rentals=[]>
customer.add_rental(movie)
=> [#<Movie:0x000000010fb45428 @price_code="price_code", @title="Jaws 2">]
customer.statement
NoMethodError: undefined method `movie` for #<Movie:0x000000010fb45428 @title="Jaws 2", @price_code="price_code">
case element.movie.price_code
^^^^^^
from /Users/joshthompson/personal/refactoring_martin_fowler/ch_01.rb:39:in block in statement
fuck. Well, I am only about 15 minutes into this. DO THIS YOURSELF! If you're reading, double your expected time, but I think it'll be worth it.
Did you take a break?
I did. This is at least two hours after the above chunk. I just let undefined method movie on 'element'
percolate. Finished dinner. Went grocery shopping.
Re-read some of my book that I'm copying this from. Evaluated how good and bad myh over-all decision-making in life is. The normal stuff. Took a hit, had some wine. Thinking about dopamine.
Anyway, when you run pry -r <file>
or ruby <file>
, with the properly-formatted "interaction pattern" at the end of the file, you can kinda TDD some stuff. A poor man's minitest.
Having some trouble with Movie.NEW_RELEASE
being legit.
undefined method `NEW_RELEASE' for Movie:Class
I feel so strongly that this is wrong, for a few reasons.
http://arxiv-export-lb.library.cornell.edu/pdf/2011.08130
# s**t I tried
movie = Movie.new("Jaws 2", "price_code")
movie = Movie.new("Jaws 2", "REGULAR")
movie = Movie.new("Jaws 2", REGULAR)
# what worked
movie = Movie.new("Jaws 2", Movie::REGULAR)
therefore:
movie = Movie.new("Jaws 2", Movie::Regular)
movie_1 = Movie.new("Jaws 2", Movie::NEW_RELEASE)
movie =_2 Movie.new("Jaws 2", Movie::CHILDRENS)
movie = Movie.new("Jaws 2", Movie::Regular)
So a simple version, and what I'm putting at the bottom of the file, hoping to get this first round of statement to print...
# speculative, how I "should" have read the interaction pattern out of the book...
movie = Movie.new("Jaws 2", Movie::REGULAR)
rental = Rental.new(movie, 3)
customer = Customer.new("Josh Thompson")
customer.add_rental(rental)
customer.statement
# printed statement
Lets see if I'm right, 12 minutes into this session. (cmd+shift+e
in Zsh)
hell yes. OK, this commit prints out exactly what I want:
$ ruby ch_01.rb
Rental Record for Josh Thompson
Jaws 2 3.5
Amount owed is 3.5
You earned 1 frequent renter points
upon this modest foundation, we shall build castles.
Did you taste the taste of luxury in your last accomplishment? Did you feel a thrill? If not, how traumatized on you? On a scale of 8-10, and you can go above 10?
Do the following:
git clone <url>
git checkout 9ed150a
ruby ruby ch_01.rb
# look at output in your motherloving terminal
# cd .. if you need to run the above commands, or:
git clone git@github.com:josh-works/refactoring_martin_fowler.git
cd refac[tab][return]
ruby ch[tab][return]
# see output
Caught up? We good? this is huge. huge huge huge. You're going to practice the real strokes of top-level refactoring. I can "smell" some of Sandi Metz in this approach, already, just in coming up with this interaction pattern.
Let's continue on to pages 5 and 6
in which we ponder our decisions
OK, I am proud in how strongly I feel like I might be channeling Sandi Metz, when I say:
In as sincerely a way as possible, I honestly believe I'm as dumb as a stump.
Do with that as you will. Some say its a sign of grandiosity and narcissism, others say
How could you be 'dumb as a stump', josh, I don't believe you, I have ascribed #{DESIRABLE_ATTRIBUTE} do some aspect of who you seem to be?
Lol. First, I might try to convince you of some undeniably latent intelligence to be found in a stump, and then I'd convince you of some unavoidably-bad, what else could you have been doing with your life
lack of meaningful intelligence in my decision-making. Absolve me or not, it is what it is.
Anyway, here's how I envision a test being written, typed at the speed of my typing, typos and all.
What would you write?
Is this intelligible to you?
result = "
Rental Record for Josh Thompson
Jaws 2 3.5
Amount owed is 3.5
You earned 1 frequent renter points
Rental Record for Josh Thompson
Jaws 2 3.5
Jaws 2 2
Amount owed is 5.5
You earned 2 frequent renter points
"
movie = Movie.new("Jaws 2", Movie::REGULAR)
rental = Rental.new(movie, 3)
customer = Customer.new("Josh Thompson")
customer.add_rental(rental)
puts customer.statement
movie_2 = Movie.new("Jaws 3", Movie::NEW_RELEASE)
rental_2 = Rental.new(movie, 2)
customer.add_rental(rental_2)
customer.statement
# require minitest/class shit
assert_equal customer.statement, result
and here should exist our bullet-proof test-suite that might support the rest of our refactorings.
Here lives my very successful output, that only took three or four failed attempts to arrive upon:
$ ruby test/ch_01_test.rb
Rental Record for Josh Thompson
Jaws 2 3.5
Amount owed is 3.5
You earned 1 frequent renter points
Run options: --seed 7852
# Running:
Rental Record for Josh Thompson
Jaws 2 3.5
Amount owed is 3.5
You earned 1 frequent renter points
F
Fabulous run in 0.020355s, 49.1280 runs/s, 49.1280 assertions/s.
1) Failure:
WtfTest#test_everything [test/ch_01_test.rb:28]:
--- expected
+++ actual
@@ -1,5 +1,9 @@
"Rental Record for Josh Thompson
-\tJaws 2\t3.5
-\tJaws 2\t2
-Amount owed is 5.5
-You earned 2 frequent renter points"
+ \tJaws 2\t3.5
+ Amount owed is 3.5
+ You earned 1 frequent renter points
+ Rental Record for Josh Thompson
+ \tJaws 2\t3.5
+ \tJaws 2\t2
+ Amount owed is 5.5
+ You earned 2 frequent renter points"
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips
Gonna tweak it:
shit finally got it. Look at this:
$ ruby test/ch_01_test.rb
Run options: --seed 11436
# Running:
F
Fabulous run in 0.012581s, 79.4849 runs/s, 79.4849 assertions/s.
1) Failure:
WtfTest#test_everything [test/ch_01_test.rb:37]:
--- expected
+++ actual
@@ -2,5 +2,4 @@
\tJaws 2\t3.5
\tJaws 2\t2
Amount owed is 5.5
-You earned 2 frequent renter points
-"
+You earned 2 frequent renter points"
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips
✘ joshthompson@Joshs-MacBook-Pro ~/personal/refactoring_martin_fowler main ±
$ ruby test/ch_01_test.rb
Run options: --seed 51658
# Running:
.
Fabulous run in 0.001011s, 989.1197 runs/s, 989.1197 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
In a moment, I'll share a screenshot with you. I suspect you'll instantly know if you're seeing this or not. If you are, quest fearlessly onward. If not, stop and get it. It might take some time, but it'll be worth it. Find the difference between your code and mine:
Have you been enjoying my information hierarchy? Lol.
I wonder if "decompose a method" is a refactoring pattern... flips through index and yep...
Decompose Conditional Refactoring... [example]...[overview]
Decomposing conditional expressions [example, goals, overview, step-by-step]
Hell. Yes. I'm gonna look at this code, then figure out how I could apply the decomposing conditional
refactors to it.
Ch 9, P 261. Read it, then pick up at page 7 again. Lets see if we can do a refactor, and if our tests can keep us happy.
Boom. Extracted the conditional into the amount_for(rental)
method. It was amount_for(element)
, but that was confusing. Done with page 11, and the key bits of the class look like:
class Customer
# [...]
def statement
total_amount, frequent_renter_points = 0, 0
result = "Rental Record for #{@name}\n"
@rentals.each do |rental|
this_amount = amount_for(rental)
# add frequent renter points
frequent_renter_points += 1
# add bonus for a two day new release rental
if rental.movie.price_code == Movie::NEW_RELEASE && rental.days_rented > 1
frequent_renter_points += 1
end
# show figures for this rental
result += "\t" + rental.movie.title + "\t" + this_amount.to_s + "\n"
total_amount += this_amount
end
# add footer lines
result += "Amount owed is #{total_amount}\n"
result += "You earned #{frequent_renter_points} frequent renter points\n"
result
end
private
def amount_for(rental)
result = 0
# determine amounts for each line
case rental.movie.price_code
when Movie::REGULAR
result += 2
result += (rental.days_rented - 2) * 1.5 if rental.days_rented > 2
when Movie::NEW_RELEASE
result += rentals.days_rented * 3
when Movie::CHILDRENS
result += 1.5
result += (rental.days_rented - 3) * 1.5 if rental.days_rented > 3
end
result
end
end
Git commit, going to bed. I'm another 15 minutes in, or about 1 hour total for the book today.