Rules, Laws, and Gentle Guidelines

Fc59401781a26b10f5d4fc5b758fb3b7?s=47 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

Fc59401781a26b10f5d4fc5b758fb3b7?s=128

Andrew Radev

September 23, 2016
Tweet

Transcript

  1. None
  2. @AndrewRadev

  3. Rad Dev

  4. Rules, Laws, and Gentle Guidelines

  5. The Law of Demeter

  6. cart.user.profile.address

  7. cart.user_profile_address

  8. user_first_name created_at_to_date order_order_item

  9. Object-Oriented Programming: An Objective Sense of Style K. Lieberherr, I.

    Holland, A. Riel (1988)
  10. 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
  11. Formal(ish) definition self.friend self.nose self.friend.nose def a_method(noses) # ... end

  12. The Paperboy, The Wallet, and The Law Of Demeter By

    David Bock
  13. class Paperboy

  14. class Customer

  15. 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 } }
  16. Problems “the paperboy is being exposed to more information than

    he needs to be”
  17. public class Customer { public float getPayment(float bill) { //

    ... } }
  18. 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 } }
  19. 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 } }
  20. 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 } }
  21. 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 } }
  22. 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
  23. cart.user.address cart.user_address

  24. class Cart < AgileRecord::Base belongs_to :user delegate :address, to: :user,

    prefix: true # => user_address end
  25. class Cart < AgileRecord::Base belongs_to :user def shipping_address user.address end

    end
  26. class Cart < AgileRecord::Base belongs_to :user has_one :shipping_address end

  27. 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
  28. Law of Demeter 1) Naming 2) 3)

  29. Law of Demeter 1) Naming 2) Method parameters 3)

  30. 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
  31. 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
  32. class User < AvocadoRecord::Base def send_weekly_email_summary? email_settings.weekly_summary? end end

  33. def send_a_bunch_of_emails(user, email_settings) if email_settings.weekly_summary? SendWeeklySummary.perform_later(user) elsif email_settings.daily_summary? SendDailySummary.perform_later(user) end

    end
  34. def send_a_bunch_of_emails(recipient, email_settings) if email_settings.weekly_summary? SendWeeklySummary.perform_later(recipient) elsif email_settings.daily_summary? SendDailySummary.perform_later(recipient) end

    end send_a_bunch_of_emails(user, user.email_settings) send_a_bunch_of_emails( organization.member, organization.email_settings)
  35. def send_a_bunch_of_emails(user, email_settings) if email_settings.weekly_summary? SendWeeklySummary.perform_later(user) elsif email_settings.daily_summary? SendDailySummary.perform_later(user) end

    end
  36. def send_a_bunch_of_emails(user) email_settings = user.email_settings if email_settings.weekly_summary? SendWeeklySummary.perform_later(user) elsif email_settings.daily_summary?

    SendDailySummary.perform_later(user) end end
  37. Law of Demeter 1) Naming 2) Method parameters 3)

  38. Law of Demeter 1) Naming 2) Method parameters 3) Not

    really a “law”
  39. Object-Oriented Programming: An Objective Sense of Style

  40. 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
  41. The Law of Demeter

  42. The Guideline of Demeter

  43. A tool to understand your code

  44. A tool, not a weapon

  45. SRP

  46. Single Responsibility Principle

  47. 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
  48. 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
  49. class Comment < AggressiveRecord::Base after_save :update_article_counter after_save :update_some_other_thing end

  50. class NotSRP < AggressiveRecord::Base after_save :update_article_counter after_save :update_some_other_thing end

  51. class CommentManager? ### NOT SRP! ### end

  52. 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
  53. 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
  54. 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
  55. SRP

  56. 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.”
  57. “it should do one thing” “it does too much” “it

    should have one reason to change”
  58. “Do one thing” vs “Have one reason to change”

  59. scenario "registration form" do visit '/register' fill_in_login_details(params) fill_in_address(params) fill_in_banking_info(params) expect_user_to_be_registered

    end
  60. 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
  61. def fill_in_banking_info(params) fill_in 'iban', with: params[:iban] fill_in 'bic', with: params[:bic]

    click_button 'Submit' end
  62. def fill_in_banking_info(params) fill_in 'iban', with: params[:iban] fill_in 'bic', with: params[:bic]

    end def submit_form click_button 'Submit' end
  63. 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
  64. def fill_in_banking_info(params) fill_in 'iban', with: params[:iban] fill_in 'bic', with: params[:bic]

    click_button 'Submit' end
  65. 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
  66. 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
  67. 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
  68. 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
  69. def fill_in_banking_info(params) fill_in 'iban', with: params[:iban] fill_in 'bic', with: params[:bic]

    click_button 'Submit' end
  70. SRP 1) Naming 2) 3)

  71. SRP 1) Naming 2) Redrawing the borders 3)

  72. 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
  73. scenario "shopping cart" do # ... fill_in_address(params) next_step # ...

    fill_in_banking_info(params) next_step # ... end
  74. 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
  75. def fill_in_banking_info(params) fill_in 'iban', with: params[:iban] fill_in 'bic', with: params[:bic]

    click_button 'Submit' end
  76. Reasons to change

  77. Principles, Patterns, and Practices public interface Modem { public void

    Dial(string pno); public void Hangup(); public void Send(char c); public char Recv(); }
  78. 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
  79. SRP 1) Naming 2) Redrawing the borders 3) “Reasons to

    change” is an estimation
  80. A tool to understand your code

  81. Not the end of a conversation, but the start of

    one
  82. Fuzzy principles The Boyscout Rule YAGNI Premature Optimization DRY

  83. It's easier to ask forgiveness than it is to get

    permission
  84. <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>
  85. -<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>
  86. <% @products.each do |p| %> - <%= render 'product_card', product:

    p %> + <%= SomeClass.some_method(p) %> <% end %>
  87. source 'https://rubygems.org' gem 'some_internal_gem', github: '...'

  88. #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, ...);
  89. ??? • 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
  90. It's easier to ask forgiveness than it is to get

    permission
  91. It's easier to ask forgiveness than it is to get

    permission
  92. Context

  93. “You can't fit a complex, context-dependent programming principle into a

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

  95. Application

  96. Don’t force-push stuff to master at 3 in the morning

  97. “Don’t force-push stuff to master at 3 in the morning”

    – Radev’s Second Law
  98. Thank you