147
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

Fluent Refactoring (Lone Star Ruby Conf 2013)

Embed Size (px)

DESCRIPTION

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.

Citation preview

Page 1: Fluent Refactoring (Lone Star Ruby Conf 2013)

Fluent RefactoringSam Livingston-Gray

THERE WILL BE CODE!

It may bethis 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 send

1

Page 2: Fluent Refactoring (Lone Star Ruby Conf 2013)

Let’s Talk About Math!

2

Page 7: Fluent Refactoring (Lone Star Ruby Conf 2013)

Algebra

7

Page 8: Fluent Refactoring (Lone Star Ruby Conf 2013)

Algebra Isn’t Math

8

Page 9: Fluent Refactoring (Lone Star Ruby Conf 2013)

Algebra Isn’t all of Math

9

Page 10: Fluent Refactoring (Lone Star Ruby Conf 2013)

Algebra ⊂ Math

Math

Algebra

10

Page 15: Fluent Refactoring (Lone Star Ruby Conf 2013)

Math is a LanguageAlgebra is its Grammar

15

Page 16: Fluent Refactoring (Lone Star Ruby Conf 2013)

Dick and Jane

16

Page 17: Fluent Refactoring (Lone Star Ruby Conf 2013)

Fluent Refactoring

17

Page 19: Fluent Refactoring (Lone Star Ruby Conf 2013)

Flu·en·cy (noun)What you can say when you’renot thinking about how to say it

19

Page 20: Fluent Refactoring (Lone Star Ruby Conf 2013)

What you can say when you’rewoken up in the middle of the night

with a flashlight in your face

Flu·en·cy (noun)

20

Page 22: Fluent Refactoring (Lone Star Ruby Conf 2013)

http://www.jamesshore.com/Blog/Proficiencies-of-Planning.html

Level 1 Tarzan ata party

“Beer!”“Good party.”

Level 2 Going tothe party

"Where is the party?""How do I get to the party?"

Level 3 Discussingthe party

"What happened at the party last night?"

Level 4 Charlie Rose "Should parties be illegal?"

Levels of Proficiency

22

Page 23: Fluent Refactoring (Lone Star Ruby Conf 2013)

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

Page 24: Fluent Refactoring (Lone Star Ruby Conf 2013)

http://refactoring.com/

"...a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior."

24

Page 25: Fluent Refactoring (Lone Star Ruby Conf 2013)

"Yeah, we're going to have to takea couple of weeks out of the schedule for refactoring, and that's probably going

to break some stuff."

Doin It Rong

25

Page 26: Fluent Refactoring (Lone Star Ruby Conf 2013)

"Yeah, we're going to have to takea couple of weeks out of the schedule for refactoring, and that's probably going

to break some stuff."

Doin It Rong

26

Page 27: Fluent Refactoring (Lone Star Ruby Conf 2013)

"Yeah, we're going to have to takea couple of weeks out of the schedule for refactoring, and that's probably going

to break some stuff."

Doin It Rong

27

Page 28: Fluent Refactoring (Lone Star Ruby Conf 2013)

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

Page 29: Fluent Refactoring (Lone Star Ruby Conf 2013)

http://refactoring.com/

"...a disciplined technique for restructuring an existing body of code, altering its internal structure

without changing its external behavior."

29

Page 30: Fluent Refactoring (Lone Star Ruby Conf 2013)

Tests are implied.-Katrina Owen,

“Therapeutic Refactoring”

30

Page 31: Fluent Refactoring (Lone Star Ruby Conf 2013)

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

Page 32: Fluent Refactoring (Lone Star Ruby Conf 2013)

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

Page 33: Fluent Refactoring (Lone Star Ruby Conf 2013)

Re·fac·tor·ing (noun)

A technique forrestructuring code

without changing behavior

33

Page 34: Fluent Refactoring (Lone Star Ruby Conf 2013)

Re·fac·tor (verb)

To restructure codewithout changing behavior

34

Page 35: Fluent Refactoring (Lone Star Ruby Conf 2013)

Tell a clearer storywith fewer details

35

Page 36: Fluent Refactoring (Lone Star Ruby Conf 2013)

Re·fac·tor·ing (noun)

A language that describes ways to make your code

suck less.

36

Page 37: Fluent Refactoring (Lone Star Ruby Conf 2013)

THESISES

37

Page 38: Fluent Refactoring (Lone Star Ruby Conf 2013)

THESISESTHESES

38

Page 39: Fluent Refactoring (Lone Star Ruby Conf 2013)

THESISESTHESESTHESII

39

Page 40: Fluent Refactoring (Lone Star Ruby Conf 2013)

THESISESTHESESTHESII

MY POINT(S)

40

Page 41: Fluent Refactoring (Lone Star Ruby Conf 2013)

You're probably already fluent in refactoring.

Level 1:Rename Variable; Rename Method.

41

Page 42: Fluent Refactoring (Lone Star Ruby Conf 2013)

You can become more fluent in refactoring.

It just takes practice.

42

Page 43: Fluent Refactoring (Lone Star Ruby Conf 2013)

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

Page 44: Fluent Refactoring (Lone Star Ruby Conf 2013)

Refactoring Session

44

Page 45: Fluent Refactoring (Lone Star Ruby Conf 2013)

Used with:

• Permission

• Obfuscation

• Respect

Production Rails Code

45

Page 46: Fluent Refactoring (Lone Star Ruby Conf 2013)

Schedule Cable Installs

46

Page 47: Fluent Refactoring (Lone Star Ruby Conf 2013)

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

Page 48: Fluent Refactoring (Lone Star Ruby Conf 2013)

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

Page 50: Fluent Refactoring (Lone Star Ruby Conf 2013)

http://shipitsquirrel.github.io/

Ship it!

50

Page 51: Fluent Refactoring (Lone Star Ruby Conf 2013)

http://shipitsquirrel.github.io/

Ship Shit!

51

Page 54: Fluent Refactoring (Lone Star Ruby Conf 2013)

Make the Job Smaller

54

Page 55: Fluent Refactoring (Lone Star Ruby Conf 2013)

Replace Method with Method Object

55

Page 56: Fluent Refactoring (Lone Star Ruby Conf 2013)

class InstallationsController < ActionController::Base def schedule # LOTS OF CODE endend

56

Page 57: Fluent Refactoring (Lone Star Ruby Conf 2013)

class InstallationsController < ActionController::Base def schedule

endend

class ScheduleInstallation def call

endend

# LOTS OF CODE

57

Page 58: Fluent Refactoring (Lone Star Ruby Conf 2013)

class InstallationsController < ActionController::Base def schedule

endend

class ScheduleInstallation def call

endend

# LOTS OF CODE

58

Page 59: Fluent Refactoring (Lone Star Ruby Conf 2013)

class InstallationsController < ActionController::Base def schedule

endend

class ScheduleInstallation def call

endend

ScheduleInstallation.new.call

# LOTS OF CODE

59

Page 60: Fluent Refactoring (Lone Star Ruby Conf 2013)

class ScheduleInstallation def call # LOTS OF CODE endend

60

Page 61: Fluent Refactoring (Lone Star Ruby Conf 2013)

class ScheduleInstallation def initialize(controller) @controller = controller end

def call # LOTS OF CODE endend

61

Page 62: Fluent Refactoring (Lone Star Ruby Conf 2013)

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

62

Page 63: Fluent Refactoring (Lone Star Ruby Conf 2013)

Code Archaeology

63

Page 64: Fluent Refactoring (Lone Star Ruby Conf 2013)

if request.xhr? # ...20 lines...else # ...22 lines...end

64

Page 65: Fluent Refactoring (Lone Star Ruby Conf 2013)

if request.xml_http_request? # ...20 lines...else # ...22 lines...end

65

Page 66: Fluent Refactoring (Lone Star Ruby Conf 2013)

if request.xml_http_request? begin if @installation.pending_credit_check? render :json => #... return end #... endelse # ...22 lines...end

66

Page 67: Fluent Refactoring (Lone Star Ruby Conf 2013)

if request.xhr? begin if @installation.pending_credit_check? render :json => #... return end #... endelse if @installation.pending_credit_check? flash[:error] = #... redirect_to installations_path(:city_id => @installation.city_id, :view => "calendar") and return end begin #...end

67

Page 68: Fluent Refactoring (Lone Star Ruby Conf 2013)

if request.xhr? begin if @installation.pending_credit_check? render :json => #... return end #... endelse if @installation.pending_credit_check? flash[:error] = #... redirect_to installations_path(:city_id => @installation.city_id, :view => "calendar") and return end begin #...end

68

Page 69: Fluent Refactoring (Lone Star Ruby Conf 2013)

if request.xhr? begin if @installation.pending_credit_check? render :json => #... return end #... endelse if @installation.pending_credit_check? flash[:error] = #... redirect_to installations_path(:city_id => @installation.city_id, :view => "calendar") and return end begin #...end

69

Page 70: Fluent Refactoring (Lone Star Ruby Conf 2013)

if request.xhr? begin if @installation.pending_credit_check? render :json => #... return end #... endelse if @installation.pending_credit_check? flash[:error] = #... redirect_to installations_path(:city_id => @installation.city_id, :view => "calendar") and return end begin #...end

70

Page 71: Fluent Refactoring (Lone Star Ruby Conf 2013)

if request.xhr? begin if @installation.pending_credit_check? render :json => #... return end #... endelse if @installation.pending_credit_check? flash[:error] = #... redirect_to installations_path(:city_id => return end begin #...end

if request.xhr? if @installation.pending_credit_check? render :json => #... return endelse if @installation.pending_credit_check? flash[:error] = #... redirect_to(...) and return return endend

if request.xhr? #...else #...71

Page 72: Fluent Refactoring (Lone Star Ruby Conf 2013)

if request.xhr? #...else #...end

if request.xhr? #...else #...end

ZOMGduplication!!!1!!

72

Page 73: Fluent Refactoring (Lone Star Ruby Conf 2013)

if request.xhr? if @installation.pending_credit_check? #... endelse if @installation.pending_credit_check? #... endend

if request.xhr? #...else #...end

73

Page 74: Fluent Refactoring (Lone Star Ruby Conf 2013)

Emphasis

74

Page 75: Fluent Refactoring (Lone Star Ruby Conf 2013)

if request.xhr? if @installation.pending_credit_check? #... endelse if @installation.pending_credit_check? #... endend

75

Page 76: Fluent Refactoring (Lone Star Ruby Conf 2013)

Flatten Nested Conditionals

source: Michael Feathers,writing for Dr. Dobbs

76

Page 77: Fluent Refactoring (Lone Star Ruby Conf 2013)

if request.xhr? if @installation.pending_credit_check? render :json => #... return endelse if @installation.pending_credit_check? flash[:error] = #... redirect_to #... return endend

77

Page 78: Fluent Refactoring (Lone Star Ruby Conf 2013)

if ajax if pending_credit_check render :json => #... return endelse if pending_credit_check flash[:error] = #... redirect_to #... return endend

78

Page 79: Fluent Refactoring (Lone Star Ruby Conf 2013)

if ajax if pending_credit_check render :json => #... return endelse if pending_credit_check flash[:error] = #... redirect_to #... return endend

if ajax if pending_credit_check render :json => #... return endendif not ajax if pending_credit_check flash[:error] = #... redirect_to #... return endend

79

Page 80: Fluent Refactoring (Lone Star Ruby Conf 2013)

if ajax if pending_credit_check render :json => #... return endendif not ajax if pending_credit_check flash[:error] = #... redirect_to #... return endend

if ajax && pending_credit_check render :json => #... returnendif (not ajax) && pending_credit_check flash[:error] = #... redirect_to #... returnend

80

Page 81: Fluent Refactoring (Lone Star Ruby Conf 2013)

if ajax && pending_credit_check render :json => #... returnendif (not ajax) && pending_credit_check flash[:error] = #... redirect_to #... returnend

if pending_credit_check if ajax render :json => #... return end if not ajax flash[:error] = #... redirect_to #... return endend

81

Page 82: Fluent Refactoring (Lone Star Ruby Conf 2013)

if pending_credit_check if ajax render :json => #... return end if not ajax flash[:error] = #... redirect_to #... return endend

if pending_credit_check if ajax render :json => #... return else flash[:error] = #... redirect_to #... return endend

82

Page 83: Fluent Refactoring (Lone Star Ruby Conf 2013)

if pending_credit_check if ajax render :json => #... return else flash[:error] = #... redirect_to #... return endend

if pending_credit_check if ajax render :json => #... else flash[:error] = #... redirect_to #... end returnend

83

Page 84: Fluent Refactoring (Lone Star Ruby Conf 2013)

if ajax if pending_credit_check render :json => #... return endelse if pending_credit_check flash[:error] = #... redirect_to #... return endend

if pending_credit_check if ajax render :json => #... else flash[:error] = #... redirect_to #... end returnend

84

Page 85: Fluent Refactoring (Lone Star Ruby Conf 2013)

if pending_credit_check if ajax render :json => #... else flash[:error] = #... redirect_to #... end returnend

if pending_credit_check cant_schedule_while_credit_check_pending returnend

85

Page 86: Fluent Refactoring (Lone Star Ruby Conf 2013)

Exception Handling

86

Page 87: Fluent Refactoring (Lone Star Ruby Conf 2013)

raise “wtf” if coin.toss.heads?begin raise “wtf” if coin.toss.heads?end

87

Page 88: Fluent Refactoring (Lone Star Ruby Conf 2013)

begin raise “wtf” if coin.toss.heads?end

begin raise “wtf” if coin.toss.heads?rescue => e raise eend

88

Page 89: Fluent Refactoring (Lone Star Ruby Conf 2013)

begin raise “wtf” if coin.toss.heads?rescue => e raise eend

89

Page 90: Fluent Refactoring (Lone Star Ruby Conf 2013)

begin begin raise “wtf” if coin.toss.heads? rescue #... endrescue => e raise eend

90

Page 91: Fluent Refactoring (Lone Star Ruby Conf 2013)

begin begin raise “wtf” if coin.toss.heads? rescue if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? end endrescue => e raise eend

begin begin raise “wtf” if coin.toss.heads? rescue if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? end endrescue => e if request.xhr? raise e else raise e endend91

Page 92: Fluent Refactoring (Lone Star Ruby Conf 2013)

begin begin raise “wtf” if coin.toss.heads? rescue if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? end endrescue => e if request.xhr? raise e else raise e endend

begin begin raise “wtf” if coin.toss.heads? rescue if request.xhr? # DO NOTHING else raise “yak” if Moon.gibbous? end endrescue => e if request.xhr? raise “tfw” if tuesday? else raise e endend92

Page 93: Fluent Refactoring (Lone Star Ruby Conf 2013)

begin begin raise “wtf” if coin.toss.heads? rescue if request.xhr? # DO NOTHING else raise “yak” if Moon.gibbous? end endrescue => e if request.xhr? raise “tfw” if tuesday? else raise e endend

begin begin raise “wtf” if coin.toss.heads? rescue if request.xhr? # DO NOTHING else # DO NOTHING end endrescue => e if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? endend93

Page 94: Fluent Refactoring (Lone Star Ruby Conf 2013)

begin begin raise “wtf” if coin.toss.heads? rescue if request.xhr? # DO NOTHING else # DO NOTHING end endrescue => e if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? endend

begin begin raise “wtf” if coin.toss.heads? rescue # DO NOTHING endrescue => e if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? endend

94

Page 95: Fluent Refactoring (Lone Star Ruby Conf 2013)

begin begin raise “wtf” if coin.toss.heads? rescue # DO NOTHING endrescue => e if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? endend

begin begin raise “wtf” if coin.toss.heads? endrescue => e if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? endend

95

Page 96: Fluent Refactoring (Lone Star Ruby Conf 2013)

begin begin raise “wtf” if coin.toss.heads? endrescue => e if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? endend

begin raise “wtf” if coin.toss.heads?rescue => e if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? endend

96

Page 97: Fluent Refactoring (Lone Star Ruby Conf 2013)

begin raise “wtf” if coin.toss.heads?rescue => e if request.xhr? raise “tfw” if tuesday? else raise “yak” if Moon.gibbous? endend

begin raise “wtf” if coin.toss.heads?rescue => e handle_exception(e)end

97

Page 98: Fluent Refactoring (Lone Star Ruby Conf 2013)

Training Montage

98

Page 99: Fluent Refactoring (Lone Star Ruby Conf 2013)

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 endend

99

Page 100: Fluent Refactoring (Lone Star Ruby Conf 2013)

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 endend

100

Page 101: Fluent Refactoring (Lone Star Ruby Conf 2013)

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 endend

101

Page 102: Fluent Refactoring (Lone Star Ruby Conf 2013)

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 endend

102

Page 103: Fluent Refactoring (Lone Star Ruby Conf 2013)

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 endend

103

Page 104: Fluent Refactoring (Lone Star Ruby Conf 2013)

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 endend

104

Page 105: Fluent Refactoring (Lone Star Ruby Conf 2013)

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 endend

105

Page 106: Fluent Refactoring (Lone Star Ruby Conf 2013)

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 endend

106

Page 107: Fluent Refactoring (Lone Star Ruby Conf 2013)

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 endend

107

Page 108: Fluent Refactoring (Lone Star Ruby Conf 2013)

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 endend

108

Page 109: Fluent Refactoring (Lone Star Ruby Conf 2013)

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 endend

request.xhr?

109

Page 110: Fluent Refactoring (Lone Star Ruby Conf 2013)

Under The Rug

110

Page 111: Fluent Refactoring (Lone Star Ruby Conf 2013)

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

Page 112: Fluent Refactoring (Lone Star Ruby Conf 2013)

class ScheduleInstallation private

def scheduling_failed if request.xhr? render :json => {:errors => [#... else flash[:error] = #... redirect_to #... end endend

112

Page 113: Fluent Refactoring (Lone Star Ruby Conf 2013)

Single Responsibility Principle

113

Page 114: Fluent Refactoring (Lone Star Ruby Conf 2013)

ScheduleInstallationScheduleInstallationAnd

DoOneThingForAJAXRequestsAndDoSomethingElseForHTMLRequests

114

Page 115: Fluent Refactoring (Lone Star Ruby Conf 2013)

ScheduleInstallationScheduleInstallation And

DoOneThingForAJAXRequests AndDoSomethingElseForHTMLRequests

115

Page 116: Fluent Refactoring (Lone Star Ruby Conf 2013)

“Methods, like classes, should have a single

responsibility.”-Sandi Metz

116

Page 119: Fluent Refactoring (Lone Star Ruby Conf 2013)

Single Responsibility Principle

Every class should have a single responsibility, and that responsibility

should be entirely encapsulatedby the class.

119

Page 120: Fluent Refactoring (Lone Star Ruby Conf 2013)

ScheduleInstallationScheduleInstallation And

DoOneThingForAJAXRequests AndDoSomethingElseForHTMLRequests

120

Page 121: Fluent Refactoring (Lone Star Ruby Conf 2013)

Responder

121

Page 122: Fluent Refactoring (Lone Star Ruby Conf 2013)

122

InstallationsController

Page 123: Fluent Refactoring (Lone Star Ruby Conf 2013)

123

InstallationsController

ScheduleInstallation???

Page 124: Fluent Refactoring (Lone Star Ruby Conf 2013)

124

InstallationsController

Responder

???

ScheduleInstallation???

Page 125: Fluent Refactoring (Lone Star Ruby Conf 2013)

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_cleanupend

class Responderend

class ScheduleInstallation def callend

class Responder def cannot_schedule_while_credit_check_pe def handle_exception(e) def scheduling_failed def scheduling_succeeded def do_post_success_cleanupend

125

Page 126: Fluent Refactoring (Lone Star Ruby Conf 2013)

126

Dualism

Page 127: Fluent Refactoring (Lone Star Ruby Conf 2013)

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

Page 128: Fluent Refactoring (Lone Star Ruby Conf 2013)

if request.xhr? # do fooelse # do barend

Page 129: Fluent Refactoring (Lone Star Ruby Conf 2013)

Replace ConditionalWith Polymorphism

129

Page 130: Fluent Refactoring (Lone Star Ruby Conf 2013)

class Responder def cannot_schedule_while_credit_check_pending def handle_exception(e) def scheduling_failed def scheduling_succeeded def do_post_success_cleanupend

class AJAXResponder def cannot_schedule_while_credit_check_pending def handle_exception(e) def scheduling_failed def scheduling_succeeded def do_post_success_cleanupend

class HTMLResponder def cannot_schedule_while_credit_check_pending def handle_exception(e) def scheduling_failed def scheduling_succeeded def do_post_success_cleanupend

Page 131: Fluent Refactoring (Lone Star Ruby Conf 2013)

class AJAXResponder def scheduling_failed if request.xhr? render :json => #... else flash[:error] = #... redirect_to #... end endend

class HTMLResponder def scheduling_failed if request.xhr? render :json => #... else flash[:error] = #... redirect_to #... end endend

class AJAXResponder def scheduling_failed render :json => #... endend

class HTMLResponder def scheduling_failed flash[:error] = #... redirect_to #... endend

Page 132: Fluent Refactoring (Lone Star Ruby Conf 2013)

132

InstallationsController

Responder

???

ScheduleInstallation???

Page 133: Fluent Refactoring (Lone Star Ruby Conf 2013)

class InstallationsController < ActionController::Base

def schedule responder = if request.xhr? AJAXResponder.new(self) else HTMLResponder.new(self) end ScheduleInstallation.new(responder).call endend

Page 134: Fluent Refactoring (Lone Star Ruby Conf 2013)

LESSONS LEARNED

134

Page 135: Fluent Refactoring (Lone Star Ruby Conf 2013)

Refactoring is Math

135

Page 136: Fluent Refactoring (Lone Star Ruby Conf 2013)

Fast CharacterizationTests Rock

136

Page 137: Fluent Refactoring (Lone Star Ruby Conf 2013)

Embrace Duplicationif request.xhr?

137

0

2

4

6

8

10

Page 138: Fluent Refactoring (Lone Star Ruby Conf 2013)

Embrace Evil Hacks

138

Page 139: Fluent Refactoring (Lone Star Ruby Conf 2013)

Perspective MattersSuperficial design flaws

can concealfundamental design flaws

139

Page 141: Fluent Refactoring (Lone Star Ruby Conf 2013)

141

Page 142: Fluent Refactoring (Lone Star Ruby Conf 2013)

142

Page 143: Fluent Refactoring (Lone Star Ruby Conf 2013)

143

Page 144: Fluent Refactoring (Lone Star Ruby Conf 2013)

http://www.poodr.info/144

Page 145: Fluent Refactoring (Lone Star Ruby Conf 2013)

Practice!• Play with automated refactorings in an IDE

• Do them manually in the editor (wax on, wax off)

145

Page 146: Fluent Refactoring (Lone Star Ruby Conf 2013)

Practice!• Commit early, commit often:

‘git reset --hard’ is your friend!

• Use throwaway branches

• Write fast characterization tests

146

Page 147: Fluent Refactoring (Lone Star Ruby Conf 2013)

Fluent Refactoringgithub.com/geeksam/fluent-refactoring

Sam [email protected]

Twitter, Github: @geeksam

147