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

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

Chris Arcand
September 10, 2016

Deletion Driven Development: Code to delete code! (RubyKaigi 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!

Chris Arcand

September 10, 2016
Tweet

More Decks by Chris Arcand

Other Decks in Programming

Transcript

  1. @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?
  2. @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. “
  3. @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
  4. @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?”
  5. @chrisarcand chrisarcand.com ✨Backus-Naur Form✨
 (BNF) One of two main notation

    techniques for context- free grammars, often used to describe computer programming languages.
  6. @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
  7. @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
  8. @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 >
  9. @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
  10. @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’
  11. @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.
  12. @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>
  13. @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>
  14. @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>
  15. @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>
  16. @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>
  17. @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
  18. @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>
  19. @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
  20. @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>
  21. @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>
  22. @chrisarcand chrisarcand.com The boy owns a dog. <subject> <predicate> <article>

    <noun> <verb> <direct-object> <article> <noun> <sentence>
  23. @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> ???
  24. @chrisarcand chrisarcand.com “The boy owns a dog.”
 “The dog loves

    the boy.” “Loves boy the.” In programming terms… OK OK SYNTAX ERROR
  25. @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
  26. @chrisarcand chrisarcand.com Figure 1-13, Ruby Under a Microscope by Pat

    Shaughnessy
 Used with permission How Ruby tokenizes and parses code
  27. @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
  28. @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
  29. @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
  30. @chrisarcand chrisarcand.com class Person def greet(name) puts "Hello #{name}!" end

    end person = Person.new person.greet("RubyKaigi") > 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, "RubyKaigi"))) example1.rb
  31. @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, …
  32. @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
  33. @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
  34. @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
  35. @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
  36. @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
  37. @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
  38. @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("RubyKaigi") example1.rb
  39. @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
  40. @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
  41. @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
  42. @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
  43. @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
  44. @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("RubyKaigi") example2.rb
  45. @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" }
  46. @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 ? ? ?
  47. @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
  48. @chrisarcand chrisarcand.com class DeadMethodFinder < MethodTrackingProcessor attr_reader :known attr_reader :called

    def initialize super @known = Hash.new { |h,k| h[k] = Set.new } @called = Set.new end
  49. @chrisarcand chrisarcand.com class DeadMethodFinder < MethodTrackingProcessor 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
  50. @chrisarcand chrisarcand.com class DeadMethodFinder < MethodTrackingProcessor 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
  51. @chrisarcand chrisarcand.com class DeadMethodFinder < MethodTrackingProcessor 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
  52. @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("RubyKaigi") 37 reuben.bark! 38 chris.pet_dog(reuben) example3.rb => Hello RubyKaigi!
 Bark! 
 Ahhh…
  53. @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("RubyKaigi") 37 reuben.bark! 38 chris.pet_dog(reuben) example3.rb > processor = DeadMethodFinder.new > processor.process(sexp) > puts processor.uncalled => {"Person"=>[:say_goodbye], "Dog"=>[:pet]}
  54. @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
  55. @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("RubyKaigi") 37 reuben.bark! 38 chris.pet_dog(reuben) example3.rb > processor = DeadMethodFinder.new > processor.process(sexp) > puts processor.uncalled => {"Person"=>[:say_goodbye]}
  56. @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
  57. @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
  58. @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("RubyKaigi") 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
  59. @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("RubyKaigi") 37 reuben.bark! 38 chris.pet_dog(reuben) # Never used def say_goodbye(name) speak "Goodbye, #{name}!" end attr_accessor :fed # Never used - - - - -
  60. @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("RubyKaigi") 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
  61. @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
  62. @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("RubyKaigi") 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
  63. @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
  64. @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
  65. @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
  66. @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
  67. @chrisarcand chrisarcand.com As with most things, with the right tools,

    the job isn’t very difficult. Customization is easy.
  68. @chrisarcand chrisarcand.com ruby_parser racc sexp_processor Ruby code (.rb) Ruby23Parser MethodBasedSexpProcessor

    SexpProcessor Parsing Generic processing File.read() s-expression (parse tree)
  69. @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
  70. @chrisarcand chrisarcand.com $ debride [options] files_or_dirs -e, --exclude FILE1,FILE2,ETC -w,

    --whitelist PATTERN -f, --focus PATH -r, --rails -v, --verbose
  71. @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
  72. @chrisarcand chrisarcand.com Future considerations: • Actually push the work I’ve

    done upstream ✴ Bug fixes ✴ Cleanup ✴ Potential features
  73. @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 Thanks, Juanito! Here’s a few…
  74. @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.