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

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

“The Land of 10,000 Lakes”

Slide 6

Slide 6 text

“The North Star State”

Slide 7

Slide 7 text

“That super cold place.”

Slide 8

Slide 8 text

“The Great White South, eh?”

Slide 9

Slide 9 text

“That one place where those JRuby guys live, right?”

Slide 10

Slide 10 text

MINNESOTA Minneapolis St. Paul

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

@chrisarcand chrisarcand.com I PROGRAMMING

Slide 17

Slide 17 text

@chrisarcand chrisarcand.com I WRITING
 CODE

Slide 18

Slide 18 text

@chrisarcand chrisarcand.com However…

Slide 19

Slide 19 text

@chrisarcand chrisarcand.com I DELETING
 CODE

Slide 20

Slide 20 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 21

Slide 21 text

@chrisarcand chrisarcand.com “Who cares?”

Slide 22

Slide 22 text

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

Slide 23

Slide 23 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 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

@chrisarcand chrisarcand.com Part I: Parsing the code

Slide 27

Slide 27 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 28

Slide 28 text

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

Slide 29

Slide 29 text

@chrisarcand chrisarcand.com “A boy bites the dog.”

Slide 30

Slide 30 text

@chrisarcand chrisarcand.com “Loves boy the.”

Slide 31

Slide 31 text

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

Slide 32

Slide 32 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 33

Slide 33 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 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 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 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.

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.

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 | A ::= BOY | DOG ::= OWNS | LOVES | BITES The boy owns a dog. Terminal found

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

@chrisarcand chrisarcand.com The boy owns a dog.

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

@chrisarcand chrisarcand.com “A boy bites the dog.”

Slide 53

Slide 53 text

@chrisarcand chrisarcand.com A BOY BITES THE DOG Parsing was successful! …but do boys often bite dogs?

Slide 54

Slide 54 text

@chrisarcand chrisarcand.com Loves boy the. “ ”

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

@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

Slide 57

Slide 57 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 58

Slide 58 text

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

Slide 59

Slide 59 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 60

Slide 60 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 61

Slide 61 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 62

Slide 62 text

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

Slide 63

Slide 63 text

@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

Slide 64

Slide 64 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 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

@chrisarcand chrisarcand.com class MinimalSexpProcessor

Slide 68

Slide 68 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 69

Slide 69 text

@chrisarcand chrisarcand.com process_defn

Slide 70

Slide 70 text

@chrisarcand chrisarcand.com process_defn

Slide 71

Slide 71 text

@chrisarcand chrisarcand.com process_defn

Slide 72

Slide 72 text

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

Slide 73

Slide 73 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 74

Slide 74 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 75

Slide 75 text

@chrisarcand chrisarcand.com class MinimalSexpProcessor

Slide 76

Slide 76 text

@chrisarcand chrisarcand.com class SillyProcessor < MinimalSexpProcessor

Slide 77

Slide 77 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 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 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 81

Slide 81 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 82

Slide 82 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("RubyConf") example1.rb

Slide 83

Slide 83 text

@chrisarcand chrisarcand.com class MethodTrackingProcessor < MinimalSexpProcessor

Slide 84

Slide 84 text

@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

Slide 85

Slide 85 text

@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

Slide 86

Slide 86 text

@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

Slide 87

Slide 87 text

@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

Slide 88

Slide 88 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 89

Slide 89 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("RubyConf") example2.rb

Slide 90

Slide 90 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 91

Slide 91 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 92

Slide 92 text

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

Slide 93

Slide 93 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 94

Slide 94 text

@chrisarcand chrisarcand.com class DeadMethodFinder < MethodTrackingProcessor

Slide 95

Slide 95 text

@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

Slide 96

Slide 96 text

@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

Slide 97

Slide 97 text

@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

Slide 98

Slide 98 text

@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

Slide 99

Slide 99 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("RubyConf") 37 reuben.bark! 38 chris.pet_dog(reuben) example3.rb => Hello RubyConf!
 Bark! 
 Ahhh…

Slide 100

Slide 100 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("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]}

Slide 101

Slide 101 text

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

Slide 102

Slide 102 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 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("RubyConf") 37 reuben.bark! 38 chris.pet_dog(reuben) example3.rb > processor = DeadMethodFinder.new > processor.process(sexp) > puts processor.uncalled => {"Person"=>[:say_goodbye]}

Slide 104

Slide 104 text

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

Slide 105

Slide 105 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 106

Slide 106 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 107

Slide 107 text

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

Slide 108

Slide 108 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("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

Slide 109

Slide 109 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("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

Slide 110

Slide 110 text

@chrisarcand chrisarcand.com ✌ Done?

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

@chrisarcand chrisarcand.com reuben.fed = true

Slide 114

Slide 114 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("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

Slide 115

Slide 115 text

Brought to you by: Julian Cheal

Slide 116

Slide 116 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 117

Slide 117 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("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

Slide 118

Slide 118 text

@chrisarcand chrisarcand.com What about Rails DSL?

Slide 119

Slide 119 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 120

Slide 120 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 121

Slide 121 text

@chrisarcand chrisarcand.com What about my own DSL?

Slide 122

Slide 122 text

@chrisarcand chrisarcand.com

Slide 123

Slide 123 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 124

Slide 124 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 125

Slide 125 text

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

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

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

Slide 129

Slide 129 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 130

Slide 130 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 131

Slide 131 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 132

Slide 132 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 133

Slide 133 text

@chrisarcand chrisarcand.com Future considerations: • Pushing more work upstream ✴ Bug fixes ✴ Cleanup ✴ Potential features

Slide 134

Slide 134 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 Here’s a few…

Slide 135

Slide 135 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 136

Slide 136 text

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