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

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.

David Copeland

November 19, 2019
Tweet

More Decks by David Copeland

Other Decks in Programming

Transcript

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

    View full-size slide

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

    View full-size slide

  3. @davetron5000
    Justin

    View full-size slide

  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?!?!??!

    View full-size slide

  5. @davetron5000
    Code does not exist
    in a vaccuum

    View full-size slide

  6. @davetron5000
    All code exists
    within some
    context

    View full-size slide

  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?

    View full-size slide

  8. @davetron5000
    Code Metrics
    Ignore Context

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  12. @davetron5000
    Rubocop Metrics
    Metric cart1.rb cart2.rb
    MethodLength 3 8
    AbcSize 6 6.71
    CyclomaticComplexity 1 3
    PerceivedComplexity 1 3

    View full-size slide

  13. @davetron5000
    How can we
    quantify the context
    of who is (or could
    be) working on this
    code?

    View full-size slide

  14. @davetron5000
    What if we counted
    all the things you
    have to know to
    understand the
    code?

    View full-size slide

  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

    View full-size slide

  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?

    View full-size slide

  17. @davetron5000
    Metrics
    Metric cart1.rb cart2.rb
    MethodLength 3 8
    AbcSize 6 6.71
    CyclomaticComplexity 1 3
    PerceivedComplexity 1 3
    # of Concepts 13

    View full-size slide

  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

    View full-size slide

  19. @davetron5000
    cart2.rb concepts
    • class
    • def
    • @line_items
    • .
    • *
    • +
    • for
    • if
    • return

    View full-size slide

  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

    View full-size slide

  21. @davetron5000
    It requires less
    knowledge to
    understand the second
    code listing, even though
    metrics tell us it's more
    complex

    View full-size slide

  22. @davetron5000
    Which code is
    "better"?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  26. @davetron5000
    All things being equal,
    code whose behavior is
    easier to understand is
    better than code whose
    behavior is harder to
    understand

    View full-size slide

  27. @davetron5000
    Understand…by
    who?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  30. @davetron5000
    Josh

    View full-size slide

  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

    View full-size slide

  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?

    View full-size slide

  33. @davetron5000
    But look at these concepts
    • class
    • def
    • @line_items
    • .
    • *
    • +
    • for
    • if
    • return

    View full-size slide

  34. @davetron5000
    The "more complex"
    code requires fewer
    concepts to understand
    and the concepts it does
    require are more
    universal

    View full-size slide

  35. @davetron5000
    Understanding
    Author
    Team Org Rubyists Programmers
    More Valuable / Better

    View full-size slide

  36. @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?

    View full-size slide

  37. @davetron5000
    Your code, and the
    concepts required to
    understand it, paint a
    picture of who can be on
    your team

    View full-size slide

  38. @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?

    View full-size slide

  39. @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

    View full-size slide

  40. @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
    • ==

    View full-size slide

  41. @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)

    View full-size slide

  42. @davetron5000
    –Various Doctrines & Manifestos
    “But you are
    ignoring the power
    of these tools.
    These SHARP KNIVES!”

    View full-size slide

  43. @davetron5000
    Sharp…or shiny?

    View full-size slide

  44. @davetron5000
    triangle_spec.rb
    describe Triangle do
    subject { Triange.new(3,3,3) }
    it { is_expected.to be_isoscoles.and be_equilateral }
    end

    View full-size slide

  45. @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

    View full-size slide

  46. @davetron5000
    config/routes.rb
    resources :messages do
    resources :comments
    end
    resources :articles do
    resources :comments
    end

    View full-size slide

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

    View full-size slide

  48. @davetron5000
    config/routes.rb
    def commentable_resource(resource)
    resources resource do
    resources :comments
    end
    end
    commentable_resource(:messages)
    commentable_resource(:articles)

    View full-size slide

  49. @davetron5000
    Still…abstractions
    are good, right?
    They reduce errors,
    ossify conventions,
    and let us go faster.

    View full-size slide

  50. @davetron5000
    Who is on the team
    matters. Who can
    and cannot easily
    join the team
    matters.

    View full-size slide

  51. @davetron5000
    Adding a concept,
    bringing in a new
    feature, adding a library,
    carries one of two
    possible costs:

    View full-size slide

  52. @davetron5000
    The cost to bring
    everyone along, or
    the cost of leaving
    some behind

    View full-size slide

  53. @davetron5000
    Like everything, it's
    a tradeoff. But you
    should make it with
    eyes open.

    View full-size slide

  54. @davetron5000

    View full-size slide

  55. @davetron5000
    But the complexity of
    the domain, the
    system, the
    deployment…who cares
    about the code?!

    View full-size slide

  56. @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

    View full-size slide

  57. @davetron5000
    # of concepts, who
    knows them, who
    could learn them—
    is fractal

    View full-size slide

  58. @davetron5000
    controller1.rb
    def mau
    Customer.signed_up.
    with_order("created_at > ?",1.year.ago).
    paid.
    count
    end
    SELECT COUNT(*) from
    CUSTOMERS
    WHERE .....

    View full-size slide

  59. @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 .....

    View full-size slide

  60. @davetron5000
    Monolith vs. Microservices

    View full-size slide

  61. @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.

    View full-size slide

  62. @davetron5000
    sustainable-rails.com

    View full-size slide