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

Here Be Dragons: The Hidden Gems of Technical Debt

Here Be Dragons: The Hidden Gems of Technical Debt

How do you find the most unmaintainable code in your codebase? What will you prioritize in your next technical debt spike, and why?

In this talk you will learn how you can use RubyCritic, SimpleCov, Flog, Reek, and Skunk to slay dragons in your next refactoring adventure! Find out how the relationship between churn, complexity, and code coverage can give you a common language to talk about code quality and increase trust in your code.

Presented at RubyConf Mini 2022.

Ernesto Tagwerker

November 16, 2022
Tweet

More Decks by Ernesto Tagwerker

Other Decks in Programming

Transcript

  1. Here Be Dragons: The Hidden Gems of Tech Debt Ernesto

    Tagwerker (@etagwerker) RubyConf Mini, November 2022
  2. “Be careful with this part of the world, nobody has

    been there, there may be monsters.”
  3. “Be careful with this part of the world, nobody has

    been there, there may be monsters.”
  4. “Be careful with this part of the codebase, nobody wants

    to touch it, there is a lot of tech debt.”
  5. 1. The Complexity Dragon 2. The Smelly Dragon 3. The

    Shapeshifting Dragon 4. The Untested Dragon
  6. Complexity 1 class Foo 2 def yay 3 a =

    eval "1+1" 4 if a == 2 5 puts "yay" 6 end 7 end 8 end
  7. Complexity 1 class Foo 2 def yay 3 a =

    eval "1+1" # 1.2 + 6.0 4 if a == 2 # + 1.2 5 puts “yay" # + 1.2 6 end. # = 7 end. # 10.6 8 end
  8. 55 ## flog.rb 56 # Eval forms 57 58 SCORES.merge!(:define_method

    => 5, 59 :eval => 5, 60 :module_eval => 5, 61 :class_eval => 5, 62 :instance_eval => 5) 63
  9. 62 ## flog.rb 63 # Various "magic" usually used for

    "clever code" 64 65 SCORES.merge!(:alias_method => 2, 66 :extend => 2, 67 :include => 2, 68 :instance_method => 2, 69 :instance_methods => 2, 70 :method_added => 2, 71 :method_defined? => 2, 72 :method_removed => 2, 73 :method_undefined => 2, 74 :private_class_method => 2, 75 :private_instance_methods => 2, 76 :private_method_defined? => 2, 77 :protected_instance_methods => 2, 78 :protected_method_defined? => 2,
  10. 37 ## flog.rb 38 # Various non-call constructs 39 40

    OTHER_SCORES = { 41 :alias => 2, 42 :assignment => 1, 43 :block => 1, 44 :block_pass => 1, 45 :block_call => 1, 46 :branch => 1, 47 :lit_fixnum => 0.25, 48 :sclass => 5, 49 :super => 1, 50 :to_proc_icky! => 10, 51 :to_proc_lasgn => 15, 52 :yield => 1, 53 } 54
  11. Code Smells “Smells are indicators of where your code might

    be hard to read, maintain or evolve.”
  12. 1 module Reek 2 module SmellDetectors 3 class UncommunicativeVariableName <

    BaseDetector 4 # 5 # Checks the given +context+ for uncommunicative names. 6 # 7 # @return [Array<SmellWarning>] 8 # 9 def sniff 10 variable_names.select do |name, _lines| 11 uncommunicative_variable_name?(name) 12 end.map do |name, lines| 13 smell_warning( 14 lines: lines, 15 message: "has the variable name '#{name}'", 16 parameters: { name: name.to_s }) 17 end 18 end
  13. Code Smells 1 class Foo 2 def yay 3 a

    = eval "1+1" 4 if a == 2 5 puts "yay" 6 end 7 end 8 end
  14. Code Smells $ reek foo.rb foo.rb -- 2 warnings: [1]:IrresponsibleModule:

    Foo has no descriptive comment [3]:UncommunicativeVariableName: Foo#yay has the variable name 'a'
  15. Code Smells 1 class Foo 2 def yay 3 a

    = eval "1+1" 4 if a == 2 5 puts "yay" 6 end 7 end 8 end
  16. Code Smells 1 class Foo 2 def yay 3 eval_result

    = eval "1+1" 4 if eval_result == 2 5 puts "yay" 6 end 7 end 8 end
  17. Code Smells 1 class Foo 2 def yay 3 eval_result

    = eval "1+1" 4 if eval_result == 2 5 puts "yay" 6 end 7 end 8 end
  18. Code Smells 1 --- 2 3 ### .reek.yml (Generic smell

    configuration) 4 5 detectors: 6 IrresponsibleModule: 7 enabled: false
  19. Churn - By itself it adds little value - Useful

    when combined with other metrics
  20. RubyCritic’s Cost module RubyCritic class AnalysedModule def cost smells.map(&:cost).inject(0.0, :+)

    + # From Reek (complexity / COMPLEXITY_FACTOR) # From Flog end end end
  21. 100% 0% 1 100_000 “No one understands these fi les

    but they work. So don’t change them.” Complexity Churn
  22. 100% 0% 1 100_000 “[…], if the code never changes,

    it's not costing us money.” Sandi Metz Complexity Churn
  23. 100% 0% 1 100_000 “These fi les are easy to

    understand but you need to change them often because of reasons…” Complexity Churn
  24. 100% 0% 1 100_000 Complexity Churn “Sometimes a class becomes

    so complex that refactoring seems too dif fi cult.” Michael Feathers
  25. 100% 0% 1 100_000 “Here be Dragons.” Complexity Churn “Here

    be Dragons.” “Here be Dragons.” “Here be Dragons.”
  26. Code Coverage # spec/spec_helper.rb if ENV["COVERAGE"] == "true" require 'simplecov'

    SimpleCov.start do add_group "Libraries", "lib" add_filter "/spec/" track_files "**/*.rb" end end
  27. Recap 1. The Complexity Dragon ( fl og) 2. The

    Smelly Dragon (reek) 3. The Shapeshifting Dragon (churn) 4. The Untested Dragon (simplecov)
  28. user.rb churn: 10 complexity: 10 smells: 10 Tech debt score:

    1,000 (10*10*10) account.rb churn: 10 complexity: 10 smells: 10 Tech debt score: 1,000 (10*10*10)
  29. user.rb churn: 10 complexity: 10 smells: 10 code_coverage: 0% Tech

    debt score: 100,000 account.rb churn: 10 complexity: 10 smells: 10 code_coverage: 90% Tech debt score: 10,000
  30. Skunk def skunk_score return cost.round(2) if coverage == PERFECT_COVERAGE (cost

    * penalty_factor).round(2) end def penalty_factor PERFECT_COVERAGE - coverage.to_i end
  31. Dragon Slaying 101: - Define what’s a dragon - Draw

    the map and place “here be dragons”
  32. Dragon Slaying 101: - Define what’s a dragon - Draw

    the map and place “here be dragons” - Pick your battles based on metrics
  33. $ reek foo.rb foo.rb -- 2 warnings: [1]:IrresponsibleModule: Foo has

    no descriptive comment [3]:UncommunicativeVariableName: Foo#yay has the variable name 'a' Slaying the smelly dragon
  34. $ reek foo.rb foo.rb -- 2 warnings: [1]:IrresponsibleModule: Foo has

    no descriptive comment [3]:UncommunicativeVariableName: Foo#yay has the variable name 'a' Slaying the smelly dragon
  35. 8. Attractor 9. MetricFu 10. CodeClimate 11. CodeScene 12. Codacy

    13. CodeCov 14. SonarQube 15. Flay 16. Sandi Meter 17. Coverband 18. Etc.. 1. Flog 2. Reek 3. Churn 4. SimpleCov 5. RubyCritic 6. Skunk 7. Rubocop
  36. 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/ fl ay 6.https://github.com/seattlerb/ fl

    og 7.http://www.sqa.net/iso9126.html 8.https://www.slideshare.net/mscottford/important-metrics-for-measuring-code-health 9.https://www.theatlantic.com/technology/archive/2013/12/no-old-maps-actually- say-here-be-dragons/282267/ 10.https://education.nationalgeographic.org/resource/here-be-dragons 11.https://resurrect3d.lib.rochester.edu/models/5bdc5e604270f527332282dd