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

Escaping The Tar Pit at RubyConf 2019

Escaping The Tar Pit at RubyConf 2019

Presentation about avoiding and escaping the tar pit. The case for a new metric: The StinkScore.

The StinkScore is a combination of smells; complexity; and code coverage. You can use it as a compass to get out of the tar pit and gradually (and measurably) pay off technical debt. 🤓❤️

Presented at RubyConf in Nashville (Nov 19th, 2019)

Ernesto Tagwerker

November 19, 2019
Tweet

More Decks by Ernesto Tagwerker

Other Decks in Programming

Transcript

  1. Escaping
    The Tar Pit
    Ernesto Tagwerker (@etagwerker)
    RubyConf, November 2019

    View Slide

  2. ! Hi, I’m from Argentina
    I live in Philadelphia
    # I ❤ Open Source

    View Slide

  3. Founder &
    Software Engineer
    @OmbuLabs

    View Slide

  4. Founder &
    Software Engineer
    @fastrubyio

    View Slide

  5. Open Source
    Maintainer
    database_cleaner;
    bundler-leak; next_rails

    View Slide

  6. Inspiration

    View Slide

  7. Chapter 1:
    The Tar Pit

    View Slide

  8. “The fiercer the struggle, the
    more entangling the tar, and
    no beast is so strong or so skillful
    but that they ultimately sink.”
    Fred Brooks

    View Slide

  9. “Large-system programming has
    over the past decade been
    such a tar pit, and many great
    and powerful beasts have thrashed
    violently in it.” Fred Brooks

    View Slide

  10. We are in the tar pit or
    trying to avoid it

    View Slide

  11. View Slide

  12. Tar Pit Symptoms
    > Projects running over budget

    View Slide

  13. Tar Pit Symptoms
    > Projects running over budget
    > Taking forever to ship small changes

    View Slide

  14. Tar Pit Symptoms
    > Projects running over budget
    > Taking forever to ship small changes
    > Sacrificing quality, increasing tech debt

    View Slide

  15. View Slide

  16. Part 1: How to avoid
    the tar pit

    View Slide

  17. “THAT’D BE GREAT”
    BOSS: “IF YOU COULD COME IN ON THIS GREAT, LEGACY
    PROJECT AND MAINTAIN IT FROM NOW ON…”

    View Slide

  18. “THAT’D BE GREAT”
    CLIENT: “IF YOU COULD COME IN ON THIS GREAT, LEGACY
    PROJECT AND MAINTAIN IT FROM NOW ON…”

    View Slide

  19. CLIENT: “HOW LONG IS IT GOING TO TAKE?”

    View Slide

  20. “BECAUSE I NEED IT BY END OF DAY TODAY”
    CLIENT: “HOW LONG IS IT GOING TO TAKE?”

    View Slide

  21. How can we quickly
    assess code quality?

    View Slide

  22. Part 2: How to get out
    of the tar pit

    View Slide

  23. How can we gradually
    pay off technical debt?

    View Slide

  24. Part 1: Avoiding
    the tar pit

    View Slide

  25. View Slide

  26. “It can’t be that hard. It’s a Ruby project.”

    View Slide

  27. How can we quickly assess
    code quality?

    View Slide

  28. Assessing Quality
    ✅ Paid Services
    ✅ Open Source Gems

    View Slide

  29. Assessing Quality
    ✅ Paid Services
    ➡ Open Source Gems

    View Slide

  30. Open Source Gems
    ✅ Static code analysis

    View Slide

  31. Open Source Gems
    ✅ Static code analysis
    ✅ Code coverage

    View Slide

  32. Open Source Gems
    ✅ Static code analysis
    ✅ Code coverage
    ✅ Code smells

    View Slide

  33. What is software
    quality anyway?

    View Slide

  34. There are hundreds of
    definitions

    View Slide

  35. “The degree to which a
    system, component, or
    process meets implicit and
    explicit requirements.” IEEE

    View Slide

  36. “It works as expected.”

    View Slide

  37. “It works as expected
    and it is not a PITA to
    maintain it.”

    View Slide

  38. ISO 9126-1 Software Quality Model
    ✅ Reliability
    ✅ Usability
    ✅ Efficiency
    ✅ Maintainability
    ✅ Portability

    View Slide

  39. ISO 9126-1 Software Quality Model
    ✅ Reliability
    ✅ Usability
    ✅ Efficiency
    ➡ Maintainability
    ✅ Portability

    View Slide

  40. Maintainability

    View Slide

  41. Maintainability
    1. Code Coverage

    View Slide

  42. Maintainability
    1. Code Coverage
    2. Code Quality

    View Slide

  43. Code Coverage
    SimpleCov
    (github.com/colszowka/simplecov)

    View Slide

  44. Code Coverage
    # Gemfile
    group :test do
    gem "simplecov", require: false
    end

    View Slide

  45. Code Coverage
    # spec/spec_helper.rb
    if ENV["COVERAGE"] == "true"
    require 'simplecov'
    SimpleCov.start do
    add_group "Models", "models"
    add_filter "/spec/"
    track_files "**/*.rb"
    end
    end

    View Slide

  46. Code Coverage
    $ COVERAGE=true rspec

    View Slide

  47. Code Coverage

    View Slide

  48. Code Quality
    n options

    View Slide

  49. Code Quality
    n options (flog; flay; reek;
    churn; RubyCritic; MetricFu;
    attractor; rubocop;
    fukuzatsu; turbulence; …)

    View Slide

  50. Code Quality
    RubyCritic
    (github.com/whitesmith/rubycritic)

    View Slide

  51. Churn

    View Slide

  52. Churn
    git

    View Slide

  53. Churn
    $ git log --follow --format=%h default.rb | cat
    573c8f5
    2ccc5b8
    b447bc3

    View Slide

  54. Churn
    $ git log --follow --format=%h default.rb | cat
    573c8f5
    2ccc5b8
    b447bc3
    # Churn Count = 3

    View Slide

  55. Complexity

    View Slide

  56. Complexity
    flog

    View Slide

  57. Complexity
    $ flog foo.rb
    => 10.8: flog total

    View Slide

  58. Complexity
    class Foo
    def yay # 10.8 =
    a = eval "1+1" # 1.2 + 6.0 +
    if a == 2 # 1.2 + 1.2 +
    puts "yay" # 1.2
    end
    end
    end

    View Slide

  59. Complexity
    $ flog foo.rb
    => 10.8: flog total

    View Slide

  60. Churn
    vs.
    Complexity

    View Slide

  61. Churn
    Complexity
    50
    0
    1 100_000

    View Slide

  62. Churn
    Complexity
    100
    0
    1 100_000
    user.rb (changed: 2 times; complexity: 100_000 )

    View Slide

  63. Churn
    Complexity
    100
    0
    1 100_000
    user.rb
    (changed: 27 times; complexity: 100)

    View Slide

  64. Code Quality
    $ gem install rubycritic

    View Slide

  65. Code Quality
    $ rubycritic

    View Slide

  66. View Slide

  67. GPA Pie Graph
    Complexity (flog)
    Code Smells (reek)

    View Slide

  68. Churn vs.
    Complexity
    Churn (SCM)
    Complexity (flog)

    View Slide

  69. 100
    0
    1 100_000
    Complexity
    Churn

    View Slide

  70. 100
    0
    1 100_000
    Complexity
    Churn

    View Slide

  71. 100
    0
    1 100_000
    “Welcome to
    the good place.” ❤
    Complexity
    Churn

    View Slide

  72. 100
    0
    1 100_000
    No one understands
    these files but they
    work. So don’t change
    them.
    Complexity
    Churn

    View Slide

  73. 100
    0
    1 100_000
    “[…], if the code
    never changes, it's not
    costing us money.”
    Sandi Metz
    Complexity
    Churn

    View Slide

  74. 100
    0
    1 100_000
    Everybody
    understands these files
    but you need to
    change them often
    Complexity
    Churn

    View Slide

  75. 100
    0
    1 100_000
    Complexity
    Churn
    These modules are
    complex and change a
    lot… '

    View Slide

  76. 100
    0
    1 100_000
    Complexity
    Churn
    “Sometimes a class
    becomes so complex that
    refactoring seems too
    difficult.”
    Michael Feathers

    View Slide

  77. Are you getting into a tar
    pit, is it a dumpster fire, or
    have you found a project
    which is easy to maintain?

    View Slide

  78. 100
    0
    1 100_000
    Welcome to the tar pit.
    Complexity
    Churn

    View Slide

  79. Maintainability
    1. Code Coverage (SimpleCov)
    2. Code Quality (RubyCritic)

    View Slide

  80. Signal #1
    Code Coverage

    View Slide

  81. Signal #2
    Complexity

    View Slide

  82. Coverage & Complexity

    View Slide

  83. But what if we could
    calculate a new signal
    that combines both?

    View Slide

  84. Signal #3
    StinkScore

    View Slide

  85. StinkScore =
    f(code_quality, code_coverage)

    View Slide

  86. StinkScore =
    f(code_smells,
    code_complexity,
    code_coverage)

    View Slide

  87. Files which lack
    tests should be
    penalized

    View Slide

  88. foo.rb
    complexity: 10
    smells: 10
    smell points: 100
    (10*10)

    View Slide

  89. bar.rb
    complexity: 10
    smells: 10
    smell points: 100
    (10*10)

    View Slide

  90. foo.rb
    complexity: 10
    smells: 10
    smell points: 100
    (10*10)
    bar.rb
    complexity: 10
    smells: 10
    smell points: 100
    (10*10)

    View Slide

  91. foo.rb and bar.rb are
    equally stinky

    View Slide

  92. foo.rb
    complexity: 10
    smells: 10
    code_coverage: 0
    stink_score: 10,000
    (10*10)*100

    View Slide

  93. bar.rb
    complexity: 10
    smells: 10
    code_coverage: 100
    stink_score: 100
    (10*10)*1

    View Slide

  94. foo.rb
    complexity: 10
    smells: 10
    code_coverage: 0
    stink_score: 10,000
    (10*10)*100
    bar.rb
    complexity: 10
    smells: 10
    code_coverage: 100
    stink_score: 100
    (10*10)*1

    View Slide

  95. foo.rb is
    considerably
    stinkier
    than bar.rb

    View Slide

  96. How can we automate
    this analysis?

    View Slide

  97. Skunk
    A Stink Score Calculator
    (github.com/fastruby/skunk)

    View Slide

  98. Skunk
    $ gem install skunk

    View Slide

  99. Skunk
    $ skunk

    View Slide

  100. View Slide

  101. View Slide

  102. Skunk
    $ skunk
    ...
    StinkScore Total: 13231.069999999996
    Modules Analysed: 71
    StinkScore Average: 186.3530985915493
    Worst StinkScore: 2401.75 (lib/rubycritic/source_control_systems/git.rb)

    View Slide

  103. Skunk
    $ skunk
    ...
    StinkScore Total: 13231.069999999996
    Modules Analysed: 71
    StinkScore Average: 186.3530985915493
    Worst StinkScore: 2401.75 (lib/rubycritic/source_control_systems/git.rb)

    View Slide

  104. RubyCritic
    module RubyCritic
    class AnalysedModule
    def cost
    smells.map(&:cost).inject(0.0, :+) +
    (complexity / COMPLEXITY_FACTOR)
    end
    end
    end

    View Slide

  105. RubyCritic
    module RubyCritic
    class AnalysedModule
    def cost
    smells.map(&:cost).inject(0.0, :+) + # From Reek
    (complexity / COMPLEXITY_FACTOR)
    end
    end
    end

    View Slide

  106. RubyCritic
    module RubyCritic
    class AnalysedModule
    def cost
    smells.map(&:cost).inject(0.0, :+) + # From Reek
    (complexity / COMPLEXITY_FACTOR) # From Flog
    end
    end
    end

    View Slide

  107. Skunk
    module RubyCritic
    class AnalysedModule
    def stink_score
    return cost if perfect_coverage?
    cost * (PERFECT_COVERAGE - coverage.to_i)
    end
    end
    end

    View Slide

  108. Skunk
    module RubyCritic
    class AnalysedModule
    def stink_score
    return cost if perfect_coverage?
    cost * (PERFECT_COVERAGE - coverage.to_i)
    # Penalty Factor = 100% - 20% => 80
    end
    end
    end

    View Slide

  109. Skunk Warning
    > skunk-v0.3.1

    View Slide

  110. Skunk Warning
    > skunk-v0.3.1
    > COVERAGE=TRUE rake test

    View Slide

  111. Skunk Warning
    > skunk-v0.3.1
    > COVERAGE=TRUE rake test
    > feedback wanted

    View Slide

  112. Maintainability
    1. Code Coverage (SimpleCov)
    2. Code Quality (RubyCritic)
    3. StinkScore (Skunk)

    View Slide

  113. You are here:

    View Slide

  114. Part 2: Getting out
    of the tar pit

    View Slide

  115. So, where do we begin?

    View Slide

  116. removing files
    will decrease
    our stink_score

    View Slide

  117. Find Dead Code
    Coverband
    (github.com/danmayer/coverband)

    View Slide

  118. View Slide

  119. refactoring
    complex files
    will decrease
    our stink_score

    View Slide

  120. 100
    0
    1 100_000
    Complexity
    Churn
    “Here be dragons.
    These modules are
    complex and
    change a lot…” '

    View Slide

  121. 100
    0%
    1 100_000
    Complexity
    Churn
    “Great
    candidates for
    refactoring
    (paying technical
    debt!)” #

    View Slide

  122. Churn vs. Complexity is
    not enough to prioritize

    View Slide

  123. Stink Score Table

    View Slide

  124. Refactor These Files:

    View Slide

  125. Refactor These Files:

    View Slide

  126. 100
    0
    1 100_000
    Complexity
    Churn
    git.rb

    View Slide

  127. 100
    0
    1 100_000
    Complexity
    Churn
    git_calculator.rb
    git_fetcher.rb
    refactor!
    git.rb

    View Slide

  128. 100
    0
    1 100_000
    Complexity
    Churn
    git_calculator.rb
    git_fetcher.rb

    View Slide

  129. Skunk
    $ skunk -b master
    Base branch (master) average stink score: 79.78
    Feature branch (refactor/git) average stink score: 71.79

    View Slide

  130. Skunk
    $ skunk -b master
    Base branch (master) average stink score: 79.78
    Feature branch (refactor/git) average stink score: 71.79
    10% better ( ( (

    View Slide

  131. writing tests
    for our app will
    decrease our
    stink_score

    View Slide

  132. Stink Score Table

    View Slide

  133. Write Tests Here:

    View Slide

  134. Skunk*
    $ skunk -b master
    Base branch (master) average stink score: 79.78
    Feature branch (refactor/git) average stink score: 71.79

    View Slide

  135. Time
    StinkScore Average
    100 (days)
    0
    1 100

    View Slide

  136. stink_score
    Code Coverage;
    Complexity; and
    Smells

    View Slide

  137. stink_score
    Your compass to
    get out of the
    tar pit

    View Slide

  138. Thank you!
    @etagwerker
    138
    Thank you!
    ‣ github.com/fastruby/skunk
    ‣ @OmbuLabs
    ‣ @fastrubyio
    ‣ @etagwerker

    View Slide

  139. Resources
    1. https://github.com/whitesmith/rubycritic
    2.https://github.com/colszowka/simplecov
    3.https://github.com/metricfu/metric_fu
    4.https://github.com/julianrubisch/attractor
    5.https://www.fastruby.io/blog/ruby/quality/code-quality-ruby-gems.html
    6.https://www.reddit.com/r/ruby/comments/2bq092/rubycritic/
    7.http://jakescruggs.blogspot.com/2008/08/whats-good-flog-score.html
    8.http://ruby.sadi.st/Flog.html
    9.http://ruby.sadi.st/Flay.html
    10.https://github.com/makaroni4/sandi_meter
    11.https://www.fastruby.io/blog/code-quality/intruducing-skunk-stink-score-
    calculator.html

    View Slide

  140. Resources
    1.https://codeclimate.com/blog/deciphering-ruby-code-metrics/
    2.https://www.stickyminds.com/article/getting-empirical-about-refactoring
    3.https://www.sandimetz.com/blog/2017/9/13/breaking-up-the-behemoth
    4.https://github.com/troessner/reek
    5.https://github.com/seattlerb/flay
    6.https://github.com/seattlerb/flog
    7.http://www.sqa.net/iso9126.html
    8.https://www.slideshare.net/mscottford/important-metrics-for-measuring-code-health
    9.https://dilbert.com/strip/2006-12-08
    10.https://www.fastruby.io/blog/ruby/quality/code-quality-ruby-gems.html
    11.https://www.fastruby.io/blog/code-quality/code-coverage/rubycritic-4-2-0-
    simplecov-support.html

    View Slide