Pro Yearly is on sale from $80 to $50! »

The fewer the concepts, the better the code?

The fewer the concepts, the better the code?

How many people could change the code you wrote yesterday if they had to? I hope to convince you that the larger that number, the better your code, and the key is to manage conceptual overhead. The fewer things someone has to know to read and modify your code, the better for you, your team, your company, your app. We'll see real examples of code that we'll put on a conceptual diet. We'll then talk about what that reduction actually does to code quality.

F74253f4a099258870157426b4cdb2dc?s=128

David Copeland

November 19, 2019
Tweet

Transcript

  1. @davetron5000 The Fewer the Concepts, The Better The Code Dave

    Copeland / @davetron5000
  2. @davetron5000 The Fewer the Concepts, The Better The Code Dave

    Copeland / @davetron5000
  3. @davetron5000 Justin

  4. @davetron5000 Code Reviews Justin Justin Me Me COOL WHY DID

    YOU DO IT THAT WAY? MY CODE IS SIMPLER! THE CYCLOMATIC COMPLEXITY IS LOWER, THERE ARE FEWER INTERMEDIATE VARIABLES, AND BY USING LIST COMPREHENSIONS I'VE MADE THE ENTIRE THING MUCH LESS COMPLEX AND THEREFORE EASIER TO UNDERSTAND, SO WHY AM I EXPLAINING THIS?!?!??!
  5. @davetron5000 Code does not exist in a vaccuum

  6. @davetron5000 All code exists within some context

  7. @davetron5000 All code exists within some context • What is

    it for? • How important is it that it works perfectly? • How often is it going to change? • Who is in charge of maintaining it? • Who might some day need to change it?
  8. @davetron5000 Code Metrics Ignore Context

  9. @davetron5000 cart1.rb class Cart def total @line_items.select(&:in_stock?).map { |line_item| line_item.price

    * line_item.quantity }.reduce(&:+) end end
  10. @davetron5000 Rubocop Metrics Metric cart1.rb MethodLength 3 AbcSize 6 CyclomaticComplexity

    1 PerceivedComplexity 1 Number of statements, branches, and conditionals. Count of branches or # of paths through the code Different way to look at paths through the code
  11. @davetron5000 cart2.rb class Cart def total total_price = 0 for

    line_item in @line_items do if line_item.in_stock? cost = line_item.price * line_item.quantity total_price = total_price + cost end end return total_price end end
  12. @davetron5000 Rubocop Metrics Metric cart1.rb cart2.rb MethodLength 3 8 AbcSize

    6 6.71 CyclomaticComplexity 1 3 PerceivedComplexity 1 3
  13. @davetron5000 How can we quantify the context of who is

    (or could be) working on this code?
  14. @davetron5000 What if we counted all the things you have

    to know to understand the code?
  15. @davetron5000 cart1.rb concepts class Cart def total @line_items.select(&:in_stock?).map { |line_item|

    line_item.price * line_item.quantity }.reduce(&:+) end end
  16. @davetron5000 cart1.rb concepts • class • def • @line_items •

    . • * • select, map, reduce • &:in_stock? • Why you can do that with plus • { creates a block • | defines params • How does the return value get sorted?
  17. @davetron5000 Metrics Metric cart1.rb cart2.rb MethodLength 3 8 AbcSize 6

    6.71 CyclomaticComplexity 1 3 PerceivedComplexity 1 3 # of Concepts 13
  18. @davetron5000 cart2.rb concepts class Cart def total total_price = 0

    for line_item in @line_items do if line_item.in_stock? cost = line_item.price * line_item.quantity total_price = total_price + cost end end return total_price end end
  19. @davetron5000 cart2.rb concepts • class • def • @line_items •

    . • * • + • for • if • return
  20. @davetron5000 Metrics Metric cart1.rb cart2.rb MethodLength 3 8 AbcSize 6

    6.71 CyclomaticComplexity 1 3 PerceivedComplexity 1 3 # of Concepts 13 9
  21. @davetron5000 It requires less knowledge to understand the second code

    listing, even though metrics tell us it's more complex
  22. @davetron5000 Which code is "better"?

  23. @davetron5000 What does "better" even mean?

  24. @davetron5000 What do we do with code? Write Change Read

  25. @davetron5000 We mostly need to understand what it does Write

    Understand
  26. @davetron5000 All things being equal, code whose behavior is easier

    to understand is better than code whose behavior is harder to understand
  27. @davetron5000 Understand…by who?

  28. @davetron5000 –No True Scotsman “Any Rubyist should know that stuff”

  29. @davetron5000 –Survivorship Bias “Anyone could learn this stuff”

  30. @davetron5000 Josh

  31. @davetron5000 Josh's Contributions Josh Josh WTF Code I Wrote That

    Was "Less Complex" Code Justin Wrote That Was "Less Fancy" I GET IT
  32. @davetron5000 Look at these concepts • class • def •

    @line_items • . • * • select, map, reduce • &:in_stock? • Why you can do that with plus • { creates a block • | defines params • How does the return value get sorted?
  33. @davetron5000 But look at these concepts • class • def

    • @line_items • . • * • + • for • if • return
  34. None
  35. @davetron5000 The "more complex" code requires fewer concepts to understand

    and the concepts it does require are more universal
  36. @davetron5000 Understanding Author Team Org Rubyists Programmers More Valuable /

    Better
  37. @davetron5000 How learnable are these things? • For any programmer?

    • For a programmer who knows an OO language? • For a Ruby programmer? • With a year of experience? • With 20 years experience?
  38. @davetron5000 Your code, and the concepts required to understand it,

    paint a picture of who can be on your team
  39. @davetron5000 Try this on your next PR • How many

    new concepts does this change introduce? • Does everyone on the team understand these concepts? • How hard are they to learn if you already understand the code before the change? • Did we decrease the number of people who could understand this code?
  40. @davetron5000 add_tax1.diff @@ -1,12 +1,24 @@ class Cart + TAX_RATES

    = Hash.new(0.45).tap { |hash| + hash[:dc] = 0.1 + hash[:ca] = 0.05 + hash[:va] = 0.03 + } + def total total_price = 0 for line_item in @line_items do if line_item.in_stock? cost = line_item.price * line_item.quantity - total_price = total_price + cost + tax = cost * TAX_RATES[@shipping_state] + total_price = total_price + cost + tax end end return total_price end end • Constants • Hash.new • [ ] • tap
  41. @davetron5000 add_tax2.diff @@ -1,12 +1,31 @@ class Cart def total

    total_price = 0 for line_item in @line_items do if line_item.in_stock? cost = line_item.price * line_item.quantity - total_price = total_price + cost + tax = cost * tax_rate(@shipping_state) + total_price = total_price + cost + tax end end return total_price end + + def tax_rate(shipping_state) + if shipping_state == :dc + return 0.1 + end + if shipping_state == :ca + return 0.05 + end + if shipping_state == :va + return 0.03 + end + return 0.45 + end end • ==
  42. @davetron5000 Metrics Metric cart2.rb diff1 diff2 MethodLength 8 9 9

    10 AbcSize 6.71 9.64 9.64 6 CyclomaticComplexity 3 3 3 4 PerceivedComplexity 3 3 3 4 # of Concepts 9 13 (+4) 10 (+1)
  43. @davetron5000 –Various Doctrines & Manifestos “But you are ignoring the

    power of these tools. These SHARP KNIVES!”
  44. @davetron5000 Sharp…or shiny?

  45. @davetron5000 triangle_spec.rb describe Triangle do subject { Triange.new(3,3,3) } it

    { is_expected.to be_isoscoles.and be_equilateral } end
  46. @davetron5000 triangle_spec.rb class TriangeTest < TestCase def test_isoscoles triangle =

    Triangle.new(3,3,3) assert triangle.isoscoles? end def test_equilateral triangle = Triangle.new(3,3,3) assert triangle.equilateral? end end
  47. @davetron5000 config/routes.rb resources :messages do resources :comments end resources :articles

    do resources :comments end
  48. @davetron5000 config/routes.rb concern :commentable do resources :comments end resources :messages,

    concerns: :commentable resources :articles, concerns: :commentable
  49. @davetron5000 config/routes.rb def commentable_resource(resource) resources resource do resources :comments end

    end commentable_resource(:messages) commentable_resource(:articles)
  50. @davetron5000 Still…abstractions are good, right? They reduce errors, ossify conventions,

    and let us go faster.
  51. @davetron5000 Who is on the team matters. Who can and

    cannot easily join the team matters.
  52. @davetron5000 Adding a concept, bringing in a new feature, adding

    a library, carries one of two possible costs:
  53. @davetron5000 The cost to bring everyone along, or the cost

    of leaving some behind
  54. @davetron5000 Like everything, it's a tradeoff. But you should make

    it with eyes open.
  55. @davetron5000

  56. @davetron5000 But the complexity of the domain, the system, the

    deployment…who cares about the code?!
  57. @davetron5000 Server Next Level Complexity Most metrics are about code,

    not systems method method class method method class method method class method method class app app DB Load Balancer CDN Cache Code Metrics Analysis Code Metrics Analysis
  58. @davetron5000 # of concepts, who knows them, who could learn

    them— is fractal
  59. @davetron5000 controller1.rb def mau Customer.signed_up. with_order("created_at > ?",1.year.ago). paid. count

    end SELECT COUNT(*) from CUSTOMERS WHERE .....
  60. @davetron5000 controller2.rb def mau ActiveRecord::Base.connection.execute(" SELECT COUNT(*) FROM customers JOIN

    orders ON orders.customer_id = customers.id WHERE ... ")[0][0] end SELECT COUNT(*) from CUSTOMERS WHERE .....
  61. @davetron5000 Monolith vs. Microservices

  62. None
  63. @davetron5000 The Metrics you have aren't bad, but… • What

    do you have to know to work on the system? • Who is being left behind? • Are you committed to bringing them along? • If you aren't or can't…maybe find a solution with fewer concepts required.
  64. @davetron5000 sustainable-rails.com