SpotFlow: Tracking Method Calls and States at Runtime
Refactoring Workshop (Rails Pacific 2014)
1. REFACTORING WORKSHOP
Bruce Li @ Rails Pacific 2014
Please clone this repo
https://github.com/ascendbruce/refactoring-workshop
run bundle install
and Check your ruby version
2. ABOUT ME
• Li, Po-Chun a.k.a. Bruce Li
• Work at Techbang
• http://ascendbruce.logdown.com/
• @BruceToyRoom, @techbangtech
3. REFACTORING
is important
• Reduce maintenance cost
• Technical debt is not about technology. It’s about
people
• programmer happiness is very important
4. TEST COVERAGE
is important
• Confident in changes
• Code School, TeaLeaf are good start
• SimpleCov and CodeClimate can make you
happier while repairing test cases
5. PRY
is useful
• gem "pry-rails"
gem "pry-byebug" # or pry-debugger for ruby 1.9
• binding.pry # to entering debug mode
• Commands: next, step, break, continue, exit
• It’s all you need at first
6. COMMENTS IN CODE
is bad (in some cases)
• Ideally, code should describe itself.
• Out-dated comments are worser than none. It’s
actively misleading.
• Comments is helpful for providing additional
information or link to issue tracking history.
7. 1. INTENTION REVEALING
METHOD
• Add comments if code need it.
• Transform comments into methods.
• Comments are now code. Code describes itself.
15. 2. MOVE MODEL LOGIC INTO
THE MODEL
• Move the logic, which belongs to model, into the
model
• instance variable should change to self in model
16. class ProjectsController
def new
# @#$^$%&#%&*#$%&
@project = Project.new
!
!
@project.do_ooxx
@project.xxoo = "xxoo"
# &*%^&@#$!
end
end
class Project
!
!
!
!
end
def do_something
!
!
end
17. class ProjectsController
def new
# @#$^$%&#%&*#$%&
@project = Project.new
@project.do_something
!
!
# &*%^&@#$!
end
end
class Project
!
!
!
!
end
def do_something
!
!
end
@project.do_ooxx
@project.xxoo = "xxoo"
18. class ProjectsController
def new
# @#$^$%&#%&*#$%&
@project = Project.new
@project.do_something
!
!
# &*%^&@#$!
end
end
class Project
!
!
!
!
end
def do_something
!
!
end
@project.do_ooxx
@project.xxoo = "xxoo"
19. class ProjectsController
def new
# @#$^$%&#%&*#$%&
@project = Project.new
@project.do_something
do_ooxx
self.xxoo = "xxoo"
!
!
# &*%^&@#$!
end
end
class Project
!
!
!
!
end
def do_something
!
!
end
20. 3. REPLACE METHOD WITH
METHOD OBJECT
• Create a class with same initialization arguments as
BIG method
• Copy & Paste the method's body in the new class,
with no arguments
• Replace original method with a call to the new class
• Apply "Intention Revealing Method" to the class
21. class OriginMethods
def a_fat_method
!
!
!
!
end
end
# $%@ + ($@# / ^%)
# && ^ %& * #%%^
# wtf
# and 100 lines...
22. class NewMethods
def perform
end
end
class OriginMethods
def a_fat_method
!
!
!
!
end
end
# $%@ + ($@# / ^%)
# && ^ %& * #%%^
# wtf
# and 100 lines...
23. class NewMethods
def perform
!
!
!
!
end
end
# $%@ + ($@# / ^%)
# && ^ %& * #%%^
# wtf
# and 100 lines...
class OriginMethods
def a_fat_method
!
!
!
!
end
end
24. class NewMethods
def perform
!
!
!
!
end
end
# $%@ + ($@# / ^%)
# && ^ %& * #%%^
# wtf
# and 100 lines...
class OriginMethods
def a_fat_method
!
!
!
!
end
end
NewMethods.new.perform
25. class NewMethods
!
!
!
!
def perform
File.open(??????????)
# ...
end
end
!
class OriginMethods
def a_fat_method(file_name)
NewMethods.new.perform
end
end
?
26. class NewMethods
!
!
!
!
def perform
File.open( )
# ...
end
end
!
class OriginMethods
def a_fat_method(file_name)
NewMethods.new
end
end
27. class NewMethods
!
!
!
!
def perform
File.open( )
# ...
end
end
!
class OriginMethods
def a_fat_method(file_name)
NewMethods.new
end
end
(file_name).perform
28. class NewMethods
!
!
!
!
def perform
def initialize(file_name)
@file_name = file_name
end
File.open( )
# ...
end
end
!
class OriginMethods
def a_fat_method(file_name)
NewMethods.new
end
end
(file_name).perform
29. class NewMethods
!
!
!
!
def perform
def initialize(file_name)
@file_name = file_name
end
@file_name
File.open( )
# ...
end
end
!
class OriginMethods
def a_fat_method(file_name)
NewMethods.new
end
end
(file_name).perform
30. 4. SERVICE OBJECT
• If we add new functionality to an object and:
• It couples to a new dependency
• It loses cohesion
• Testing gets harder and slower
31. 4. SERVICE OBJECT
• If it's a domain concept, it's an Object. If it's only
an algorithm (no state) we call it a Service.
• Delegate to the Service Object
32. 5. FORM OBJECT
• accepts_nested_attributes_for is hard to track
• Making very different forms for the same object is
a pain. The model is messed up
33. 5. FORM OBJECT
• include ActiveModel::Model
• Set attr_reader for related models
• Set attr_accessor for accepted fields
• Add validators (ActiveModel::Model will take care of validation and
errors)
• Define persisted? method (false for create, true for update form)
• Add initialize, save, update etc… if needed
34. REFERENCE
• Crisp's Blog - Good and Bad Technical Debt
• RailsCasts - Form Objects
• 7 Patterns to Refactor Fat ActiveRecord Models