Deletion Driven Development: Code to delete code! (North American edition)

956593b947dfddf6d89168fe09aa628a?s=47 Chris Arcand
November 10, 2016

Deletion Driven Development: Code to delete code! (North American edition)

Good news! Ruby is a successful and mature programming language with a wealth of libraries and legacy applications that have been contributed to for many years. The bad news: Those projects might contain a large amount of useless, unused code which adds needless complexity and confuses new developers. In this talk I’ll explain how to build a static analysis tool to help you clear out the cruft - because there’s no code that’s easier to maintain than no code at all!

956593b947dfddf6d89168fe09aa628a?s=128

Chris Arcand

November 10, 2016
Tweet

Transcript

  1. 3.
  2. 4.
  3. 11.
  4. 12.
  5. 13.
  6. 14.
  7. 15.
  8. 20.

    @chrisarcand chrisarcand.com • Over-engineering
 Previous developer adding “potentially helpful” methods

    • Poorly written code
 Super-specific, single edge case methods • Refactoring, deprecations
 Callers get refactored over time and methods eventually aren’t needed anymore • etc, etc Dead code? How?
  9. 23.

    @chrisarcand chrisarcand.com If you have a chunk of code you

    don't need any more, there's one big reason to delete it for real rather than leaving it in a disabled state: To reduce noise and uncertainty. Some of the worst enemies a developer has are noise or uncertainty in his code, because they prevent him from working with it effectively in the future. “
  10. 27.

    @chrisarcand chrisarcand.com Do you understand the following sequences of characters?

    How do you know? Modified examples and grammar from Robert B. Heckendorn, University of Idaho http://marvin.cs.uidaho.edu/Teaching/CS445/grammar.html
  11. 32.

    @chrisarcand chrisarcand.com ✨Context-free Grammar✨
 (CFG) A set of production rules

    that describe all possible strings in a given formal language. “What sentences are in the language and what are not?”
  12. 33.

    @chrisarcand chrisarcand.com ✨Backus-Naur Form✨
 (BNF) One of two main notation

    techniques for context- free grammars, often used to describe computer programming languages.
  13. 34.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES
  14. 35.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES Symbols
  15. 36.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES < non-terminals >
  16. 37.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES TERMINALS
  17. 38.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES Symbol on left is replaced with expression on the right ‘OR’ (choose between expressions) ‘definitional operator’
  18. 39.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES The boy owns a dog.
  19. 40.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES The boy owns a dog. <sentence>
  20. 41.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES The boy owns a dog. <subject> <predicate>
  21. 42.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES The boy owns a dog. <subject> <predicate>
  22. 43.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES The boy owns a dog. <subject> <predicate> <article> <noun>
  23. 44.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES The boy owns a dog. <subject> <predicate> <article> <noun>
  24. 45.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES The boy owns a dog. <subject> <predicate> <article> <noun> Terminal found
  25. 46.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES The boy owns a dog. <subject> <predicate> <article> <noun>
  26. 47.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES The boy owns a dog. <subject> <predicate> <article> <noun> Terminal found
  27. 48.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES The boy owns a dog. <subject> <predicate> <article> <noun> <verb> <direct-object> <article> <noun>
  28. 49.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES | BITES The boy owns a dog. <subject> <predicate> <article> <noun> <verb> <direct-object> <article> <noun>
  29. 50.

    @chrisarcand chrisarcand.com The boy owns a dog. <subject> <predicate> <article>

    <noun> <verb> <direct-object> <article> <noun> <sentence>
  30. 53.

    @chrisarcand chrisarcand.com <subject> <predicate> <article> <noun> <verb> <direct-object> <article> <noun>

    <sentence> A BOY BITES THE DOG Parsing was successful! …but do boys often bite dogs?
  31. 55.

    @chrisarcand chrisarcand.com <sentence> ::= <subject> <predicate> <subject> ::= <article> <noun>

    <predicate> ::= <verb> <direct-object> <direct-object> ::= <article> <noun> <article> ::= THE | A <noun> ::= BOY | DOG <verb> ::= OWNS | LOVES Loves boy the. <subject> ??? <article> ??? <noun> ???
  32. 56.

    @chrisarcand chrisarcand.com “The boy owns a dog.”
 “A boy bites

    the dog.” “Loves boy the.” In programming terms… OK OK, but perhaps
 semantically incorrect
 (A bug!) SYNTAX ERROR
  33. 57.

    @chrisarcand chrisarcand.com How does Ruby know the meaning of these

    characters? class Person def initialize(name) @name = name end def say_hello puts “Hi! My name is #{@name}!” end end chris = Person.new(“Chris”) chris.say_hello
  34. 58.

    @chrisarcand chrisarcand.com Figure 1-13, Ruby Under a Microscope by Pat

    Shaughnessy
 Used with permission How Ruby tokenizes and parses code
  35. 59.

    @chrisarcand chrisarcand.com Figure 1-13, Ruby Under a Microscope by Pat

    Shaughnessy
 Used with permission How Ruby tokenizes and parses code Ruby uses a LALR parser generator called Bison Look-Ahead, Left, Reversed Rightmost Derivation LALR(k): k refers to the amount of look-ahead
  36. 60.

    @chrisarcand chrisarcand.com Figure 1-13, Ruby Under a Microscope by Pat

    Shaughnessy
 Used with permission How Ruby tokenizes and parses code Ruby uses a LALR parser generator called Bison Look-Ahead, Left, Reversed Rightmost Derivation LALR(k): k refers to the amount of look-ahead
  37. 61.

    @chrisarcand chrisarcand.com Racc https://github.com/tenderlove/racc
 An LALR(1) parser generator How we

    will tokenize and parse code ruby_parser https://github.com/seattlerb/ruby_parser
 A Ruby parser, written in Ruby, using racc
  38. 63.

    @chrisarcand chrisarcand.com class Person def greet(name) puts "Hello #{name}!" end

    end person = Person.new person.greet("RubyConf") > parser = RubyParser.for_current_ruby > parser.parse(File.read(“example1.rb”)) 1 s(:block, 2 s(:class, :Person, nil, 3 s(:defn, :greet, s(:args, :name), 4 s(:call, nil, :puts, 
 5 s(:dstr, "Hello ", s(:evstr, s(:lvar, :name)), s(:str, "!"))))), 6 s(:lasgn, :person, s(:call, s(:const, :Person), :new)), 7 s(:call, s(:lvar, :person), :greet, s(:str, "RubyConf"))) example1.rb
  39. 64.

    @chrisarcand chrisarcand.com 1 s(:block, 2 s(:class, :Person, nil, 3 s(:defn,

    :greet, s(:args, :name), 4 s(:call, nil, :puts, 
 5 s(:dstr, "Hello ", s(:evstr, … 6 s(:lasgn, :person, s(:call, … 7 s(:call, s(:lvar, :person), :greet, …
  40. 65.
  41. 68.

    @chrisarcand chrisarcand.com def initialize @processors ={} public_methods.each do |name| case

    name when /^process_(.*)/ then @processors[$1.to_sym] = name.to_sym end end end end class MinimalSexpProcessor
  42. 73.

    @chrisarcand chrisarcand.com def process(exp) return nil if exp.nil? type =

    exp.first meth = @processors[type] || default_method return nil unless meth if meth == default_method && warn_on_default puts "WARNING: Using default method #{meth} for #{type}" end self.send(meth, exp) if meth end
  43. 74.

    @chrisarcand chrisarcand.com class MinimalSexpProcessor attr_accessor :default_method attr_accessor :warn_on_default def initialize

    @default_method = nil @warn_on_default = true @processors ={} public_methods.each do |name| case name when /^process_(.*)/ then @processors[$1.to_sym] = name.to_sym end end end end def process(exp) return nil if exp.nil? type = exp.first meth = @processors[type] || default_method return nil unless meth if meth == default_method && warn_on_default puts "WARNING: Using default method #{meth} for #{type}" end self.send(meth, exp) if meth end
  44. 77.

    @chrisarcand chrisarcand.com def process_defn(exp) method_name = exp[1] puts "PROCESSING A

    METHOD DEFINITION NODE: #{method_name}" process_until_empty(exp) s() end def process_not_defn(exp) node_type = exp[0] puts "Processing node: #{node_type}" process_until_empty(exp) s() end
  45. 80.

    @chrisarcand chrisarcand.com class SillyProcessor < MinimalSexpProcessor def process_defn(exp) method_name =

    exp[1] puts "PROCESSING A METHOD DEFINITION NODE: #{method_name}" process_until_empty(exp) s() end def process_not_defn(exp) node_type = exp[0] puts "Processing node: #{node_type}" process_until_empty(exp) s() end def process_until_empty(exp) until exp.empty? sexp = exp.shift process(sexp) if Sexp === sexp end end end def initialize super @default_method = :process_not_defn @warn_on_default = false end
  46. 81.

    @chrisarcand chrisarcand.com 1 require 'ruby_parser' 2 require_relative 'minimal_sexp_processor.rb' 3 require_relative

    'silly_processor.rb' 4 5 parser = RubyParser.for_current_ruby 6 file = File.read(ARGV[0]) 7 sexp = parser.parse(file) 8 9 processor = SillyProcessor.new 10 processor.process(sexp) silly_processor_demo.rb
  47. 82.

    @chrisarcand chrisarcand.com $ ruby minimal_parser_demo.rb example1.rb Processing node: block Processing

    node: class PROCESSING A METHOD DEFINITION NODE: greet Processing node: args Processing node: call Processing node: dstr Processing node: evstr Processing node: lvar Processing node: str Processing node: lasgn Processing node: call Processing node: const Processing node: call Processing node: lvar Processing node: str 1 class Person 2 def greet(name) 3 puts "Hello #{name}!" 4 end 5 end 6 7 person = Person.new 8 person.greet("RubyConf") example1.rb
  48. 84.

    @chrisarcand chrisarcand.com attr_reader :method_locations def initialize super @default_method = :process_until_empty

    @warn_on_default = false @class_stack = [] @method_stack = [] @method_locations = {} end class MethodTrackingProcessor < MinimalSexpProcessor
  49. 85.

    @chrisarcand chrisarcand.com def process_defn(exp) exp.shift # node type name =

    exp.shift in_method(name, exp.file, exp.line) do process_until_empty(exp) end s() end def process_class(exp) exp.shift # node type in_klass(exp.shift) do process_until_empty(exp) end s() end def process_until_empty(exp) until exp.empty? sexp = exp.shift process(sexp) if Sexp === sexp end end class MethodTrackingProcessor < MinimalSexpProcessor attr_reader :method_locations def initialize super @default_method = :process_until_empty @warn_on_default = false @class_stack = [] @method_stack = [] @method_locations = {} end
  50. 86.

    @chrisarcand chrisarcand.com def in_method(name, file, line) method_name = Regexp ===

    name ? name.inspect : name.to_s @method_stack.unshift(method_name) @method_locations[signature] = "#{file}:#{line}" yield ensure @method_stack.shift end def in_klass(name) @class_stack.unshift(name) with_new_method_stack do yield end ensure @class_stack.shift end def with_new_method_stack old_method_stack, @method_stack = @method_stack, [] yield ensure @method_stack = old_method_stack end class MethodTrackingProcessor < MinimalSexpProcessor attr_reader :method_locations def initialize super @default_method = :process_until_empty @warn_on_default = false @class_stack = [] @method_stack = [] @method_locations = {} end def process_defn(exp) exp.shift # node type name = exp.shift in_method(name, exp.file, exp.line) do process_until_empty(exp) end s() end def process_class(exp) exp.shift # node type in_klass(exp.shift) do process_until_empty(exp) end s() end def process_until_empty(exp) until exp.empty? sexp = exp.shift process(sexp) if Sexp === sexp end end
  51. 87.

    @chrisarcand chrisarcand.com def klass_name @class_stack.first end def method_name @method_stack.first end

    def signature "#{klass_name}##{method_name}" end class MethodTrackingProcessor < MinimalSexpProcessor attr_reader :method_locations def initialize super @default_method = :process_until_empty @warn_on_default = false @class_stack = [] @method_stack = [] @method_locations = {} end def process_defn(exp) exp.shift # node type name = exp.shift in_method(name, exp.file, exp.line) do process_until_empty(exp) end s() end def process_class(exp) exp.shift # node type in_klass(exp.shift) do process_until_empty(exp) end s() end def process_until_empty(exp) until exp.empty? sexp = exp.shift process(sexp) if Sexp === sexp end end def in_method(name, file, line) method_name = Regexp === name ? name.inspect : name.to_s @method_stack.unshift(method_name) @method_locations[signature] = "#{file}:#{line}" yield ensure @method_stack.shift end def in_klass(name) @class_stack.unshift(name) with_new_method_stack do yield end ensure @class_stack.shift end def with_new_method_stack old_method_stack, @method_stack = @method_stack, [] yield ensure @method_stack = old_method_stack end
  52. 88.

    @chrisarcand chrisarcand.com class MethodTrackingProcessor < MinimalSexpProcessor attr_reader :method_locations def initialize

    super @default_method = :process_until_empty @warn_on_default = false @class_stack = [] @method_stack = [] @method_locations = {} end def process_defn(exp) exp.shift # node type name = exp.shift in_method(name, exp.file, exp.line) do process_until_empty(exp) end s() end def process_class(exp) exp.shift # node type in_klass(exp.shift) do process_until_empty(exp) end s() end def process_until_empty(exp) until exp.empty? sexp = exp.shift process(sexp) if Sexp === sexp end end def in_method(name, file, line) method_name = Regexp === name ? name.inspect : name.to_s @method_stack.unshift(method_name) @method_locations[signature] = "#{file}:#{line}" yield ensure @method_stack.shift end def in_klass(name) @class_stack.unshift(name) with_new_method_stack do yield end ensure @class_stack.shift end def with_new_method_stack old_method_stack, @method_stack = @method_stack, [] yield ensure @method_stack = old_method_stack end def klass_name @class_stack.first end def method_name @method_stack.first end def signature "#{klass_name}##{method_name}" end end
  53. 89.

    @chrisarcand chrisarcand.com 1 class Person 2 def greet(name) 3 puts

    "Hello #{name}!" 4 end 5 6 def say_goodbye(name) 7 puts "Goodbye, #{name}!" 8 end 9 end 10 11 class Dog 12 def bark! 13 puts "Woof!" 14 end 15 end 16 17 person = Person.new 18 person.greet("RubyConf") example2.rb
  54. 90.

    @chrisarcand chrisarcand.com 5 path = ARGV[0] 6 file = File.read(path)

    7 sexp = RubyParser.for_current_ruby.process(file, path) 8 9 processor = MethodTrackingProcessor.new 10 processor.process(sexp) 11 pp processor.method_locations method_tracking_demo.rb $ ruby minimal_parser_demo.rb example2.rb { "Person#greet" => "example2.rb:2", "Person#say_goodbye" => "example2.rb:6", “Dog#bark!" => "example2.rb:12" }
  55. 91.

    @chrisarcand chrisarcand.com ruby_parser racc Ruby code (.rb) Ruby23Parser MethodTrackingProcessor MinimalSexpProcessor

    Parsing Generic processing File.read() s-expression (parse tree) Call processing and reporting ? ? ?
  56. 93.

    @chrisarcand chrisarcand.com ruby_parser racc Ruby code (.rb) Ruby23Parser Parsing Generic

    processing File.read() s-expression (parse tree) Call processing and reporting Our tool DeadMethodFinder MethodTrackingProcessor MinimalSexpProcessor
  57. 95.

    @chrisarcand chrisarcand.com attr_reader :known attr_reader :called def initialize super @known

    = Hash.new { |h,k| h[k] = Set.new } @called = Set.new end class DeadMethodFinder < MethodTrackingProcessor
  58. 96.

    @chrisarcand chrisarcand.com def process_defn(exp) super do known[plain_method_name] << klass_name process_until_empty(exp)

    end end def process_call(exp) method_name = exp[2] called << method_name process_until_empty(exp) exp end attr_reader :known attr_reader :called def initialize super @known = Hash.new { |h,k| h[k] = Set.new } @called = Set.new end class DeadMethodFinder < MethodTrackingProcessor
  59. 97.

    @chrisarcand chrisarcand.com def uncalled not_called = known.keys - called.to_a by_class

    = Hash.new { |h,k| h[k] = [] } not_called.each do |meth| known[meth].each do |klass| by_class[klass] << meth end end by_class end # #method_name -> :method_name def plain_method_name method_name.to_s.sub(/^::|#/, "").to_sym end attr_reader :known attr_reader :called def initialize super @known = Hash.new { |h,k| h[k] = Set.new } @called = Set.new end def process_defn(exp) super do known[plain_method_name] << klass_name process_until_empty(exp) end end def process_call(exp) method_name = exp[2] called << method_name process_until_empty(exp) exp end class DeadMethodFinder < MethodTrackingProcessor
  60. 98.

    @chrisarcand chrisarcand.com attr_reader :known attr_reader :called def initialize super @known

    = Hash.new { |h,k| h[k] = Set.new } @called = Set.new end def process_defn(exp) super do known[plain_method_name] << klass_name process_until_empty(exp) end end def process_call(exp) method_name = exp[2] called << method_name process_until_empty(exp) exp end def uncalled not_called = known.keys - called.to_a by_class = Hash.new { |h,k| h[k] = [] } not_called.each do |meth| known[meth].each do |klass| by_class[klass] << meth end end by_class end # #method_name -> :method_name def plain_method_name method_name.to_s.sub(/^::|#/, "").to_sym end end class DeadMethodFinder < MethodTrackingProcessor
  61. 99.

    @chrisarcand chrisarcand.com 1 class Person 2 def greet(name) 3 speak

    "Hello #{name}!" 4 end 5 6 # Never used 7 def say_goodbye(name) 8 speak "Goodbye, #{name}!" 9 end 10 11 def speak(text) 12 puts text 13 end 14 15 def pet_dog(dog) 16 dog.send(:pet) 17 end 18 end 19 20 class Dog 21 attr_accessor :fed # Never used 22 23 def bark! 24 puts "Bark!" 25 end 26 27 def pet 28 puts "Ahhh..." 29 end 30 end 31 32 33 chris = Person.new 34 reuben = Dog.new 35 36 chris.greet("RubyConf") 37 reuben.bark! 38 chris.pet_dog(reuben) example3.rb => Hello RubyConf!
 Bark! 
 Ahhh…
  62. 100.

    @chrisarcand chrisarcand.com 1 class Person 2 def greet(name) 3 speak

    "Hello #{name}!" 4 end 5 6 # Never used 7 def say_goodbye(name) 8 speak "Goodbye, #{name}!" 9 end 10 11 def speak(text) 12 puts text 13 end 14 15 def pet_dog(dog) 16 dog.send(:pet) 17 end 18 end 19 20 class Dog 21 attr_accessor :fed # Never used 22 23 def bark! 24 puts "Bark!" 25 end 26 27 def pet 28 puts "Ahhh..." 29 end 30 end 31 32 33 chris = Person.new 34 reuben = Dog.new 35 36 chris.greet("RubyConf") 37 reuben.bark! 38 chris.pet_dog(reuben) example3.rb > processor = DeadMethodFinder.new > processor.process(sexp) > puts processor.uncalled => {"Person"=>[:say_goodbye], "Dog"=>[:pet]}
  63. 102.

    @chrisarcand chrisarcand.com def process_call(exp) method_name = exp[2] case method_name when

    :send, :public_send, :__send__ msg_arg = exp[3] if Sexp === msg_arg && [:lit, :str].include?(msg_arg.sexp_type) called << msg_arg.last.to_sym end end called << method_name process_until_empty(exp) exp end s(:call, s(:lvar, :dog), :send, s(:lit, :pet)) 0 1 2 3
  64. 103.

    @chrisarcand chrisarcand.com 1 class Person 2 def greet(name) 3 speak

    "Hello #{name}!" 4 end 5 6 # Never used 7 def say_goodbye(name) 8 speak "Goodbye, #{name}!" 9 end 10 11 def speak(text) 12 puts text 13 end 14 15 def pet_dog(dog) 16 dog.send(:pet) 17 end 18 end 19 20 class Dog 21 attr_accessor :fed # Never used 22 23 def bark! 24 puts "Bark!" 25 end 26 27 def pet 28 puts "Ahhh..." 29 end 30 end 31 32 33 chris = Person.new 34 reuben = Dog.new 35 36 chris.greet("RubyConf") 37 reuben.bark! 38 chris.pet_dog(reuben) example3.rb > processor = DeadMethodFinder.new > processor.process(sexp) > puts processor.uncalled => {"Person"=>[:say_goodbye]}
  65. 105.

    @chrisarcand chrisarcand.com s(:call, nil, :attr_accessor, s(:lit, :fed)) def process_call(exp) method_name

    = exp[2] case method_name when :attr_accessor _, _, _, *args = exp file, line = exp.file, exp.line args.each do |(_, name)| record_known_method(name, file, line) record_known_method("#{name}=".to_sym, file, line) end when :send, :public_send, :__send__ _, _, _, msg_arg, * = exp if Sexp === msg_arg && [:lit, :str].include?(msg_arg.sexp_type) called << msg_arg.last.to_sym end end called << method_name process_until_empty(exp) exp end def record_known_method(name, file, line) signature = "#{klass_name}##{name}" method_locations[signature] = "#{file}:#{line}" known[name] << klass_name end 0 1 2 3
  66. 106.

    @chrisarcand chrisarcand.com def report puts "These methods MIGHT not be

    called:" uncalled.each do |klass, methods| not_called_methods = methods.map do |method| location = method_locations["#{klass}##{method}"] " %-35s %s" % [method, location] end not_called_methods.compact! next if not_called_methods.empty? puts "\n#{klass}" puts not_called_methods.join "\n" end end
  67. 108.

    @chrisarcand chrisarcand.com 1 class Person 2 def greet(name) 3 speak

    "Hello #{name}!" 4 end 5 6 # Never used 7 def say_goodbye(name) 8 speak "Goodbye, #{name}!" 9 end 10 11 def speak(text) 12 puts text 13 end 14 15 def pet_dog(dog) 16 dog.send(:pet) 17 end 18 end 19 20 class Dog 21 attr_accessor :fed # Never used 22 23 def bark! 24 puts "Bark!" 25 end 26 27 def pet 28 puts "Ahhh..." 29 end 30 end 31 32 33 chris = Person.new 34 reuben = Dog.new 35 36 chris.greet("RubyConf") 37 reuben.bark! 38 chris.pet_dog(reuben) example2.rb These methods MIGHT not be called:
 
 Person say_goodbye example2.rb:7 Dog fed example2.rb:21 fed= example2.rb:21
  68. 109.

    @chrisarcand chrisarcand.com 1 class Person 2 def greet(name) 3 speak

    "Hello #{name}!" 4 end 5 6 7 8 9 10 11 def speak(text) 12 puts text 13 end 14 15 def pet_dog(dog) 16 dog.send(:pet) 17 end 18 end 19 20 class Dog 21 22 23 def bark! 24 puts "Bark!" 25 end 26 27 def pet 28 puts "Ahhh..." 29 end 30 end 31 32 33 chris = Person.new 34 reuben = Dog.new 35 36 chris.greet("RubyConf") 37 reuben.bark! 38 chris.pet_dog(reuben) # Never used def say_goodbye(name) speak "Goodbye, #{name}!" end attr_accessor :fed # Never used - - - - - example2.rb
  69. 114.

    @chrisarcand chrisarcand.com 1 class Person 2 def greet(name) 3 speak

    "Hello #{name}!" 4 end 5 6 # Never used 7 def say_goodbye(name) 8 speak "Goodbye, #{name}!" 9 end 10 11 def speak(text) 12 puts text 13 end 14 15 def pet_dog(dog) 16 dog.send(:pet) 17 end 18 end 19 20 class Dog 21 attr_accessor :fed # Never used 22 23 def bark! 24 puts "Bark!" 25 end 26 27 def pet 28 puts "Ahhh..." 29 end 30 end 31 32 33 chris = Person.new 34 reuben = Dog.new 35 36 chris.greet("RubyConf") 37 reuben.bark! 38 chris.pet_dog(reuben)
 39 example4.rb Person say_goodbye example2.rb:7 Dog fed example2.rb:21 fed= example2.rb:21 reuben.fed = true
  70. 116.

    @chrisarcand chrisarcand.com s(:attrasgn, s(:lvar, :reuben), :fed=, s(:true)) def process_attrasgn(exp) method_name

    = exp[2] method_name = method_name.last if Sexp === method_name called << method_name process_until_empty exp exp end reuben.fed = true
  71. 117.

    @chrisarcand chrisarcand.com 1 class Person 2 def greet(name) 3 speak

    "Hello #{name}!" 4 end 5 6 # Never used 7 def say_goodbye(name) 8 speak "Goodbye, #{name}!" 9 end 10 11 def speak(text) 12 puts text 13 end 14 15 def pet_dog(dog) 16 dog.send(:pet) 17 end 18 end 19 20 class Dog 21 attr_accessor :fed # Never used 22 23 def bark! 24 puts "Bark!" 25 end 26 27 def pet 28 puts "Ahhh..." 29 end 30 end 31 32 33 chris = Person.new 34 reuben = Dog.new 35 36 chris.greet("RubyConf") 37 reuben.bark! 38 chris.pet_dog(reuben)
 39 reuben.fed = true example4.rb Person say_goodbye example2.rb:7 Dog fed example2.rb:21
  72. 119.

    @chrisarcand chrisarcand.com :after_commit :before_create :after_create :before_destroy :before_filter :before_action :after_validation :before_update

    :around_save :validates :validates_length_of :validates_format_of :validates_cuteness_of :validates_confirmation_of :validate
  73. 120.

    @chrisarcand chrisarcand.com :after_commit :before_create :after_create :before_destroy :before_filter :before_action :after_validation :before_update

    :around_save :validates :validates_length_of :validates_format_of :validates_cuteness_of :validates_confirmation_of :validate
  74. 123.

    @chrisarcand chrisarcand.com class Disk < ActiveRecord::Base has_many :partitions virtual_column :allocated_space,

    type: :integer, uses: :partitions virtual_column :used_percent_of_provisioned, type: :float virtual_has_many :storage_systems, class_name: "CimComputerSystem" def allocated_space # ... end def used_percent_of_provisioned # ... end end
  75. 124.

    @chrisarcand chrisarcand.com RAILS_DSL_METHODS = [ :after_action, :around_action, :before_action, ...
 ...

    ] def process_call(exp) # ... when *RAILS_DSL_METHODS + AR_VIRTUAL_DSL_METHODS _, _, _, (_, new_name), possible_hash = exp called << new_name if Sexp === possible_hash && possible_hash.sexp_type == :hash possible_hash.sexp_body.each_slice(2) do |key, val| next unless Sexp === val called << val.last if val.first == :lit called << val.last.to_sym if val.first == :str end end # ... end
  76. 125.

    @chrisarcand chrisarcand.com As with most things, with the right tools,

    the job isn’t very difficult. Customization is easy.
  77. 129.

    @chrisarcand chrisarcand.com ruby_parser racc sexp_processor Ruby code (.rb) Ruby23Parser MethodBasedSexpProcessor

    SexpProcessor Parsing Generic processing File.read() s-expression (parse tree)
  78. 130.

    @chrisarcand chrisarcand.com ruby_parser racc sexp_processor Ruby code (.rb) Ruby23Parser MethodBasedSexpProcessor

    SexpProcessor Parsing Generic processing File.read() s-expression (parse tree) Call processing and reporting debride Debride
  79. 131.

    @chrisarcand chrisarcand.com $ debride [options] files_or_dirs -e, --exclude FILE1,FILE2,ETC -w,

    --whitelist PATTERN -f, --focus PATH -r, --rails -v, --verbose
  80. 132.

    @chrisarcand chrisarcand.com - @called = Set.new + @called = Hash.new

    { |h,k| h[k] = 0 } - called << method_name + called[method_name] += 1 - not_called = known.keys - called.to_a + called_once = called.select { |_, v| v == 1 } API Quality Control - Single call methods
  81. 134.

    @chrisarcand chrisarcand.com But wait, there’s more! Unused by Josh Clayton


    https://github.com/joshuaclayton/unused
 Written in Haskell, this project utilizes ctags to statically find unused code OldeCodeFinder by Tom Copeland
 https://github.com/tcopeland/olde_code_finder
 A Ruby gem checking code content by date and authorship Here’s a few…
  82. 135.

    @chrisarcand chrisarcand.com No code is faster than no code. No

    code has fewer bugs than no code. No code is easier to understand than no code. No code is more maintainable than no code.