Fluent Refactoring (Cascadia Ruby Conf 2013)

Preview:

Citation preview

Fluent RefactoringSam Livingston-Gray

THERE WILL BE CODE!

It may bethis small

puts (1..100).map { |i| s = '' fizz = (i % 3).zero? buzz = (i % 5).zero? s << 'Fizz' if fizz s << 'Buzz' if buzz s = i if s.empty? s}

1

Tuesday, October 22, 13

Math

2

\m/

Tuesday, October 22, 13

http://www.wikihow.com/Image:Solve-for-X-Step-12.jpg4

Tuesday, October 22, 13

http://math.about.com/od/algebra/ss/birthday.htm5

Tuesday, October 22, 13

Algebra

7

Tuesday, October 22, 13

Algebra Isn’t Math

8

Tuesday, October 22, 13

Algebra Isn’t all of Math

9

Tuesday, October 22, 13

Algebra ⊂ Math

Math

Algebra

10

Tuesday, October 22, 13

Math is a Language

15

Tuesday, October 22, 13

Math is a LanguageAlgebra is[one of]

its Grammar[s]

15

Tuesday, October 22, 13

Fluent Refactoring

16

Tuesday, October 22, 13

Re·fac·tor·ing (noun)

18

Tuesday, October 22, 13

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

18

Tuesday, October 22, 13

TL;DR

19

Tuesday, October 22, 13

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

19

Tuesday, October 22, 13

Re·fac·tor·ing (noun)

A techniquefor restructuring code

without changing behavior

20

Tuesday, October 22, 13

Tell a clearer storywith fewer details

21

Tuesday, October 22, 13

http://www.tagxedo.com/app.html

Jargon

22

Tuesday, October 22, 13

http://www.tagxedo.com/app.html

Jargon

22

Tuesday, October 22, 13

Re·fac·tor·ing (noun)

23

Tuesday, October 22, 13

Re·fac·tor·ing (noun)

A language thatdescribes ways tomake your code

suck less.

23

Tuesday, October 22, 13

Flu·en·cy (noun)

24

Tuesday, October 22, 13

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

24

Tuesday, October 22, 13

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

with a flashlight in your face

Flu·en·cy (noun)

25

Tuesday, October 22, 13

Language Hunters

27

Tuesday, October 22, 13

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

Level 1

Level 2

Level 3

Level 4

Levels of Proficiency

28

Tuesday, October 22, 13

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

Level 1 Tarzan ata party

“Beer!”“Good party.”

Level 2

Level 3

Level 4

Levels of Proficiency

29

Tuesday, October 22, 13

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

Level 4

Levels of Proficiency

30

Tuesday, October 22, 13

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

Levels of Proficiency

31

Tuesday, October 22, 13

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

32

Tuesday, October 22, 13

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

33

Tuesday, October 22, 13

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?"

Levels of Proficiency

34

Tuesday, October 22, 13

Story Time

35

Tuesday, October 22, 13

Production Rails Code

36

Tuesday, October 22, 13

Used with:

• Permission

Production Rails Code

36

Tuesday, October 22, 13

Used with:

• Permission

• Obfuscation

Production Rails Code

36

Tuesday, October 22, 13

Used with:

• Permission

• Obfuscation

• Respect

Production Rails Code

36

Tuesday, October 22, 13

Respect

37

Tuesday, October 22, 13

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

38

Tuesday, October 22, 13

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

39

Tuesday, October 22, 13

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

39

Tuesday, October 22, 13

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

39

Tuesday, October 22, 13

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

39

Tuesday, October 22, 13

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

39

Tuesday, October 22, 13

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:

39

Tuesday, October 22, 13

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

39

Tuesday, October 22, 13

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

39

Tuesday, October 22, 13

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

39

Tuesday, October 22, 13

http://shipitsquirrel.github.io/

Ship it!

41

Tuesday, October 22, 13

~800 lines

42

Tuesday, October 22, 13

Make the Job Smaller

44

Tuesday, October 22, 13

Replace Method with Method Object

45

See also:

Katrina Owen,“Therapeutic Refactoring”

Tuesday, October 22, 13

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

46

Tuesday, October 22, 13

class InstallationsController < ActionController::Base def schedule

endend

class ScheduleInstallation def call

endend

# LOTS OF CODE

47

Tuesday, October 22, 13

class InstallationsController < ActionController::Base def schedule

endend

class ScheduleInstallation def call

endend

# LOTS OF CODE

47

Tuesday, October 22, 13

class InstallationsController < ActionController::Base def schedule

endend

class ScheduleInstallation def call

endend

ScheduleInstallation.new.call

# LOTS OF CODE

47

Tuesday, October 22, 13

48

NoMethodErrorLOL WUT?

Tuesday, October 22, 13

class ScheduleInstallation def call # LOTS OF CODE # ... params ... # ... render ... # ... redirect_to ... endend

49

Tuesday, October 22, 13

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

def call # LOTS OF CODE # ... params ... # ... render ... # ... redirect_to ... endend

50

Tuesday, October 22, 13

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

def call # LOTS OF CODE # ... @controller.params ... # ... @controller.render ... # ... @controller.redirect_to ... endend

51

Tuesday, October 22, 13

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

extend Forwardable def_delegators :@controller, :params, :render, :redirect_to

def call # LOTS OF CODE # ... params ... # ... render ... # ... redirect_to ... endend

52

Tuesday, October 22, 13

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

53

Tuesday, October 22, 13

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

55

Tuesday, October 22, 13

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

56

Tuesday, October 22, 13

if request.xml_http_request? begin #... endelse # ...22 lines...end

57

Tuesday, October 22, 13

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

58

Tuesday, October 22, 13

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

58

Tuesday, October 22, 13

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

58

Guard Clause-Smalltalk Best Practice Patterns

by Kent Beck

Tuesday, October 22, 13

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 #... endend 59

Tuesday, October 22, 13

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 #... endend 59

Tuesday, October 22, 13

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 60

Tuesday, October 22, 13

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 end begin #... endelse if @installation.pending_credit_check? flash[:error] = #... redirect_to installations_path( return end begin #... end60

Tuesday, October 22, 13

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

Tuesday, October 22, 13

if request.xhr? if @installation.pending_credit_check? render :json => #... return end begin #... 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 #... return endend

if request.xhr? begin #... end61

Tuesday, October 22, 13

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

62

Tuesday, October 22, 13

emph·AS·is

63

Tuesday, October 22, 13

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

64

Tuesday, October 22, 13

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

64

Tuesday, October 22, 13

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

64

Tuesday, October 22, 13

Flatten Nested Conditionals

source:Michael Feathers

in Dr. Dobbs

65

Tuesday, October 22, 13

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

66

Tuesday, October 22, 13

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

67

Tuesday, October 22, 13

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

67

ajax nope

TRUE TRUE

ajax nope

FALSE TRUE

Tuesday, October 22, 13

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

68

Tuesday, October 22, 13

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

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

68

Tuesday, October 22, 13

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

69

Tuesday, October 22, 13

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

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

69

Tuesday, October 22, 13

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

70

Tuesday, October 22, 13

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

70

Tuesday, October 22, 13

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

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

70

Tuesday, October 22, 13

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

71

Tuesday, October 22, 13

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

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

71

Tuesday, October 22, 13

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

72

Tuesday, October 22, 13

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

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

72

Tuesday, October 22, 13

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

73

Tuesday, October 22, 13

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

if @installation.pending_credit_check? if request.xhr? render :json => #... else flash[:error] = #... redirect_to #... end returnend

73

Tuesday, October 22, 13

if @installation.pending_credit_check? if request.xhr? render :json => #... else flash[:error] = #... redirect_to #... end returnend

74

Tuesday, October 22, 13

if @installation.pending_credit_check? if request.xhr? render :json => #... else flash[:error] = #... redirect_to #... end returnend

if @installation.pending_credit_check? cant_schedule_while_credit_check_pending returnend

74

Tuesday, October 22, 13

if @installation.pending_credit_check? cant_schedule_while_credit_check_pending returnend

if request.xhr? begin #... endelse begin #... endend

75

Tuesday, October 22, 13

class InstallationsController < ActionController::Base 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 endend

77

Tuesday, October 22, 13

class InstallationsController < ActionController::Base 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 endend

class ScheduleInstallation def call if @installation.pending_credit_check? cant_schedule_while_credit_check_pending return end

audit_trail_for(current_user) if schedule! if @installation.scheduled_date scheduling_succeeded end do_post_success_cleanup else scheduling_failed end end rescue => e handle_exception e endend

77

Tuesday, October 22, 13

class ScheduleInstallation def call if @installation.pending_credit_check? cant_schedule_while_credit_check_pending return end

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 => e handle_exception e endend

78

Tuesday, October 22, 13

class ScheduleInstallation def call if @installation.pending_credit_check? cant_schedule_while_credit_check_pending return end

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 => e handle_exception e endend

request.xhr?

79

Tuesday, October 22, 13

class ScheduleInstallation def call if @installation.pending_credit_check? cant_schedule_while_credit_check_pending return end

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 => e handle_exception e endend

request.xhr?

79

Tuesday, October 22, 13

Under The Rug

80

Tuesday, October 22, 13

def cannot_schedule_while_#... if request.xhr? # ...1 line... else # ...2 lines... endend

def handle_exception(e) if request.xhr? # ...7 lines... else # ...2 lines... endend

def scheduling_succeeded if request.xhr? # ...2 lines... else # ...5 lines... endend

def scheduling_failed if request.xhr? # ...1 line... else # ...2 lines... endend

def do_post_success_cleanup if request.xhr? # DO NOTHING else # ...1 line... endend

81

Tuesday, October 22, 13

def cannot_schedule_while_#... if request.xhr? # ...1 line... else # ...2 lines... endend

def handle_exception(e) if request.xhr? # ...7 lines... else # ...2 lines... endend

def scheduling_succeeded if request.xhr? # ...2 lines... else # ...5 lines... endend

def scheduling_failed if request.xhr? # ...1 line... else # ...2 lines... endend

def do_post_success_cleanup if request.xhr? # DO NOTHING else # ...1 line... endend

81

Tuesday, October 22, 13

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

Tuesday, October 22, 13

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

Tuesday, October 22, 13

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

Tuesday, October 22, 13

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

Tuesday, October 22, 13

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

Tuesday, October 22, 13

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

Tuesday, October 22, 13

Single Responsibility Principle

83

(SRP for short)

Tuesday, October 22, 13

ScheduleInstallation

84

Tuesday, October 22, 13

ScheduleInstallationScheduleInstallationAnd

DoOneThingForAJAXRequestsAndDoSomethingElseForHTMLRequests

84

Tuesday, October 22, 13

ScheduleInstallationScheduleInstallation And

DoOneThingForAJAXRequests And DoSomethingElseForHTMLRequests

85

Tuesday, October 22, 13

ScheduleInstallationScheduleInstallation And

DoOneThingForAJAXRequests And DoSomethingElseForHTMLRequests

86

Tuesday, October 22, 13

Recap

87

Tuesday, October 22, 13

88

InstallationsController

Tuesday, October 22, 13

88

InstallationsController

ScheduleInstallation

Tuesday, October 22, 13

88

InstallationsController

ScheduleInstallation

???

Tuesday, October 22, 13

89

InstallationsController

Responder

ScheduleInstallation

???

Tuesday, October 22, 13

89

InstallationsController

Responder

???

ScheduleInstallation

???

Tuesday, October 22, 13

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

90

Tuesday, October 22, 13

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

90

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

Tuesday, October 22, 13

91

Responder

Tuesday, October 22, 13

if request.xhr? # do thiselse # do thatend

Tuesday, October 22, 13

Replace ConditionalWith Polymorphism

93

Tuesday, October 22, 13

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

Tuesday, October 22, 13

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

Tuesday, October 22, 13

class InstallationsController < ActionController::Base

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

Tuesday, October 22, 13

class InstallationsController < ActionController::Base

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

Tuesday, October 22, 13

class InstallationsController < ActionController::Base

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

Tuesday, October 22, 13

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

Tuesday, October 22, 13

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

Tuesday, October 22, 13

class InstallationsController < ActionController::Base

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

Tuesday, October 22, 13

http://www.poodr.info/99

Tuesday, October 22, 13

100

Tuesday, October 22, 13

101

Tuesday, October 22, 13

102

Tuesday, October 22, 13

103

Tuesday, October 22, 13

Commit Early,Commit Often

104

Tuesday, October 22, 13

Throw Your Work Away

105

Tuesday, October 22, 13

Speed Up Your Tests!

106

Tuesday, October 22, 13

Speed Up Your Tests!See also:

Katrina Owen,“Therapeutic Refactoring”

(srsly)

106

Tuesday, October 22, 13

107

I work at LivingSocial.We’re hiring.

Tuesday, October 22, 13

107

I work at LivingSocial.We’re hiring.

(Who isn’t?)

Tuesday, October 22, 13

Jim Shore and Diana Larsenfor “Agile Fluency”

Kirsten Comandich

Sandi Metz, Katrina Owen,Sonia Connolly

PDX.rb

108

Thanks to:

Tuesday, October 22, 13

github.com/geeksam/fluent-refactoring

109

Tuesday, October 22, 13

github.com/geeksam/fluent-refactoring

Sam Livingston-Graygeeksam@gmail.com

Twitter, Github: @geeksam

110

Tuesday, October 22, 13

Recommended