refactoring workshop (rails pacific 2014)
DESCRIPTION
Refactoring Workshop (Rails Pacific 2014)TRANSCRIPT
REFACTORING WORKSHOPBruce Li @ Rails Pacific 2014
Please clone this repo https://github.com/ascendbruce/refactoring-workshop run bundle install and Check your ruby version
ABOUT ME
• Li, Po-Chun a.k.a. Bruce Li
• Work at Techbang
• http://ascendbruce.logdown.com/
• @BruceToyRoom, @techbangtech
REFACTORING
• Reduce maintenance cost
• Technical debt is not about technology. It’s about people
• programmer happiness is very important
is important
TEST COVERAGE
• Confident in changes
• Code School, TeaLeaf are good start
• SimpleCov and CodeClimate can make you happier while repairing test cases
is important
PRY
• 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
is useful
COMMENTS IN CODE
• 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.
is bad (in some cases)
1. INTENTION REVEALING METHOD
• Add comments if code need it.
• Transform comments into methods.
• Comments are now code. Code describes itself.
user_is_admitted?
user_is_admitted?
user_is_admitted?
set_marketing_flash
user_is_admitted?
set_marketing_flash
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
class ProjectsController def new # @#$^$%&#%&*#$%& @project = Project.new !! # &*%^&@#$! end end
class Project !!!!end
@project.do_ooxx @project.xxoo = "xxoo"
def do_something !!end
@project.do_something
class ProjectsController def new # @#$^$%&#%&*#$%& @project = Project.new !! # &*%^&@#$! end end
class Project !!!!end
@project.do_ooxx @project.xxoo = "xxoo"
def do_something !!end
@project.do_something
class ProjectsController def new # @#$^$%&#%&*#$%& @project = Project.new !! # &*%^&@#$! end end
class Project !!!!end
@project.do_ooxx @project.xxoo = "xxoo"
def do_something !!end
do_ooxx self.xxoo = "xxoo"
@project.do_something
class ProjectsController def new # @#$^$%&#%&*#$%& @project = Project.new !! # &*%^&@#$! end end
class Project !!!!end
def do_something !!end
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
class OriginMethods def a_fat_method !!!! end end
# $%@ + ($@# / ^%) # && ^ %& * #%%^ # wtf # and 100 lines...
class OriginMethods def a_fat_method !!!! end end
class NewMethodsdef perform
endend
# $%@ + ($@# / ^%) # && ^ %& * #%%^ # wtf # and 100 lines...
class OriginMethods def a_fat_method !!!! end end
class NewMethods def perform !!!! end end
# $%@ + ($@# / ^%) # && ^ %& * #%%^ # wtf # and 100 lines...
class OriginMethods def a_fat_method !!!! end end
class NewMethods def perform !!!! end end
# $%@ + ($@# / ^%) # && ^ %& * #%%^ # wtf # and 100 lines...
NewMethods.new.perform
class NewMethods !!!! def perform File.open(??????????) # ... end end !class OriginMethods def a_fat_method(file_name) NewMethods.new.perform end end
?
class NewMethods !!!! def perform File.open( ) # ... end end !class OriginMethods def a_fat_method(file_name) NewMethods.new end end
class NewMethods !!!! def perform File.open( ) # ... end end !class OriginMethods def a_fat_method(file_name) NewMethods.new end end
(file_name).perform
class NewMethods !!!! def perform File.open( ) # ... end end !class OriginMethods def a_fat_method(file_name) NewMethods.new end end
def initialize(file_name)@file_name = file_name
end
(file_name).perform
class NewMethods !!!! def perform File.open( ) # ... end end !class OriginMethods def a_fat_method(file_name) NewMethods.new end end
@file_name
def initialize(file_name)@file_name = file_name
end
(file_name).perform
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
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
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
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
REFERENCE
• Crisp's Blog - Good and Bad Technical Debt
• RailsCasts - Form Objects
• 7 Patterns to Refactor Fat ActiveRecord Models