4 simple rules to refactoring

57
4 SIMPLE RULES TO REFACTORING STEVEN YAP, FUTUREWORKZ

Upload: futureworkz

Post on 13-Apr-2017

719 views

Category:

Software


0 download

TRANSCRIPT

Page 1: 4 Simple Rules to Refactoring

4 SIMPLE RULES TO REFACTORINGSTEVEN YAP, FUTUREWORKZ

Page 2: 4 Simple Rules to Refactoring

Steven [email protected]://github.com/stevenyap

Page 3: 4 Simple Rules to Refactoring
Page 4: 4 Simple Rules to Refactoring
Page 5: 4 Simple Rules to Refactoring

COUNTRY = SINGAPORE STATE = SINGAPORE CITY = SINGAPORE

CAPITAL = SINGAPORE

Page 6: 4 Simple Rules to Refactoring

• Host Saigon.rb Ruby Meetup• Co-Founder of Futureworkz• Ruby on Rails coder• Agile startup consultant

Page 7: 4 Simple Rules to Refactoring

Awesome Ruby on Rails Developmenthttp://www.futureworkz.com

http://playbook.futureworkz.com/

Page 8: 4 Simple Rules to Refactoring

WHAT IS REFACTORING?

Page 9: 4 Simple Rules to Refactoring

IS REFACTORING IMPORTANT?

Page 10: 4 Simple Rules to Refactoring

WHO LOVES REFACTORING?

Page 11: 4 Simple Rules to Refactoring

WHO HATES REFACTORING?

Page 12: 4 Simple Rules to Refactoring

4 SIMPLE RULES TO REFACTORING BY STEVEN YAP

I HATE REFACTORING BECAUSE...

▸ extra work to do

▸ what if i break something else?

▸ waste of time

▸ takes up too much time

▸ no time

▸ don't know what to refactor

▸ how to refactor?

▸ i can't see the code smell

▸ makes me feel stupid

▸ any other reasons?

Page 13: 4 Simple Rules to Refactoring

HATE REFACTORING 😡😡😡😡

Page 14: 4 Simple Rules to Refactoring

4 SIMPLE RULES TO REFACTORINGHTTP://PLAYBOOK.FUTUREWORKZ.COM/PROTOCOLS/CODE-REVIEW/INDEX.HTML

Page 15: 4 Simple Rules to Refactoring

LOVE REFACTORING 😍😍😍😍

Page 16: 4 Simple Rules to Refactoring

4 SIMPLE RULES TO REFACTORING BY STEVEN YAP

THE FOUR RULES

▸ Write Test Cases

▸ Don't Repeat Yourself (DRY)

▸ Naming Reveals Intention

▸ Single Responsibility Principle (SRP)

Page 17: 4 Simple Rules to Refactoring

RULE #1: WRITE TEST CASES

Page 18: 4 Simple Rules to Refactoring

4 SIMPLE RULES TO REFACTORING BY STEVEN YAP

RULE #1: WRITE TEST CASES

▸ No test cases = no refactoring

▸ Never try to refactor without a test case to cover you

▸ Other coders' test cases protect you too

▸ Stress-free in coding = more happiness

▸ Easy way to write test cases (self-promotion):http://blog.futureworkz.com/ruby-on-rails/easy-way-write-test-case/

▸ The more test cases you write, the faster you write the test cases

Page 19: 4 Simple Rules to Refactoring

require'rails_helper'

describeProgramdocontext'validations'doit{is_expected.tovalidate_presence_of:name}it{is_expected.tovalidate_presence_of:category_id}it{is_expected.tovalidate_presence_of:team_id}it{is_expected.tovalidate_presence_of:estimated_start_date}it{is_expected.toenumerize(:status).in(:wip,:unsuccessful,:pending_approval,:approved,:rejected,:completed)}end

context'associations'doit{is_expected.tohave_one:job}it{is_expected.tohave_many:quotations}it{is_expected.tohave_many:pos_to_suppliers}it{is_expected.tohave_many:pos_to_suppliers_in_group}it{is_expected.tohave_many:approved_quotations}it{is_expected.tohave_many:cost_items}end

describe'.started'dolet!(:pending_program){create(:pending_program)}let!(:started_program){create(:approved_program)}

it'returnsanarrayofstartedprogram'doexpect(Program.started).toincludestarted_programexpect(Program.started).to_notincludepending_programendendend

Page 20: 4 Simple Rules to Refactoring

HATE REFACTORING 😡😡😡😡

Page 21: 4 Simple Rules to Refactoring

HATE REFACTORING 😍😡😡😡

Page 22: 4 Simple Rules to Refactoring

RULE #2: DON'T REPEAT YOURSELF

Page 23: 4 Simple Rules to Refactoring

classShoppingCarthas_many:products

deffinal_priceproducts.map(&:price).inject(&:+)+country_taxend

defcountry_taxproducts.map(&:price).inject(&:+)*0.1endend

Page 24: 4 Simple Rules to Refactoring

classShoppingCarthas_many:products

deffinal_pricetotal_price+country_taxend

defcountry_taxtotal_price*0.1end

deftotal_priceproducts.map(&:price).inject(&:+)endend

classShoppingCarthas_many:products

deffinal_priceproducts.map(&:price).inject(&:+)+country_taxend

defcountry_taxproducts.map(&:price).inject(&:+)*0.1endend

Page 25: 4 Simple Rules to Refactoring

classOrder<ActiveRecord::Basedefpending?status==:pendingend

defpaid?status==:paidend

defdelivered?status==:deliveredend

defcancelled?status==:cancelledend

defrefunded?status==:refundedendend

Page 26: 4 Simple Rules to Refactoring

classOrder<ActiveRecord::BaseSTATUSES=[:pending,:paid,:delivered,:cancelled,:refunded]

STATUSES.eachdo|status_name|define_method"#{status}?"dostatus==status_nameendendend

classOrder<ActiveRecord::Basedefpending?status==:pendingend

defpaid?status==:paidend

defdelivered?status==:deliveredend

defcancelled?status==:cancelledend

defrefunded?status==:refundedendend

Page 27: 4 Simple Rules to Refactoring

4 SIMPLE RULES TO REFACTORING BY STEVEN YAP

RULE #2: DON'T REPEAT YOURSELF (DRY)

▸ Easiest way to refactor

▸ Find duplicates in codes and extract them into a method/class

▸ JUST OPEN YOUR EYES AND READ YOUR CODE AGAIN

▸ Find duplicated patterns in codes and extract them

▸ Find duplicated codes across files is harder

Page 28: 4 Simple Rules to Refactoring

HATE REFACTORING 😍😡😡😡

Page 29: 4 Simple Rules to Refactoring

HATE REFACTORING 😍😍😡😡

Page 30: 4 Simple Rules to Refactoring

RULE #3: NAMING REVEALS INTENTION

Page 31: 4 Simple Rules to Refactoring

4 SIMPLE RULES TO REFACTORING BY STEVEN YAP

GOOD NAMING IN RUBY/RAILS

▸ Array.first, Array.second, Array.third, ..., Array.forty_two

▸ Date.tomorrow, Date.today

▸ 1.hour.ago, 3.weeks.from_now

▸ Array.each

Page 32: 4 Simple Rules to Refactoring

4 SIMPLE RULES TO REFACTORING BY STEVEN YAP

GOOD NAMING?

▸ shopping_cart.total_price

▸ Product.price_less_than(20)

▸ Numerology.calculate_lucky_number_from(dob: user.dob)

▸ person.male?

▸ get_youtube_id(youtube_url)

Page 33: 4 Simple Rules to Refactoring

4 SIMPLE RULES TO REFACTORING BY STEVEN YAP

RULE #3: NAMING REVEALS INTENTION

▸ Naming for variable, method, class, file, even values

▸ Reveal what you are doing or why you are doing, not how you are doingeg.(how) UserMailer.use_gmail_smtp_send_email(what) UserMailer.send_email(why) UserMailer.send_activation_email

▸ The next coder can understand/guess intuitively what your variable/method/class is doing

Page 34: 4 Simple Rules to Refactoring

require'rails_helper'

describeProductsController,type::controllerdodescribe'#index'dolet!(:p1){create(:product,published:true)}let!(:p2){create(:product,published:false)}

it'returnproducts'doget:indexexpect(assigns(:products)).to_notincludep2endendend

Page 35: 4 Simple Rules to Refactoring

require'rails_helper'

describeProductsController,type::controllerdodescribe'#index'dolet!(:published_product){create(:product,published:true)}let!(:unpublished_product){create(:product,published:false)}

it'doesnotreturnunpublishedproducts'doget:indexexpect(assigns(:products)).to_notincludeunpublished_productendendend

require'rails_helper'

describeProductsController,type::controllerdodescribe'#index'dolet!(:p1){create(:product,published:true)}let!(:p2){create(:product,published:false)}

it'returnproducts'doget:indexexpect(assigns(:products)).to_notincludep2endendend

Page 36: 4 Simple Rules to Refactoring

classUser<ActiveRecord::Baseafter_create:verify

defverifyUserMailer.verify(self).deliverendend

Page 37: 4 Simple Rules to Refactoring

classUser<ActiveRecord::Baseafter_create:verify

defverify#sendemailverificationUserMailer.verify(self).deliverendend

Page 38: 4 Simple Rules to Refactoring

classUser<ActiveRecord::Baseafter_create:send_email_verification

defsend_email_verificationUserMailer.verify_email(self).deliverendend

classUser<ActiveRecord::Baseafter_create:verify

defverify#sendemailverificationUserMailer.verify(self).deliverendend

Page 39: 4 Simple Rules to Refactoring

4 SIMPLE RULES TO REFACTORING BY STEVEN YAP

RULE #3: NAMING REVEALS INTENTION - THE DON'TS

▸ DON'T DO THIS:

▸ n = 100

▸ p1 = Product.new

▸ product1 = Product.new

▸ category = Product.new

▸ Write comments to explain your code*

Page 40: 4 Simple Rules to Refactoring

4 SIMPLE RULES TO REFACTORING BY STEVEN YAP

RULE #3: NAMING REVEALS INTENTION - THE DOS

▸ Use conventional naming:created_at (datetime) vs created_on (date)products (array of product)published?

▸ Name what the variable/method/class is doing or why it needs to do this

▸ Search in thesaurus.com

▸ Ask a non-coder how to name something or describe what you want to do

▸ Convert comment into a method instead

▸ Ask yourself: How can I let myself understand this code 1 year later?

Page 41: 4 Simple Rules to Refactoring

HATE REFACTORING 😍😍😡😡

Page 42: 4 Simple Rules to Refactoring

HATE REFACTORING 😍😍😍😡

Page 43: 4 Simple Rules to Refactoring

RULE #4: SINGLE RESPONSIBILITY PRINCIPLE

Page 44: 4 Simple Rules to Refactoring

4 SIMPLE RULES TO REFACTORING BY STEVEN YAP

RULE #4: SINGLE RESPONSIBILITY PRINCIPLE (SRP)

▸ Robert C. Martin: "A class should have only one reason to change"

▸ A class or method should only do one thing

Page 45: 4 Simple Rules to Refactoring

classMessagesController<ApplicationControllerdefcreaterecipient=User.find(message_params[:recipient])subject=block_contact_information(message_params[:subject])body=block_contact_information(message_params[:body])

message=Message.new(recipient,subject,body)ifmessage.saverenderjson:{success:"Yourmessagehasbeensentsuccessfully"}elserenderjson:{error:message.errors}endend

private

defblock_contact_information(content)phone_regex=/(?:\+?|\b)[0-9]{4,}/content.gsub!(phone_regex,'[BlockedContent]')

email_regex=/[a-z]+[a-z0-9_\-]*@[a-z0-9_\-]+\.[a-z]{2,}/icontent.gsub!(email_regex,'[BlockedContent]')

website_regex=/https?:\/\/[\S]+/content.gsub!(website_regex,'[BlockedContent]')end

defmessage_paramsparams.require(:message).permit(:receipient,:subject,:body)endend

Page 46: 4 Simple Rules to Refactoring

defblock_contact_information(content)phone_regex=/(?:\+?|\b)[0-9]{4,}/content.gsub!(phone_regex,'[BlockedContent]')

email_regex=/[a-z]+[a-z0-9_\-]*@[a-z0-9_\-]+\.[a-z]{2,}/icontent.gsub!(email_regex,'[BlockedContent]')

website_regex=/https?:\/\/[\S]+/content.gsub!(website_regex,'[BlockedContent]')end

Page 47: 4 Simple Rules to Refactoring

defblock_contact_information(content)block_phone_contact(content)block_email_contact(content)block_website_contact(content)end

defblock_phone_contact(content)phone_regex=/(?:\+?|\b)[0-9]{4,}/content.gsub!(phone_regex,'[BlockedContent]')end

defblock_email_contact(content)email_regex=/[a-z]+[a-z0-9_\-]*@[a-z0-9_\-]+\.[a-z]{2,}/icontent.gsub!(email_regex,'[BlockedContent]')end

defblock_website_contact(content)website_regex=/https?:\/\/[\S]+/content.gsub!(website_regex,'[BlockedContent]')end

defblock_contact_information(content)phone_regex=/(?:\+?|\b)[0-9]{4,}/content.gsub!(phone_regex,'[BlockedContent]')

email_regex=/[a-z]+[a-z0-9_\-]*@[a-z0-9_\-]+\.[a-z]{2,}/icontent.gsub!(email_regex,'[BlockedContent]')

website_regex=/https?:\/\/[\S]+/content.gsub!(website_regex,'[BlockedContent]')end

Page 48: 4 Simple Rules to Refactoring

classBlockContactdefself.santize(content)[:phone,:email,:website].inject(content)do|content,contact_type|self.send("block_#{contact_type}",content)endend

defself.block_phone(content)phone_regex=/(?:\+?|\b)[0-9]{4,}/content.gsub(phone_regex,'[BlockedContent]')end

defself.block_email(content)email_regex=/[a-z]+[a-z0-9_\-]*@[a-z0-9_\-]+\.[a-z]{2,}/icontent.gsub(email_regex,'[BlockedContent]')end

defself.block_website(content)website_regex=/https?:\/\/[\S]+/content.gsub(website_regex,'[BlockedContent]')endend

#BlockContact.santize('[email protected]')#=>"Myphoneis[BlockedContent]andmyemailis[BlockedContent]"

Page 49: 4 Simple Rules to Refactoring

4 SIMPLE RULES TO REFACTORING BY STEVEN YAP

RULE #4: SINGLE RESPONSIBILITY PRINCIPLE (SRP)

▸ Long methods are the easiest to find (>10 lines?)

▸ Know the responsibility of Model, View, Controller well

▸ Ask yourself if this object is doing the right thing

▸ Ask yourself if this method is doing only one thing

▸ SRP normally give rise to more advanced refactoring/patternsEg. observer, delegate, services, etc

Page 50: 4 Simple Rules to Refactoring

HATE REFACTORING 😍😍😍😡

Page 51: 4 Simple Rules to Refactoring

HATE REFACTORING 😍😍😍😍

Page 52: 4 Simple Rules to Refactoring

LOVE REFACTORING 😍😍😍😍

Page 53: 4 Simple Rules to Refactoring

4 SIMPLE RULES TO REFACTORING BY STEVEN YAP

USING THE FOUR RULES

▸ Rule #1 of rule #1:Always think about test cases first!

▸ Refactor using DRY + Naming + SRP iteratively

▸ Think through your code using the 4 rules to achieve a minimum good quality

▸ Don't stop at the 4 rules & learn/refactor more as you gain more experience(eg. SOLID principles, LoD, Anti-patterns)

Page 54: 4 Simple Rules to Refactoring

4 SIMPLE RULES TO REFACTORING BY STEVEN YAP

FINAL TIPS ON REFACTORING & FEELING BETTER AS A CODER

▸ Don't stress yourself to refactor the code to be the bestThere are no BEST code in the world

▸ Don't be hard on yourself if you cannot detect a code smellLearn from it! You will become better over time!

▸ Take a moment to relish your refactored code! Be proud to show it off in the pull request for code review.

▸ Protect your reputation as a world-class coderHave pride as a coder!

Page 55: 4 Simple Rules to Refactoring

CLEAN & CLEAR CODE EASY TO UNDERSTAND

Page 56: 4 Simple Rules to Refactoring

CODE IS BEAUTIFUL CODE IS ART

Page 57: 4 Simple Rules to Refactoring

LOVE REFACTORING 😍😍😍😍

4 SIMPLE RULES TO REFACTORING BY STEVEN YAP