Fluent Refactoring
Sam Livingston-Gray
THERE
WILL BE
CODE!
It may be
this small
(1..100).each do |i|
s = ''
fizz = (i % 3)...
Let’s Talk About Math!
2
http://2012books.lardbucket.org/books/elementary-algebra/section_06/5d10b670d78abac93a4572dc0c2afb0f.jpg
3
http://www.wikihow.com/Image:Solve-for-X-Step-12.jpg
4
http://math.about.com/od/algebra/ss/birthday.htm
5
http://www.smosh.com/smosh-pit/photos/16-wonderfully-stupid-test-answers
6
Algebra
7
Algebra Isn’t Math
8
Algebra Isn’t all of Math
9
Algebra ⊂ Math
Math
Algebra
10
http://upload.wikimedia.org/wikipedia/commons/thumb/0/08/NautilusCutawayLogarithmicSpiral.jpg/793px-NautilusCutawayLogarit...
http://upload.wikimedia.org/wikipedia/commons/a/a4/Mandelbrot_sequence_new.gif
12
http://mathequalslove.blogspot.com/2012/12/hexaflexagon-love.html
13
http://think-like-a-git.net/sections/graph-theory/seven-bridges-of-konigsberg.html
14
Math is a Language
Algebra is its Grammar
15
Dick and Jane
16
Fluent Refactoring
17
http://www.kickasslabs.com/2012/04/28/rails-conf-2012-images/100_0304/
Can I get a
definition?
18
Flu·en·cy (noun)
What you can say when you’re
not thinking about how to say it
19
What you can say when you’re
woken up in the middle of the night
with a flashlight in your face
Flu·en·cy (noun)
20
http://dailyawesimity.files.wordpress.com/2013/01/cat-stress-relief-4.jpg
Stress
21
http://www.jamesshore.com/Blog/Proficiencies-of-Planning.html
Level 1
Tarzan at
a party
“Beer!”
“Good party.”
Level 2
Going...
Re·fac·tor·ing (noun)
"...a disciplined technique for
restructuring an existing body of code,
altering its internal struct...
http://refactoring.com/
"...a disciplined
technique for restructuring an existing
body of code, altering its internal
stru...
"Yeah, we're going to have to take
a couple of weeks out of the schedule
for refactoring, and that's probably going
to bre...
"Yeah, we're going to have to take
a couple of weeks out of the schedule
for refactoring, and that's probably going
to bre...
"Yeah, we're going to have to take
a couple of weeks out of the schedule
for refactoring, and that's probably going
to bre...
Re·fac·tor·ing (noun)
"...a disciplined technique for
restructuring an existing body of code,
altering its internal struct...
http://refactoring.com/
"...a disciplined technique for
restructuring an existing body of code,
altering its internal stru...
Tests are implied.
-Katrina Owen,
“Therapeutic Refactoring”
30
Re·fac·tor·ing (noun)
"...a disciplined technique for
restructuring an existing body of
code, altering its internal struct...
Re·fac·tor·ing (noun)
"...a disciplined technique for
restructuring an existing body of
code, altering its internal struct...
Re·fac·tor·ing (noun)
A technique for
restructuring code
without changing behavior
33
Re·fac·tor (verb)
To restructure code
without changing behavior
34
Tell a clearer story
with fewer details
35
Re·fac·tor·ing (noun)
A language that describes
ways to make your code
suck less.
36
THESISES
37
THESISES
THESES
38
THESISES
THESES
THESII
39
THESISES
THESES
THESII
MY POINT(S)
40
You're probably already fluent
in refactoring.
Level 1:
RenameVariable; Rename Method.
41
You can become more fluent in
refactoring.
It just takes practice.
42
Putting in the practice to
become more fluent in
refactoring is worth it.
Because you’ll be able to say more
things when yo...
Refactoring Session
44
Used with:
• Permission
• Obfuscation
• Respect
Production Rails Code
45
Schedule Cable Installs
46
class InstallationsController < ActionController::Base
# lots more stuff...
def schedule
desired_date = params[:desired_da...
class InstallationsController < ActionController::Base
# lots more stuff...
def schedule
desired_date = params[:desired_da...
http://scientopia.org/blogs/whitecoatunderground/2009/06/10/my-head-just-asploded-twice/
Complexity
49
http://shipitsquirrel.github.io/
Ship it!
50
http://shipitsquirrel.github.io/
Ship Shit!
51
http://hyperboleandahalf.blogspot.com/2010/06/this-is-why-ill-never-be-adult.html
~800 lines
52
http://hyperboleandahalf.blogspot.com/2010/06/this-is-why-ill-never-be-adult.html
~800 lines
53
Make the Job Smaller
54
Replace Method with
Method Object
55
class InstallationsController <
ActionController::Base
def schedule
# LOTS OF CODE
end
end
56
class InstallationsController <
ActionController::Base
def schedule
end
end
class ScheduleInstallation
def call
end
end
# ...
class InstallationsController <
ActionController::Base
def schedule
end
end
class ScheduleInstallation
def call
end
end
# ...
class InstallationsController <
ActionController::Base
def schedule
end
end
class ScheduleInstallation
def call
end
end
Sc...
class ScheduleInstallation
def call
# LOTS OF CODE
end
end
60
class ScheduleInstallation
def initialize(controller)
@controller = controller
end
def call
# LOTS OF CODE
end
end
61
class ScheduleInstallation
def initialize(controller)
@controller = controller
end
def call
# LOTS OF CODE
end
def method_...
Code Archaeology
63
if request.xhr?
# ...20 lines...
else
# ...22 lines...
end
64
if request.xml_http_request?
# ...20 lines...
else
# ...22 lines...
end
65
if request.xml_http_request?
begin
if @installation.pending_credit_check?
render :json => #...
return
end
#...
end
else
# ...
if request.xhr?
begin
if @installation.pending_credit_check?
render :json => #...
return
end
#...
end
else
if @installatio...
ion.pending_credit_check?
n => #...
n.pending_credit_check?
= #...
nstallations_path(:city_id => @installation.city
68
check?
eck?
city_id => @installation.city_id, :view => "cale
69
lation.city_id, :view => "calendar") and return
70
if request.xhr?
begin
if @installation.pending_credit_check?
render :json => #...
return
end
#...
end
else
if @installatio...
if request.xhr?
#...
else
#...
end
if request.xhr?
#...
else
#...
end
ZOMG
duplication!!!1!!
72
if request.xhr?
if @installation.pending_credit_check?
#...
end
else
if @installation.pending_credit_check?
#...
end
end
i...
Emphasis
74
if request.xhr?
if @installation.pending_credit_check?
#...
end
else
if @installation.pending_credit_check?
#...
end
end
75
Flatten Nested
Conditionals
source: Michael Feathers,
writing for Dr. Dobbs
76
if request.xhr?
if @installation.pending_credit_check?
render :json => #...
return
end
else
if @installation.pending_credi...
if ajax
if pending_credit_check
render :json => #...
return
end
else
if pending_credit_check
flash[:error] = #...
redirect...
if ajax
if pending_credit_check
render :json => #...
return
end
else
if pending_credit_check
flash[:error] = #...
redirect...
if ajax
if pending_credit_check
render :json => #...
return
end
end
if not ajax
if pending_credit_check
flash[:error] = #....
if ajax && pending_credit_check
render :json => #...
return
end
if (not ajax) && pending_credit_check
flash[:error] = #......
if pending_credit_check
if ajax
render :json => #...
return
end
if not ajax
flash[:error] = #...
redirect_to #...
return
e...
if pending_credit_check
if ajax
render :json => #...
return
else
flash[:error] = #...
redirect_to #...
return
end
end
if p...
if ajax
if pending_credit_check
render :json => #...
return
end
else
if pending_credit_check
flash[:error] = #...
redirect...
if pending_credit_check
if ajax
render :json => #...
else
flash[:error] = #...
redirect_to #...
end
return
end
if pending_...
Exception Handling
86
raise “wtf” if coin.toss.heads?begin
raise “wtf” if coin.t
end
87
begin
raise “wtf” if coin.toss.heads?
end
begin
raise “wtf” if coin.t
rescue => e
raise e
end
88
begin
raise “wtf” if coin.toss.heads?
rescue => e
raise e
end
89
begin
begin
raise “wtf” if coin.toss.heads?
rescue
#...
end
rescue => e
raise e
end
90
begin
begin
raise “wtf” if coin.toss.heads?
rescue
if request.xhr?
raise “tfw” if tuesday?
else
raise “yak” if Moon.gibbou...
begin
begin
raise “wtf” if coin.toss.heads?
rescue
if request.xhr?
raise “tfw” if tuesday?
else
raise “yak” if Moon.gibbou...
begin
begin
raise “wtf” if coin.toss.heads?
rescue
if request.xhr?
# DO NOTHING
else
raise “yak” if Moon.gibbous?
end
end
...
begin
begin
raise “wtf” if coin.toss.heads?
rescue
if request.xhr?
# DO NOTHING
else
# DO NOTHING
end
end
rescue => e
if r...
begin
begin
raise “wtf” if coin.toss.heads?
rescue
# DO NOTHING
end
rescue => e
if request.xhr?
raise “tfw” if tuesday?
el...
begin
begin
raise “wtf” if coin.toss.heads?
end
rescue => e
if request.xhr?
raise “tfw” if tuesday?
else
raise “yak” if Mo...
begin
raise “wtf” if coin.toss.heads?
rescue => e
if request.xhr?
raise “tfw” if tuesday?
else
raise “yak” if Moon.gibbous...
Training Montage
98
class ScheduleInstallation
def call
desired_date = params[:desired_date]
if @installation.pending_credit_check?
cant_sched...
class ScheduleInstallation
def call
desired_date = params[:desired_date]
if @installation.pending_credit_check?
cant_sched...
class ScheduleInstallation
def call
if @installation.pending_credit_check?
cant_schedule_while_credit_check_pending
return...
class ScheduleInstallation
def call
if @installation.pending_credit_check?
cant_schedule_while_credit_check_pending
return...
class ScheduleInstallation
def call
if @installation.pending_credit_check?
cant_schedule_while_credit_check_pending
return...
class ScheduleInstallation
def call
if @installation.pending_credit_check?
cant_schedule_while_credit_check_pending
return...
class ScheduleInstallation
def call
if @installation.pending_credit_check?
cant_schedule_while_credit_check_pending
return...
class ScheduleInstallation
def call
if @installation.pending_credit_check?
cant_schedule_while_credit_check_pending
return...
class ScheduleInstallation
def call
if @installation.pending_credit_check?
cant_schedule_while_credit_check_pending
return...
class ScheduleInstallation
def call
if @installation.pending_credit_check?
cant_schedule_while_credit_check_pending
return...
class ScheduleInstallation
def call
if @installation.pending_credit_check?
cant_schedule_while_credit_check_pending
return...
Under The Rug
110
class ScheduleInstallation
def cannot_schedule_while_#...
if request.xhr?
# ...1 line...
else
# ...2 lines...
end
end
def ...
class ScheduleInstallation
private
def scheduling_failed
if request.xhr?
render :json => {:errors => [#...
else
flash[:err...
Single Responsibility
Principle
113
ScheduleInstallation
ScheduleInstallationAnd
DoOneThingForAJAXRequestsAnd
DoSomethingElseForHTMLRequests
114
ScheduleInstallation
ScheduleInstallation And
DoOneThingForAJAXRequests And
DoSomethingElseForHTMLRequests
115
“Methods, like classes,
should have a single
responsibility.”
-Sandi Metz
116
http://en.wikipedia.org/wiki/File:Bill_%26_Ted%27s_Excellent_Adventure_(Original_Motion_Picture_Soundtrack).jpg
117
http://en.wikipedia.org/wiki/File:Paris_Tuileries_Garden_Facepalm_statue.jpg
118
Single Responsibility
Principle
Every class should have a single
responsibility, and that responsibility
should be entirel...
ScheduleInstallation
ScheduleInstallation And
DoOneThingForAJAXRequests And
DoSomethingElseForHTMLRequests
120
Responder
121
122
InstallationsController
123
InstallationsController
ScheduleInstallation
???
124
InstallationsController
Responder
???
ScheduleInstallation
???
class ScheduleInstallation
def call
private
def cannot_schedule_while_credit_check_pendin
def handle_exception(e)
def sche...
126
Dualism
class Responder
def cannot_schedule_while_credit_check_pending
# ...6 lines...
end
def cannot_schedule_while_credit_check_...
if request.xhr?
# do foo
else
# do bar
end
Replace Conditional
With Polymorphism
129
class Responder
def cannot_schedule_while_credit_check_pending
def handle_exception(e)
def scheduling_failed
def schedulin...
class AJAXResponder
def scheduling_failed
if request.xhr?
render :json => #...
else
flash[:error] = #...
redirect_to #...
...
132
InstallationsController
Responder
???
ScheduleInstallation
???
class InstallationsController
< ActionController::Base
def schedule
responder = if request.xhr?
AJAXResponder.new(self)
el...
LESSONS LEARNED
134
Refactoring is Math
135
Fast Characterization
Tests Rock
136
Embrace Duplication
if request.xhr?
137
0
2
4
6
8
10
Embrace Evil Hacks
138
Perspective Matters
Superficial design flaws
can conceal
fundamental design flaws
139
http://gomakemeasandwich.wordpress.com/2011/10/26/a-yeaf-of-gmmas-where-do-we-go-from-here/
Where Do We Go
From Here?
140
141
142
143
http://www.poodr.info/
144
Practice!
• Play with automated refactorings in an IDE
• Do them manually in the editor (wax on,
wax off)
145
Practice!
• Commit early, commit often:
‘git reset --hard’ is your friend!
• Use throwaway branches
• Write fast character...
Fluent Refactoring
github.com/geeksam
/fluent-refactoring
Sam Livingston-Gray
geeksam@gmail.com
Twitter, Github: @geeksam
...
Upcoming SlideShare
Loading in …5
×

Fluent Refactoring (Lone Star Ruby Conf 2013)

1,551 views

Published on

Fluency is "what you can say without having to think about how to say it." "Refactoring" is a language that describes ways to make your code better. I want to inspire you to learn more of that language, so you can make your code better without having to think about it.

I'll walk you through the process of reworking a 50-line controller action that's hard to comprehend, let alone refactor. We'll tease apart fiendishly intertwined structures, embrace duplication, use dirty tricks to our advantage, and uncover responsibilities—and bugs!—that weren't obvious at first glance.

Published in: Technology, Education
1 Comment
2 Likes
Statistics
Notes
No Downloads
Views
Total views
1,551
On SlideShare
0
From Embeds
0
Number of Embeds
65
Actions
Shares
0
Downloads
8
Comments
1
Likes
2
Embeds 0
No embeds

No notes for slide

Fluent Refactoring (Lone Star Ruby Conf 2013)

  1. 1. Fluent Refactoring Sam Livingston-Gray THERE WILL BE CODE! It may be this small (1..100).each do |i| s = '' fizz = (i % 3).zero? buzz = (i % 5).zero? s << 'Fizz' if fizz s << 'Buzz' if buzz s << '!' if fizz || buzz s = i if s =~ /^$/ puts s end 1
  2. 2. Let’s Talk About Math! 2
  3. 3. http://2012books.lardbucket.org/books/elementary-algebra/section_06/5d10b670d78abac93a4572dc0c2afb0f.jpg 3
  4. 4. http://www.wikihow.com/Image:Solve-for-X-Step-12.jpg 4
  5. 5. http://math.about.com/od/algebra/ss/birthday.htm 5
  6. 6. http://www.smosh.com/smosh-pit/photos/16-wonderfully-stupid-test-answers 6
  7. 7. Algebra 7
  8. 8. Algebra Isn’t Math 8
  9. 9. Algebra Isn’t all of Math 9
  10. 10. Algebra ⊂ Math Math Algebra 10
  11. 11. http://upload.wikimedia.org/wikipedia/commons/thumb/0/08/NautilusCutawayLogarithmicSpiral.jpg/793px-NautilusCutawayLogarithmicSpiral.jpg 11
  12. 12. http://upload.wikimedia.org/wikipedia/commons/a/a4/Mandelbrot_sequence_new.gif 12
  13. 13. http://mathequalslove.blogspot.com/2012/12/hexaflexagon-love.html 13
  14. 14. http://think-like-a-git.net/sections/graph-theory/seven-bridges-of-konigsberg.html 14
  15. 15. Math is a Language Algebra is its Grammar 15
  16. 16. Dick and Jane 16
  17. 17. Fluent Refactoring 17
  18. 18. http://www.kickasslabs.com/2012/04/28/rails-conf-2012-images/100_0304/ Can I get a definition? 18
  19. 19. Flu·en·cy (noun) What you can say when you’re not thinking about how to say it 19
  20. 20. What you can say when you’re woken up in the middle of the night with a flashlight in your face Flu·en·cy (noun) 20
  21. 21. http://dailyawesimity.files.wordpress.com/2013/01/cat-stress-relief-4.jpg Stress 21
  22. 22. http://www.jamesshore.com/Blog/Proficiencies-of-Planning.html Level 1 Tarzan at a party “Beer!” “Good party.” Level 2 Going to the party "Where is the party?" "How do I get to the party?" Level 3 Discussing the party "What happened at the party last night?" Level 4 Charlie Rose "Should parties be illegal?" Levels of Proficiency 22
  23. 23. Re·fac·tor·ing (noun) "...a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior." -refactoring.com 23
  24. 24. http://refactoring.com/ "...a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior." 24
  25. 25. "Yeah, we're going to have to take a couple of weeks out of the schedule for refactoring, and that's probably going to break some stuff." Doin It Rong 25
  26. 26. "Yeah, we're going to have to take a couple of weeks out of the schedule for refactoring, and that's probably going to break some stuff." Doin It Rong 26
  27. 27. "Yeah, we're going to have to take a couple of weeks out of the schedule for refactoring, and that's probably going to break some stuff." Doin It Rong 27
  28. 28. Re·fac·tor·ing (noun) "...a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior." -refactoring.com 28
  29. 29. http://refactoring.com/ "...a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior." 29
  30. 30. Tests are implied. -Katrina Owen, “Therapeutic Refactoring” 30
  31. 31. Re·fac·tor·ing (noun) "...a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior." -refactoring.com 31
  32. 32. Re·fac·tor·ing (noun) "...a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior." 32
  33. 33. Re·fac·tor·ing (noun) A technique for restructuring code without changing behavior 33
  34. 34. Re·fac·tor (verb) To restructure code without changing behavior 34
  35. 35. Tell a clearer story with fewer details 35
  36. 36. Re·fac·tor·ing (noun) A language that describes ways to make your code suck less. 36
  37. 37. THESISES 37
  38. 38. THESISES THESES 38
  39. 39. THESISES THESES THESII 39
  40. 40. THESISES THESES THESII MY POINT(S) 40
  41. 41. You're probably already fluent in refactoring. Level 1: RenameVariable; Rename Method. 41
  42. 42. You can become more fluent in refactoring. It just takes practice. 42
  43. 43. Putting in the practice to become more fluent in refactoring is worth it. Because you’ll be able to say more things when you’re under stress. 43
  44. 44. Refactoring Session 44
  45. 45. Used with: • Permission • Obfuscation • Respect Production Rails Code 45
  46. 46. Schedule Cable Installs 46
  47. 47. class InstallationsController < ActionController::Base # lots more stuff... def schedule desired_date = params[:desired_date] if request.xhr? begin if @installation.pending_credit_check? render :json => {:errors => ["Cannot schedule installation while credit check is pending"]}, :status => 400 return end audit_trail_for(current_user) do if @installation.schedule!(desired_date, :installation_type => params[:installation_type], :city => @city) if @installation.scheduled_date date = @installation.scheduled_date.in_time_zone(@installation.city.timezone).to_date render :json => {:errors => nil, :html => schedule_response(@installation, date)} end else render :json => {:errors => [%Q{Could not update installation. #{@installation.errors.full_messages.join(' ')}}] } end end rescue ActiveRecord::RecordInvalid => e render :json => {:errors => [e.message] } rescue ArgumentError => e render :json => {:errors => ["Could not schedule installation. Start by making sure the desired date is on a business day."]} end else if @installation.pending_credit_check? flash[:error] = "Cannot schedule installation while credit check is pending" redirect_to installations_path(:city_id => @installation.city_id, :view => "calendar") and return end begin audit_trail_for(current_user) do if @installation.schedule!(desired_date, :installation_type => params[:installation_type], :city => @city) if @installation.scheduled_date if @installation.customer_provided_equipment? flash[:success] = %Q{Installation scheduled} else flash[:success] = %Q{Installation scheduled! Don't forget to order the equipment also.} end end else flash[:error] = %Q{Could not schedule installation, check the phase of the moon} end end rescue => e flash[:error] = e.message end redirect_to(@installation.customer_provided_equipment? ? customer_provided_installations_path : installations_path(:city_id => @installation.city_id, :view => "calendar")) end end # lots more stuff... end 47
  48. 48. class InstallationsController < ActionController::Base # lots more stuff... def schedule desired_date = params[:desired_date] if request.xhr? begin if @installation.pending_credit_check? render :json => {:errors => ["Cannot schedule installation while credit check is pending"]}, :status => 400 return end audit_trail_for(current_user) do if @installation.schedule!(desired_date, :installation_type => params[:installation_type], :city => @city) if @installation.scheduled_date date = @installation.scheduled_date.in_time_zone(@installation.city.timezone).to_date render :json => {:errors => nil, :html => schedule_response(@installation, date)} end else render :json => {:errors => [%Q{Could not update installation. #{@installation.errors.full_messages.join(' ')}}] } end end rescue ActiveRecord::RecordInvalid => e render :json => {:errors => [e.message] } rescue ArgumentError => e render :json => {:errors => ["Could not schedule installation. Start by making sure the desired date is on a business day."]} end else if @installation.pending_credit_check? flash[:error] = "Cannot schedule installation while credit check is pending" redirect_to installations_path(:city_id => @installation.city_id, :view => "calendar") and return end begin audit_trail_for(current_user) do if @installation.schedule!(desired_date, :installation_type => params[:installation_type], :city => @city) if @installation.scheduled_date if @installation.customer_provided_equipment? flash[:success] = %Q{Installation scheduled} else flash[:success] = %Q{Installation scheduled! Don't forget to order the equipment also.} end end else flash[:error] = %Q{Could not schedule installation, check the phase of the moon} end end rescue => e flash[:error] = e.message end redirect_to(@installation.customer_provided_equipment? ? customer_provided_installations_path : installations_path(:city_id => @installation.city_id, :view => "calendar")) end end # lots more stuff... end Observations ~800 lines in file ~50 lines in method Longest line: 177 chars Indentation: 4-16 spaces Nested control structures: audit_trail_for begin/rescue/end if/else/end 48
  49. 49. http://scientopia.org/blogs/whitecoatunderground/2009/06/10/my-head-just-asploded-twice/ Complexity 49
  50. 50. http://shipitsquirrel.github.io/ Ship it! 50
  51. 51. http://shipitsquirrel.github.io/ Ship Shit! 51
  52. 52. http://hyperboleandahalf.blogspot.com/2010/06/this-is-why-ill-never-be-adult.html ~800 lines 52
  53. 53. http://hyperboleandahalf.blogspot.com/2010/06/this-is-why-ill-never-be-adult.html ~800 lines 53
  54. 54. Make the Job Smaller 54
  55. 55. Replace Method with Method Object 55
  56. 56. class InstallationsController < ActionController::Base def schedule # LOTS OF CODE end end 56
  57. 57. class InstallationsController < ActionController::Base def schedule end end class ScheduleInstallation def call end end # LOTS OF CODE 57
  58. 58. class InstallationsController < ActionController::Base def schedule end end class ScheduleInstallation def call end end # LOTS OF CODE 58
  59. 59. class InstallationsController < ActionController::Base def schedule end end class ScheduleInstallation def call end end ScheduleInstallation.new.call # LOTS OF CODE 59
  60. 60. class ScheduleInstallation def call # LOTS OF CODE end end 60
  61. 61. class ScheduleInstallation def initialize(controller) @controller = controller end def call # LOTS OF CODE end end 61
  62. 62. class ScheduleInstallation def initialize(controller) @controller = controller end def call # LOTS OF CODE end def method_missing(m, *a, &b) @controller.send(m, *a, &b) end end 62
  63. 63. Code Archaeology 63
  64. 64. if request.xhr? # ...20 lines... else # ...22 lines... end 64
  65. 65. if request.xml_http_request? # ...20 lines... else # ...22 lines... end 65
  66. 66. if request.xml_http_request? begin if @installation.pending_credit_check? render :json => #... return end #... end else # ...22 lines... end 66
  67. 67. if request.xhr? begin if @installation.pending_credit_check? render :json => #... return end #... end else if @installation.pending_credit_check? flash[:error] = #... redirect_to installations_path(:city_id => end begin #... end 67
  68. 68. ion.pending_credit_check? n => #... n.pending_credit_check? = #... nstallations_path(:city_id => @installation.city 68
  69. 69. check? eck? city_id => @installation.city_id, :view => "cale 69
  70. 70. lation.city_id, :view => "calendar") and return 70
  71. 71. if request.xhr? begin if @installation.pending_credit_check? render :json => #... return end #... end else if @installation.pending_credit_check? flash[:error] = #... redirect_to installations_path(:city_id => return end begin #... end if request.xhr? if @installation.pend render :json => #.. return end else if @installation.pend flash[:error] = #.. redirect_to(...) an return end end if request.xhr? #... else #...71
  72. 72. if request.xhr? #... else #... end if request.xhr? #... else #... end ZOMG duplication!!!1!! 72
  73. 73. if request.xhr? if @installation.pending_credit_check? #... end else if @installation.pending_credit_check? #... end end if request.xhr? #... else #... end 73
  74. 74. Emphasis 74
  75. 75. if request.xhr? if @installation.pending_credit_check? #... end else if @installation.pending_credit_check? #... end end 75
  76. 76. Flatten Nested Conditionals source: Michael Feathers, writing for Dr. Dobbs 76
  77. 77. if request.xhr? if @installation.pending_credit_check? render :json => #... return end else if @installation.pending_credit_check? flash[:error] = #... redirect_to #... return end end 77
  78. 78. if ajax if pending_credit_check render :json => #... return end else if pending_credit_check flash[:error] = #... redirect_to #... return end end 78
  79. 79. if ajax if pending_credit_check render :json => #... return end else if pending_credit_check flash[:error] = #... redirect_to #... return end end if ajax if pending_credit_che render :json => #.. return end end if not ajax if pending_credit_che flash[:error] = #.. redirect_to #... return end end 79
  80. 80. if ajax if pending_credit_check render :json => #... return end end if not ajax if pending_credit_check flash[:error] = #... redirect_to #... return end end if ajax && pending_cred render :json => #... return end if (not ajax) && pendin flash[:error] = #... redirect_to #... return end 80
  81. 81. if ajax && pending_credit_check render :json => #... return end if (not ajax) && pending_credit_check flash[:error] = #... redirect_to #... return end if pending_credit_check if ajax render :json => #.. return end if not ajax flash[:error] = #.. redirect_to #... return end end 81
  82. 82. if pending_credit_check if ajax render :json => #... return end if not ajax flash[:error] = #... redirect_to #... return end end if pending_credit_check if ajax render :json => #.. return else flash[:error] = #.. redirect_to #... return end end 82
  83. 83. if pending_credit_check if ajax render :json => #... return else flash[:error] = #... redirect_to #... return end end if pending_credit_check if ajax render :json => #.. else flash[:error] = #.. redirect_to #... end return end 83
  84. 84. if ajax if pending_credit_check render :json => #... return end else if pending_credit_check flash[:error] = #... redirect_to #... return end end if pending_credit_check if ajax render :json => #.. else flash[:error] = #.. redirect_to #... end return end 84
  85. 85. if pending_credit_check if ajax render :json => #... else flash[:error] = #... redirect_to #... end return end if pending_credit_check cant_schedule_while_c return end 85
  86. 86. Exception Handling 86
  87. 87. raise “wtf” if coin.toss.heads?begin raise “wtf” if coin.t end 87
  88. 88. begin raise “wtf” if coin.toss.heads? end begin raise “wtf” if coin.t rescue => e raise e end 88
  89. 89. begin raise “wtf” if coin.toss.heads? rescue => e raise e end 89
  90. 90. begin begin raise “wtf” if coin.toss.heads? rescue #... end rescue => e raise e end 90
  91. 91. begin begin raise “wtf” if coin.toss.heads? rescue if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? end end rescue => e raise e end begin begin raise “wtf” if coin rescue if request.xhr? raise “tfw” if tu else raise “yak” if Mo end end rescue => e if request.xhr? raise e else raise e end end91
  92. 92. begin begin raise “wtf” if coin.toss.heads? rescue if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? end end rescue => e if request.xhr? raise e else raise e end end begin begin raise “wtf” if coin rescue if request.xhr? # DO NOTHING else raise “yak” if Mo end end rescue => e if request.xhr? raise “tfw” if tues else raise e end end92
  93. 93. begin begin raise “wtf” if coin.toss.heads? rescue if request.xhr? # DO NOTHING else raise “yak” if Moon.gibbous? end end rescue => e if request.xhr? raise “tfw” if tuesday? else raise e end end begin begin raise “wtf” if coin rescue if request.xhr? # DO NOTHING else # DO NOTHING end end rescue => e if request.xhr? raise “tfw” if tues else raise “yak” if Moon end end93
  94. 94. begin begin raise “wtf” if coin.toss.heads? rescue if request.xhr? # DO NOTHING else # DO NOTHING end end rescue => e if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? end end begin begin raise “wtf” if coin rescue # DO NOTHING end rescue => e if request.xhr? raise “tfw” if tues else raise “yak” if Moon end end 94
  95. 95. begin begin raise “wtf” if coin.toss.heads? rescue # DO NOTHING end rescue => e if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? end end begin begin raise “wtf” if coin end rescue => e if request.xhr? raise “tfw” if tues else raise “yak” if Moon end end 95
  96. 96. begin begin raise “wtf” if coin.toss.heads? end rescue => e if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? end end begin raise “wtf” if coin.t rescue => e if request.xhr? raise “tfw” if tues else raise “yak” if Moon end end 96
  97. 97. begin raise “wtf” if coin.toss.heads? rescue => e if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? end end begin raise “wtf” if coin.t rescue => e handle_exception(e) end 97
  98. 98. Training Montage 98
  99. 99. class ScheduleInstallation def call desired_date = params[:desired_date] if @installation.pending_credit_check? cant_schedule_while_credit_check_pending return end begin if request.xhr? audit_trail_for(current_user) do if @installation.schedule!(desired_date, :installation_type => params[:installation_type], :city => @city) if @installation.scheduled_date date = @installation.scheduled_date.in_time_zone(@installation.city.timezone).to_date render :json => {:errors => nil, :html => schedule_response(@installation, date)} end else render :json => {:errors => [%Q{Could not update installation. #{@installation.errors.full_messages.join(' ')}}] } end end else audit_trail_for(current_user) do if @installation.schedule!(desired_date, :installation_type => params[:installation_type], :city => @city) if @installation.scheduled_date if @installation.customer_provided_equipment? flash[:success] = %Q{Installation scheduled} else flash[:success] = %Q{Installation scheduled! Don't forget to order the equipment also.} end end else flash[:error] = %Q{Could not schedule installation, check the phase of the moon} end end redirect_to(@installation.customer_provided_equipment? ? customer_provided_installations_path : installations_path(:city_id => @installation.city_id, :view => "calendar")) end rescue Exception => e handle_exception e end end end 99
  100. 100. class ScheduleInstallation def call desired_date = params[:desired_date] if @installation.pending_credit_check? cant_schedule_while_credit_check_pending return end begin audit_trail_for(current_user) do if request.xhr? if @installation.schedule!(desired_date, :installation_type => params[:installation_type], :city => @city) if @installation.scheduled_date date = @installation.scheduled_date.in_time_zone(@installation.city.timezone).to_date render :json => {:errors => nil, :html => schedule_response(@installation, date)} end else render :json => {:errors => [%Q{Could not update installation. #{@installation.errors.full_messages.join(' ')}}] } end else if @installation.schedule!(desired_date, :installation_type => params[:installation_type], :city => @city) if @installation.scheduled_date if @installation.customer_provided_equipment? flash[:success] = %Q{Installation scheduled} else flash[:success] = %Q{Installation scheduled! Don't forget to order the equipment also.} end end else flash[:error] = %Q{Could not schedule installation, check the phase of the moon} end redirect_to(@installation.customer_provided_equipment? ? customer_provided_installations_path : installations_path(:city_id => @installation.city_id, :view => "calendar")) end end rescue Exception => e handle_exception e end end end 100
  101. 101. class ScheduleInstallation def call if @installation.pending_credit_check? cant_schedule_while_credit_check_pending return end begin audit_trail_for(current_user) do if request.xhr? if @installation.schedule!(params[:desired_date], :installation_type => params[:installation_type], :city => @city) if @installation.scheduled_date date = @installation.scheduled_date.in_time_zone(@installation.city.timezone).to_date render :json => {:errors => nil, :html => schedule_response(@installation, date)} end else render :json => {:errors => [%Q{Could not update installation. #{@installation.errors.full_messages.join(' ')}}] } end else if @installation.schedule!(params[:desired_date], :installation_type => params[:installation_type], :city => @city) if @installation.scheduled_date if @installation.customer_provided_equipment? flash[:success] = %Q{Installation scheduled} else flash[:success] = %Q{Installation scheduled! Don't forget to order the equipment also.} end end else flash[:error] = %Q{Could not schedule installation, check the phase of the moon} end redirect_to(@installation.customer_provided_equipment? ? customer_provided_installations_path : installations_path(:city_id => @installation.city_id, :view => "calendar")) end end rescue Exception => e handle_exception e end end end 101
  102. 102. class ScheduleInstallation def call if @installation.pending_credit_check? cant_schedule_while_credit_check_pending return end begin audit_trail_for(current_user) do success = schedule! if request.xhr? if success if @installation.scheduled_date date = @installation.scheduled_date.in_time_zone(@installation.city.timezone).to_date render :json => {:errors => nil, :html => schedule_response(@installation, date)} end else render :json => {:errors => [%Q{Could not update installation. #{@installation.errors.full_messages.join(' ')}}] } end else if success if @installation.scheduled_date if @installation.customer_provided_equipment? flash[:success] = %Q{Installation scheduled} else flash[:success] = %Q{Installation scheduled! Don't forget to order the equipment also.} end end else flash[:error] = %Q{Could not schedule installation, check the phase of the moon} end redirect_to(@installation.customer_provided_equipment? ? customer_provided_installations_path : installations_path(:city_id => @installation.city_id, :view => "calendar")) end end rescue Exception => e handle_exception e end end end 102
  103. 103. class ScheduleInstallation def call if @installation.pending_credit_check? cant_schedule_while_credit_check_pending return end begin audit_trail_for(current_user) do success = schedule! if success if request.xhr? if @installation.scheduled_date date = @installation.scheduled_date.in_time_zone(@installation.city.timezone).to_date render :json => {:errors => nil, :html => schedule_response(@installation, date)} end else if @installation.scheduled_date if @installation.customer_provided_equipment? flash[:success] = %Q{Installation scheduled} else flash[:success] = %Q{Installation scheduled! Don't forget to order the equipment also.} end end redirect_to(@installation.customer_provided_equipment? ? customer_provided_installations_path : installations_path(:city_id => @installation.city_id, :view => "calendar")) end else if request.xhr? render :json => {:errors => [%Q{Could not update installation. #{@installation.errors.full_messages.join(' ')}}] } else flash[:error] = %Q{Could not schedule installation, check the phase of the moon} redirect_to(@installation.customer_provided_equipment? ? customer_provided_installations_path : installations_path(:city_id => @installation.city_id, :view => "calendar")) end end end rescue Exception => e handle_exception e end end end 103
  104. 104. class ScheduleInstallation def call if @installation.pending_credit_check? cant_schedule_while_credit_check_pending return end begin audit_trail_for(current_user) do success = schedule! if success if request.xhr? if @installation.scheduled_date date = @installation.scheduled_date.in_time_zone(@installation.city.timezone).to_date render :json => {:errors => nil, :html => schedule_response(@installation, date)} end else if @installation.scheduled_date if @installation.customer_provided_equipment? flash[:success] = %Q{Installation scheduled} else flash[:success] = %Q{Installation scheduled! Don't forget to order the equipment also.} end end redirect_to(@installation.customer_provided_equipment? ? customer_provided_installations_path : installations_path(:city_id => @installation.city_id, :view => "calendar")) end else scheduling_failed end end rescue Exception => e handle_exception e end end end 104
  105. 105. class ScheduleInstallation def call if @installation.pending_credit_check? cant_schedule_while_credit_check_pending return end begin audit_trail_for(current_user) do success = schedule! if success if @installation.scheduled_date if request.xhr? date = @installation.scheduled_date.in_time_zone(@installation.city.timezone).to_date render :json => {:errors => nil, :html => schedule_response(@installation, date)} else if @installation.customer_provided_equipment? flash[:success] = %Q{Installation scheduled} else flash[:success] = %Q{Installation scheduled! Don't forget to order the equipment also.} end end end if request.xhr? # do nothing else redirect_to(@installation.customer_provided_equipment? ? customer_provided_installations_path : installations_path(:city_id => @installation.city_id, :view => "calendar")) end else scheduling_failed end end rescue Exception => e handle_exception e end end end 105
  106. 106. class ScheduleInstallation def call if @installation.pending_credit_check? cant_schedule_while_credit_check_pending return end begin audit_trail_for(current_user) do success = schedule! if success if @installation.scheduled_date scheduling_succeeded end if request.xhr? # do nothing else redirect_to(@installation.customer_provided_equipment? ? customer_provided_installations_path : installations_path(:city_id => @installation.city_id, :view => "calendar")) end else scheduling_failed end end rescue Exception => e handle_exception e end end end 106
  107. 107. class ScheduleInstallation def call if @installation.pending_credit_check? cant_schedule_while_credit_check_pending return end begin audit_trail_for(current_user) do success = schedule! if success if @installation.scheduled_date scheduling_succeeded end do_post_success_cleanup else scheduling_failed end end rescue Exception => e handle_exception e end end end 107
  108. 108. class ScheduleInstallation def call if @installation.pending_credit_check? cant_schedule_while_credit_check_pending return end begin audit_trail_for(current_user) do if schedule! if @installation.scheduled_date scheduling_succeeded end do_post_success_cleanup else scheduling_failed end end rescue Exception => e handle_exception e end end end 108
  109. 109. class ScheduleInstallation def call if @installation.pending_credit_check? cant_schedule_while_credit_check_pending return end begin audit_trail_for(current_user) do if schedule! if @installation.scheduled_date scheduling_succeeded end do_post_success_cleanup else scheduling_failed end end rescue Exception => e handle_exception e end end end request.xhr? 109
  110. 110. Under The Rug 110
  111. 111. class ScheduleInstallation def cannot_schedule_while_#... if request.xhr? # ...1 line... else # ...2 lines... end end def handle_exception(e) if request.xhr? # ...7 lines... else # ...2 lines... end end def scheduling_failed if request.xhr? # ...1 line... else # ...2 lines... end end def scheduling_succeeded if request.xhr? # ...2 lines... else # ...5 lines... end end def do_post_success_cleanup if request.xhr? # DO NOTHING else # ...1 line... end end end 111
  112. 112. class ScheduleInstallation private def scheduling_failed if request.xhr? render :json => {:errors => [#... else flash[:error] = #... redirect_to #... end end end 112
  113. 113. Single Responsibility Principle 113
  114. 114. ScheduleInstallation ScheduleInstallationAnd DoOneThingForAJAXRequestsAnd DoSomethingElseForHTMLRequests 114
  115. 115. ScheduleInstallation ScheduleInstallation And DoOneThingForAJAXRequests And DoSomethingElseForHTMLRequests 115
  116. 116. “Methods, like classes, should have a single responsibility.” -Sandi Metz 116
  117. 117. http://en.wikipedia.org/wiki/File:Bill_%26_Ted%27s_Excellent_Adventure_(Original_Motion_Picture_Soundtrack).jpg 117
  118. 118. http://en.wikipedia.org/wiki/File:Paris_Tuileries_Garden_Facepalm_statue.jpg 118
  119. 119. Single Responsibility Principle Every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class. 119
  120. 120. ScheduleInstallation ScheduleInstallation And DoOneThingForAJAXRequests And DoSomethingElseForHTMLRequests 120
  121. 121. Responder 121
  122. 122. 122 InstallationsController
  123. 123. 123 InstallationsController ScheduleInstallation ???
  124. 124. 124 InstallationsController Responder ??? ScheduleInstallation ???
  125. 125. class ScheduleInstallation def call private def cannot_schedule_while_credit_check_pendin def handle_exception(e) def scheduling_failed def scheduling_succeeded def do_post_success_cleanup end class Responder end class ScheduleInstallat def call end class Responder def cannot_schedule_w def handle_exception( def scheduling_failed def scheduling_succee def do_post_success_c end 125
  126. 126. 126 Dualism
  127. 127. class Responder def cannot_schedule_while_credit_check_pending # ...6 lines... end def cannot_schedule_while_credit_check_pending if request.xhr? # ...1 line... else # ...2 lines... end end def handle_exception(e) if request.xhr? # ...7 lines... else # ...2 lines... end end def scheduling_failed if request.xhr? # ...1 line... else # ...2 lines... end end def scheduling_succeeded if request.xhr? # ...2 lines... else # ...5 lines... end end def do_post_success_cleanup if request.xhr? # NOP else # ...2 lines... end end end
  128. 128. if request.xhr? # do foo else # do bar end
  129. 129. Replace Conditional With Polymorphism 129
  130. 130. class Responder def cannot_schedule_while_credit_check_pending def handle_exception(e) def scheduling_failed def scheduling_succeeded def do_post_success_cleanup end class AJAXResponder def cannot_schedule_while_cre def handle_exception(e) def scheduling_failed def scheduling_succeede def do_post_success_cle end class HTMLResponder def cannot_schedule_while_cre def handle_exception(e) def scheduling_failed def scheduling_succeede def do_post_success_cle end
  131. 131. class AJAXResponder def scheduling_failed if request.xhr? render :json => #... else flash[:error] = #... redirect_to #... end end end class HTMLResponder def scheduling_failed if request.xhr? render :json => #... else flash[:error] = #... redirect_to #... end end end class AJAXResponder def scheduling_failed render :json => #... end end class HTMLResponder def scheduling_failed flash[:error] = #... redirect_to #... end end
  132. 132. 132 InstallationsController Responder ??? ScheduleInstallation ???
  133. 133. class InstallationsController < ActionController::Base def schedule responder = if request.xhr? AJAXResponder.new(self) else HTMLResponder.new(self) end ScheduleInstallation.new(responder).call end end
  134. 134. LESSONS LEARNED 134
  135. 135. Refactoring is Math 135
  136. 136. Fast Characterization Tests Rock 136
  137. 137. Embrace Duplication if request.xhr? 137 0 2 4 6 8 10
  138. 138. Embrace Evil Hacks 138
  139. 139. Perspective Matters Superficial design flaws can conceal fundamental design flaws 139
  140. 140. http://gomakemeasandwich.wordpress.com/2011/10/26/a-yeaf-of-gmmas-where-do-we-go-from-here/ Where Do We Go From Here? 140
  141. 141. 141
  142. 142. 142
  143. 143. 143
  144. 144. http://www.poodr.info/ 144
  145. 145. Practice! • Play with automated refactorings in an IDE • Do them manually in the editor (wax on, wax off) 145
  146. 146. Practice! • Commit early, commit often: ‘git reset --hard’ is your friend! • Use throwaway branches • Write fast characterization tests 146
  147. 147. Fluent Refactoring github.com/geeksam /fluent-refactoring Sam Livingston-Gray geeksam@gmail.com Twitter, Github: @geeksam 147

×