Successfully reported this slideshow.

Fluent Refactoring (Lone Star Ruby Conf 2013)

3

Share

Loading in …3
×
1 of 147
1 of 147

Fluent Refactoring (Lone Star Ruby Conf 2013)

3

Share

Download to read offline

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.

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.

More Related Content

Related Books

Free with a 14 day trial from Scribd

See all

Related Audiobooks

Free with a 14 day trial from Scribd

See all

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

×