Slide 1

Slide 1 text

@chrisarcand Deletion Driven Development: Code to delete code!

Slide 2

Slide 2 text

Chris Arcand
 chrisarcand chrisarcand www.chrisarcand.com

Slide 3

Slide 3 text

MINNESOTA

Slide 4

Slide 4 text

MINNESOTA

Slide 5

Slide 5 text

MINNESOTA Minneapolis St. Paul

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

@chrisarcand chrisarcand.com I PROGRAMMING

Slide 12

Slide 12 text

@chrisarcand chrisarcand.com I WRITING
 CODE

Slide 13

Slide 13 text

@chrisarcand chrisarcand.com However…

Slide 14

Slide 14 text

@chrisarcand chrisarcand.com I DELETING
 CODE

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

@chrisarcand chrisarcand.com “Who cares?”

Slide 17

Slide 17 text

@chrisarcand chrisarcand.com “Deleting Code” (2002) Ned Batchelder http://nedbatchelder.com/text/deleting-code.html

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

@chrisarcand chrisarcand.com What if we could programmatically find unused code to delete?

Slide 20

Slide 20 text

@chrisarcand chrisarcand.com Ruby code (.rb) ? ? ?

Slide 21

Slide 21 text

@chrisarcand chrisarcand.com Part I: Parsing the code

Slide 22

Slide 22 text

@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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

@chrisarcand chrisarcand.com “The dog loves the boy.”

Slide 25

Slide 25 text

@chrisarcand chrisarcand.com “Loves boy the.”

Slide 26

Slide 26 text

@chrisarcand chrisarcand.com How could you programmatically determine which of those are correct… …and which are not?

Slide 27

Slide 27 text

@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?”

Slide 28

Slide 28 text

@chrisarcand chrisarcand.com ✨Backus-Naur Form✨
 (BNF) One of two main notation techniques for context- free grammars, often used to describe computer programming languages.

Slide 29

Slide 29 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES

Slide 30

Slide 30 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES Symbols

Slide 31

Slide 31 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES < non-terminals >

Slide 32

Slide 32 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES TERMINALS

Slide 33

Slide 33 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES Symbol on left is replaced with expression on the right ‘OR’ (choose between expressions) ‘definitional operator’

Slide 34

Slide 34 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES The boy owns a dog.

Slide 35

Slide 35 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES The boy owns a dog.

Slide 36

Slide 36 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES The boy owns a dog.

Slide 37

Slide 37 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES The boy owns a dog.

Slide 38

Slide 38 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES The boy owns a dog.

Slide 39

Slide 39 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES The boy owns a dog.

Slide 40

Slide 40 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES The boy owns a dog. Terminal found

Slide 41

Slide 41 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES The boy owns a dog.

Slide 42

Slide 42 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES The boy owns a dog. Terminal found

Slide 43

Slide 43 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES The boy owns a dog.

Slide 44

Slide 44 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES | BITES The boy owns a dog.

Slide 45

Slide 45 text

@chrisarcand chrisarcand.com The boy owns a dog.

Slide 46

Slide 46 text

@chrisarcand chrisarcand.com THE BOY OWNS A DOG Parsing was successful!

Slide 47

Slide 47 text

@chrisarcand chrisarcand.com “The dog loves the boy.”

Slide 48

Slide 48 text

@chrisarcand chrisarcand.com THE DOG LOVES THE BOY Parsing was successful!

Slide 49

Slide 49 text

@chrisarcand chrisarcand.com Loves boy the. “ ”

Slide 50

Slide 50 text

@chrisarcand chrisarcand.com ::= ::= ::= ::= ::= THE | A ::= BOY | DOG ::= OWNS | LOVES Loves boy the. ??? ??? ???

Slide 51

Slide 51 text

@chrisarcand chrisarcand.com “The boy owns a dog.”
 “The dog loves the boy.” “Loves boy the.” In programming terms… OK OK SYNTAX ERROR

Slide 52

Slide 52 text

@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

Slide 53

Slide 53 text

@chrisarcand chrisarcand.com Figure 1-13, Ruby Under a Microscope by Pat Shaughnessy
 Used with permission How Ruby tokenizes and parses code

Slide 54

Slide 54 text

@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

Slide 55

Slide 55 text

@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

Slide 56

Slide 56 text

@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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

@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

Slide 59

Slide 59 text

@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, …

Slide 60

Slide 60 text

@chrisarcand chrisarcand.com ruby_parser racc Ruby code (.rb) Ruby23Parser Parsing Generic processing File.read() s-expression (parse tree) ? ? ?

Slide 61

Slide 61 text

@chrisarcand chrisarcand.com Part II: Processing the s-expression

Slide 62

Slide 62 text

@chrisarcand chrisarcand.com class MinimalSexpProcessor

Slide 63

Slide 63 text

@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

Slide 64

Slide 64 text

@chrisarcand chrisarcand.com process_defn

Slide 65

Slide 65 text

@chrisarcand chrisarcand.com process_defn

Slide 66

Slide 66 text

@chrisarcand chrisarcand.com process_defn

Slide 67

Slide 67 text

@chrisarcand chrisarcand.com defn =>:process_defn : @processors = { } Node type Corresponding processor

Slide 68

Slide 68 text

@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

Slide 69

Slide 69 text

@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

Slide 70

Slide 70 text

@chrisarcand chrisarcand.com class MinimalSexpProcessor

Slide 71

Slide 71 text

@chrisarcand chrisarcand.com class SillyProcessor < MinimalSexpProcessor

Slide 72

Slide 72 text

@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

Slide 73

Slide 73 text

@chrisarcand chrisarcand.com def process_until_empty(exp) until exp.empty? sexp = exp.shift process(sexp) if Sexp === sexp end end

Slide 74

Slide 74 text

@chrisarcand chrisarcand.com def initialize super @default_method = :process_not_defn @warn_on_default = false end

Slide 75

Slide 75 text

@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

Slide 76

Slide 76 text

@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

Slide 77

Slide 77 text

@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

Slide 78

Slide 78 text

@chrisarcand chrisarcand.com class MethodTrackingProcessor < MinimalSexpProcessor

Slide 79

Slide 79 text

@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

Slide 80

Slide 80 text

@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

Slide 81

Slide 81 text

@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

Slide 82

Slide 82 text

@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

Slide 83

Slide 83 text

@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

Slide 84

Slide 84 text

@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

Slide 85

Slide 85 text

@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" }

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

@chrisarcand chrisarcand.com Part III: Building the dead method finder
 (or, drawing the rest of the damn owl)

Slide 88

Slide 88 text

@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

Slide 89

Slide 89 text

@chrisarcand chrisarcand.com class DeadMethodFinder < MethodTrackingProcessor

Slide 90

Slide 90 text

@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

Slide 91

Slide 91 text

@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

Slide 92

Slide 92 text

@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

Slide 93

Slide 93 text

@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

Slide 94

Slide 94 text

@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…

Slide 95

Slide 95 text

@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]}

Slide 96

Slide 96 text

@chrisarcand chrisarcand.com s(:call, s(:lvar, :dog), :send, s(:lit, :pet)) dog.send(:pet)

Slide 97

Slide 97 text

@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

Slide 98

Slide 98 text

@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]}

Slide 99

Slide 99 text

@chrisarcand chrisarcand.com s(:call, nil, :attr_accessor, s(:lit, :fed)) attr_accessor :fed

Slide 100

Slide 100 text

@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

Slide 101

Slide 101 text

@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

Slide 102

Slide 102 text

@chrisarcand chrisarcand.com > processor = DeadMethodFinder.new > processor.process(sexp)
 > processor.report

Slide 103

Slide 103 text

@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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

@chrisarcand chrisarcand.com ✌ Done?

Slide 106

Slide 106 text

@chrisarcand chrisarcand.com Done Ruby is complex (to parse)

Slide 107

Slide 107 text

@chrisarcand chrisarcand.com Done? Ruby is complex (to parse) …but adding edge cases is easy.

Slide 108

Slide 108 text

@chrisarcand chrisarcand.com reuben.fed = true

Slide 109

Slide 109 text

@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

Slide 110

Slide 110 text

Brought to you by: Julian Cheal

Slide 111

Slide 111 text

@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

Slide 112

Slide 112 text

@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

Slide 113

Slide 113 text

@chrisarcand chrisarcand.com What about Rails DSL?

Slide 114

Slide 114 text

@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

Slide 115

Slide 115 text

@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

Slide 116

Slide 116 text

@chrisarcand chrisarcand.com What about my own DSL?

Slide 117

Slide 117 text

@chrisarcand chrisarcand.com

Slide 118

Slide 118 text

@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

Slide 119

Slide 119 text

@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

Slide 120

Slide 120 text

@chrisarcand chrisarcand.com As with most things, with the right tools, the job isn’t very difficult. Customization is easy.

Slide 121

Slide 121 text

@chrisarcand chrisarcand.com Other things that are easy: Executing this code on your project, right now.

Slide 122

Slide 122 text

@chrisarcand chrisarcand.com debride https://github.com/seattlerb/debride debride (v): To remove dead, contaminated, or adherent tissue and/or foreign material.

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

@chrisarcand chrisarcand.com ruby_parser racc sexp_processor Ruby code (.rb) Ruby23Parser MethodBasedSexpProcessor SexpProcessor Parsing Generic processing File.read() s-expression (parse tree)

Slide 125

Slide 125 text

@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

Slide 126

Slide 126 text

@chrisarcand chrisarcand.com $ debride [options] files_or_dirs -e, --exclude FILE1,FILE2,ETC -w, --whitelist PATTERN -f, --focus PATH -r, --rails -v, --verbose

Slide 127

Slide 127 text

@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

Slide 128

Slide 128 text

@chrisarcand chrisarcand.com Future considerations: • Actually push the work I’ve done upstream ✴ Bug fixes ✴ Cleanup ✴ Potential features

Slide 129

Slide 129 text

@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…

Slide 130

Slide 130 text

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

Slide 131

Slide 131 text

Thank you!
 Questions? chrisarcand chrisarcand www.chrisarcand.com