Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rules, Laws, and Gentle Guidelines

Andrew Radev
September 23, 2016

Rules, Laws, and Gentle Guidelines

A talk about programming rules, and about what happens when you try to take them too far.

Video: https://youtu.be/BDXQ4pcbEBA

Andrew Radev

September 23, 2016
Tweet

More Decks by Andrew Radev

Other Decks in Programming

Transcript

  1. Formal definition The Law of Demeter for functions requires that

    a method m of an object O may only invoke the methods of the following kinds of objects: • O itself • m's parameters • Any objects created/instantiated within m • O's direct component objects • A global variable, accessible by O, in the scope of m
  2. public class Paperboy { payment = 2.00; // “I want

    my two dollars!” Wallet theWallet = myCustomer.getWallet(); if (theWallet.getTotalMoney() > payment) { theWallet.subtractMoney(payment); } else { // come back later and get my money } }
  3. public class Paperboy { payment = 2.00; // “I want

    my two dollars!” paidAmount = myCustomer.getPayment(payment); if (paidAmount == payment) { // say thank you and give customer a receipt } else { // come back later and get my money } }
  4. public class Paperboy { payment = 2.00; // “I want

    my two dollars!” paidAmount = myCustomer.getPayment(payment); if (paidAmount == payment) { // say thank you and give customer a receipt } else { // come back later and get my money } }
  5. public class Paperboy { payment = 2.00; // “I want

    my two dollars!” paidAmount = myCustomer.getWalletSubtractAmount(payment); if (paidAmount == payment) { // say thank you and give customer a receipt } else { // come back later and get my money } }
  6. public class Paperboy { payment = 2.00; // “I want

    my two dollars!” paidAmount = myCustomer.subtractAmountFromWallet(payment); if (paidAmount == payment) { // say thank you and give customer a receipt } else { // come back later and get my money } }
  7. The above method appears to obey the Law of Demeter

    [ ]. However, on closer inspection … [ / … This clearly violates the spirit of the Law. 7 The Interface
  8. class Cart < AgileRecord::Base def shipping_address # ... end end

    class AnonymousCart def initialize(session) # ... end def shipping_address # guess default country/city based on # geoip, products, etc. end end
  9. class User < AvocadoRecord::Base attribute :send_weekly_email_summary?, :boolean attribute :send_daily_email_summary?, :boolean

    # … 10 more email-related attributes … end def send_a_bunch_of_emails(user) if user.send_weekly_email_summary? SendWeeklySummary.perform_later(user) elsif user.send_daily_email_summary? SendDailySummary.perform_later(user) end end
  10. class User < AvocadoRecord::Base def email_settings EmailSettings.new(...) end end def

    send_a_bunch_of_emails(user) if user.email_settings.weekly_summary? SendWeeklySummary.perform_later(user) elsif user.email_settings.daily_summary? SendDailySummary.perform_later(user) end end
  11. Writing programs which follow the the Law of Demeter [

    / … increases the number of methods. [ / … the abstraction may be less comprehensible, and implementation and maintenance are more difficult. There might also be an increase in the number of arguments passed to some methods. 6 The Trade-off
  12. SRP

  13. class UpdateComment def initialize(comment) @comment = comment end def call

    Comment.transaction do @comment.save! update_article_counter update_some_other_thing end end private def update_article_counter @comment.article.update_comment_count end def update_some_other_thing # ... end end
  14. class DestroyComment def initialize(comment) @comment = comment end def call

    Comment.transaction do @comment.destroy update_article_counter update_some_other_thing end end private def update_article_counter @comment.article.update_comment_count end def update_some_other_thing # ... end end
  15. class CommentLifecycle def initialize(comment) @comment = comment end def update

    Comment.transaction do @comment.save! update_article_counter update_some_other_thing end end def destroy Comment.transaction do @comment.destroy update_article_counter update_some_other_thing end end private # ... end
  16. class CommentLifecycle def initialize(comment) @comment = comment end def not_very_srp?

    Comment.transaction do @comment.save! update_article_counter update_some_other_thing end end def destroy Comment.transaction do @comment.destroy update_article_counter update_some_other_thing end end private # ... end
  17. class CommentLifecycle def initialize(comment) @comment = comment end def not_very_srp?

    Comment.transaction do @comment.save! update_article_counter update_some_other_thing end end def not_very_srp! Comment.transaction do @comment.destroy update_article_counter update_some_other_thing end end private # ... end
  18. SRP

  19. Robert C. Martin Separation of Concerns, Coupling, Cohesion “In the

    late 1990s I tried to consolidate these notions into a principle, which I called: The Single Responsibility Principle.” “[It] states that each software module should have one and only one reason to change.”
  20. “it should do one thing” “it does too much” “it

    should have one reason to change”
  21. def fill_in_login_details(params) fill_in 'email', with: params[:email] fill_in 'password', with: params[:password]

    end def fill_in_address(params) fill_in 'city', with: params[:city] fill_in 'country', with: params[:country] # ... end def fill_in_banking_info(params) fill_in 'iban', with: params[:iban] fill_in 'bic', with: params[:bic] click_button 'Submit' end
  22. def fill_in_banking_info(params) fill_in 'email', with: params[:email] fill_in 'password', with: params[:password]

    fill_in 'city', with: params[:city] fill_in 'country', with: params[:country] # ... fill_in 'iban', with: params[:iban] fill_in 'bic', with: params[:bic] click_button 'Submit' end
  23. def fill_in_banking_info(params) visit '/register' fill_in 'email', with: params[:email] fill_in 'password',

    with: params[:password] fill_in 'city', with: params[:city] fill_in 'country', with: params[:country] # ... fill_in 'iban', with: params[:iban] fill_in 'bic', with: params[:bic] click_button 'Submit' end
  24. def register_user(params) visit '/register' fill_in 'email', with: params[:email] fill_in 'password',

    with: params[:password] fill_in 'city', with: params[:city] fill_in 'country', with: params[:country] # ... fill_in 'iban', with: params[:iban] fill_in 'bic', with: params[:bic] click_button 'Submit' end
  25. scenario "registration form creates a user" do register_user(user_params) expect_user_to_be_registered end

    scenario "registration form sends an email" do register_user(user_params) expect_an_email_to_have_been_sent end scenario "invalid registration form" do register_user(invalid_user_params) expect_errors_in_the_form end
  26. scenario "shopping cart" do # ... fill_in_address(params) next_step # ...

    fill_in_banking_info(params) next_step # ... end
  27. scenario "registration form" do visit '/register' fill_in_login_details(params) fill_in_address(params) fill_in_banking_info(params) submit_form

    expect_user_to_be_registered end scenario "registration form" do visit '/register' fill_in_login_details(invalid_params) fill_in_address(invalid_params) fill_in_banking_info(invalid_params) submit_form expect_errors_in_form end
  28. Principles, Patterns, and Practices public interface Modem { public void

    Dial(string pno); public void Hangup(); public void Send(char c); public char Recv(); }
  29. Responsibilities “Should these two responsibilities be separated? That depends upon

    how the application is changing.” “[…] If, on the other hand, the application is not changing in ways that cause the the two responsibilities to change at different times, then there is no need to separate them.” “An axis of change is only an axis of change if the changes actually occur. It is not wise to apply the SRP, or any other principle for that matter, if there is no symptom.” – Robert C. Martin
  30. <div class="product-wrapper"> <img class="product-image" src="<%= product.image_url %>"> <div class="product-info"> <h2

    class="product-title"><%= product.title %></h2> <p class="product-description"> <%= product.description %> </p> </div> </div>
  31. -<div class="product-wrapper"> - <img class="product-image" - src="<%= product.image_url %>"> -

    - <div class="product-info"> - <h2 class="product-title"><%= product.title %></h2> - - <p class="product-description"> - <%= product.description %> - </p> - </div> -</div>
  32. <% @products.each do |p| %> - <%= render 'product_card', product:

    p %> + <%= SomeClass.some_method(p) %> <% end %>
  33. #define HTML \ "<div class=\"product-wrapper\"> \ <img class=\"product-image\" src=\"%s\"> \

    <div class=\"product-info\"> \ <h2 class=\"product-title\">%s</h2> \ <p class=\"product-description\"> \ %s \ </p> \ </div> \ </div>" char data[1000]; sprintf(data, HTML, ...);
  34. ??? • It’s fast! • Coded properly • “Use the

    best tool for the job” • We’re not really going to touch it again • It’s already done
  35. “You can't fit a complex, context-dependent programming principle into a

    tweet-length sentence.” – Radev’s First Law