fluent refactoring (lone star ruby conf 2013)

Post on 11-May-2015

1.177 Views

Category:

Technology

3 Downloads

Preview:

Click to see full reader

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.

TRANSCRIPT

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

Let’s Talk About Math!

2

Algebra

7

Algebra Isn’t Math

8

Algebra Isn’t all of Math

9

Algebra ⊂ Math

Math

Algebra

10

Math is a LanguageAlgebra is its Grammar

15

Dick and Jane

16

Fluent Refactoring

17

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

19

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

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

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

http://refactoring.com/

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

24

"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

"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

"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

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

http://refactoring.com/

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

without changing its external behavior."

29

Tests are implied.-Katrina Owen,

“Therapeutic Refactoring”

30

Re·fac·tor·ing (noun)"...a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior."

-refactoring.com

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

32

Re·fac·tor·ing (noun)

A technique forrestructuring code

without changing behavior

33

Re·fac·tor (verb)

To restructure codewithout changing behavior

34

Tell a clearer storywith fewer details

35

Re·fac·tor·ing (noun)

A language that describes ways to make your code

suck less.

36

THESISES

37

THESISESTHESES

38

THESISESTHESESTHESII

39

THESISESTHESESTHESII

MY POINT(S)

40

You're probably already fluent in refactoring.

Level 1:Rename Variable; Rename Method.

41

You can become more fluent in refactoring.

It just takes practice.

42

Putting in the practice to become more fluent in refactoring is worth it.

Because you’ll be able to say more things when you’re under stress.

43

Refactoring Session

44

Used with:

• Permission

• Obfuscation

• Respect

Production Rails Code

45

Schedule Cable Installs

46

class InstallationsController < ActionController::Base # lots more stuff...

def schedule desired_date = params[:desired_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

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

http://shipitsquirrel.github.io/

Ship it!

50

http://shipitsquirrel.github.io/

Ship Shit!

51

Make the Job Smaller

54

Replace Method with Method Object

55

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

56

class InstallationsController < ActionController::Base def schedule

endend

class ScheduleInstallation def call

endend

# LOTS OF CODE

57

class InstallationsController < ActionController::Base def schedule

endend

class ScheduleInstallation def call

endend

# LOTS OF CODE

58

class InstallationsController < ActionController::Base def schedule

endend

class ScheduleInstallation def call

endend

ScheduleInstallation.new.call

# LOTS OF CODE

59

class ScheduleInstallation def call # LOTS OF CODE endend

60

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

def call # LOTS OF CODE endend

61

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

Code Archaeology

63

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

64

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

65

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

66

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

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

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

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

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

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

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

ZOMGduplication!!!1!!

72

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

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

73

Emphasis

74

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

75

Flatten Nested Conditionals

source: Michael Feathers,writing for Dr. Dobbs

76

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

77

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

78

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

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

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

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

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

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

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

Exception Handling

86

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

87

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

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

88

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

89

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

90

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

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

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

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

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

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

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

Training Montage

98

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

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

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

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

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

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

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

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

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

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

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

Under The Rug

110

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

class ScheduleInstallation private

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

112

Single Responsibility Principle

113

ScheduleInstallationScheduleInstallationAnd

DoOneThingForAJAXRequestsAndDoSomethingElseForHTMLRequests

114

ScheduleInstallationScheduleInstallation And

DoOneThingForAJAXRequests AndDoSomethingElseForHTMLRequests

115

“Methods, like classes, should have a single

responsibility.”-Sandi Metz

116

Single Responsibility Principle

Every class should have a single responsibility, and that responsibility

should be entirely encapsulatedby the class.

119

ScheduleInstallationScheduleInstallation And

DoOneThingForAJAXRequests AndDoSomethingElseForHTMLRequests

120

Responder

121

122

InstallationsController

123

InstallationsController

ScheduleInstallation???

124

InstallationsController

Responder

???

ScheduleInstallation???

class ScheduleInstallation def call

private

def cannot_schedule_while_credit_check_pendin def handle_exception(e) def 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

126

Dualism

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

if request.xhr? # do fooelse # do barend

Replace ConditionalWith Polymorphism

129

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

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

132

InstallationsController

Responder

???

ScheduleInstallation???

class InstallationsController < ActionController::Base

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

LESSONS LEARNED

134

Refactoring is Math

135

Fast CharacterizationTests Rock

136

Embrace Duplicationif request.xhr?

137

0

2

4

6

8

10

Embrace Evil Hacks

138

Perspective MattersSuperficial design flaws

can concealfundamental design flaws

139

141

142

143

http://www.poodr.info/144

Practice!• Play with automated refactorings in an IDE

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

145

Practice!• Commit early, commit often:

‘git reset --hard’ is your friend!

• Use throwaway branches

• Write fast characterization tests

146

Fluent Refactoringgithub.com/geeksam/fluent-refactoring

Sam Livingston-Graygeeksam@gmail.com

Twitter, Github: @geeksam

147

top related