Find dead methods in your Ruby app
zom·bie code noun Undead code that shambles around your repository, and eats your brains.
You don't want zombie code around. But you can't spend all your time looking for it. So get yourself a Zombie Scout.
Zombie Scout is light & quick. Its only tools are parser
and grep
. It
parses your code to find method declarations, and then greps through your
project's source, looking for each method. If Zombie Scout can't find any
calls to a method, it presumes the method is dead, and reports back to you.
Zombie Scout parses your ruby files, and remembers all the methods it sees defined.
Then it greps your whole project - currently .rb, .erb, and *.haml files - for
whole-word occurrances of any of the methods it found. The whole-word grep
(grep -w
) means that searching for dead_method
won't count dead_methods
as a match, and accidentally think it's live.
So, if you have 1,000 methods, you have to run 1,000 greps? That's slow, right? Good news: back in Phase 1, when Zombie Scout parsed your code, it also remembered all the method CALLS it saw, and it automatically counts those methods as not-zombies, so it saves a bunch of time by not grepping for them.
Zombie Scout isn't exhaustive or thorough - it's a scout, not a spy. (That could be another project, though - a Zombie Spy.)
If you generate methods in a way that's hard to grep for...
method_name = ['s', 'e', 'c', 'r', 'e', 't']
object.send(method_name.join(''))
...then Zombie Scout won't find it. Remember: light & quick.
That said, it will find methods defined with attr_reader
& friends, or
Forwardable
delegates, or Rails scopes. Rails delegators are on the To-Do
list.
If you have methods that are used by another library - say, callbacks - Zombie Scout will probably think they're dead, because it's not looking at the source for that other library.
Finally, if you have a method named after a common human-language word, and that word appears in (say) hard-coded strings or comments, Zombie Scout will think it's calling the method, and assume that the method is used.
# this is an awesome method! <-- false positive right there
def awesome
'AWESOME!'
end
Be wise.
The basics:
gem install 'zombie_scout'
Or, add this to your Gemfile:
gem 'zombie_scout'
You can run it on a whole folder:
dan@aleph:~/projects/zombie_scout$ zombie_scout scout
Scouted 48 methods in 10 files, in 1.470468836 seconds.
Found 13 potential zombies, with a combined flog score of 66.9.
lib/zombie_scout/parser.rb:45 ZombieScout::Parser#on_send 8.3
lib/zombie_scout/parser.rb:17 ZombieScout::Parser#on_class 7.9
lib/zombie_scout/parser.rb:25 ZombieScout::Parser#on_module 7.2
lib/zombie_scout/parser.rb:39 ZombieScout::Parser#on_defs 5.9
lib/zombie_scout/parser.rb:81 ZombieScout::Parser#handle_def_delegators 5.8
lib/zombie_scout/parser.rb:72 ZombieScout::Parser#handle_attr_accessor 5.6
lib/zombie_scout/parser.rb:33 ZombieScout::Parser#on_def 4.9
lib/zombie_scout/parser.rb:56 ZombieScout::Parser#handle_attr_reader 4.3
lib/zombie_scout/parser.rb:64 ZombieScout::Parser#handle_attr_writer 4.3
lib/zombie_scout/parser.rb:89 ZombieScout::Parser#handle_def_delegator 3.8
lib/zombie_scout/parser.rb:95 ZombieScout::Parser#handle_scope 3.8
lib/zombie_scout/parser.rb:122 ZombieScout::ConstExtracter#on_const 2.7
lib/zombie_scout/parser.rb:116 ZombieScout::SymbolExtracter#on_sym 2.4
(See what I meant about callbacks and false-positives?)
Or you can run it on a given file or glob:
dan@aleph:~/projects/zombie_scout$ zombie_scout scout lib/app.rb
Scouted 1 methods in 1 files, in 0.041942649 seconds.
Found 0 potential zombies, with a combined flog score of 0.0.
ZombieScout will also report in CSV, if you like:
dan@aleph:~/projects/zombie_scout$ zombie_scout scout --format csv
location,name,flog_score
lib/zombie_scout/parser.rb:45,ZombieScout::Parser#on_send,8.3
lib/zombie_scout/parser.rb:17,ZombieScout::Parser#on_class,7.9
lib/zombie_scout/parser.rb:25,ZombieScout::Parser#on_module,7.2
lib/zombie_scout/parser.rb:39,ZombieScout::Parser#on_defs,5.9
lib/zombie_scout/parser.rb:81,ZombieScout::Parser#handle_def_delegators,5.8
lib/zombie_scout/parser.rb:72,ZombieScout::Parser#handle_attr_accessor,5.6
lib/zombie_scout/parser.rb:33,ZombieScout::Parser#on_def,4.9
lib/zombie_scout/parser.rb:56,ZombieScout::Parser#handle_attr_reader,4.3
lib/zombie_scout/parser.rb:64,ZombieScout::Parser#handle_attr_writer,4.3
lib/zombie_scout/parser.rb:89,ZombieScout::Parser#handle_def_delegator,3.8
lib/zombie_scout/parser.rb:95,ZombieScout::Parser#handle_scope,3.8
lib/zombie_scout/parser.rb:122,ZombieScout::ConstExtracter#on_const,2.7
lib/zombie_scout/parser.rb:116,ZombieScout::SymbolExtracter#on_sym,2.4
You can also embed ZombieScout in your own code, if you need that kind of thing:
irb> require 'zombie_scout'
=> true
irb> require 'pp'
=> true
irb> > pp ZombieScout::Mission.new('.').scout
[{:location=>"./lib/zombie_scout/parser.rb:17",
:file_path=>"./lib/zombie_scout/parser.rb",
:name=>:on_class,
:full_name=>"ZombieScout::Parser#on_class",
:flog_score=>7.9},
{:location=>"./lib/zombie_scout/parser.rb:25",
:file_path=>"./lib/zombie_scout/parser.rb",
:name=>:on_module,
:full_name=>"ZombieScout::Parser#on_module",
:flog_score=>7.2},
{:location=>"./lib/zombie_scout/parser.rb:33",
:file_path=>"./lib/zombie_scout/parser.rb",
:name=>:on_def,
:full_name=>"ZombieScout::Parser#on_def",
:flog_score=>4.9}, ...]
.zombie_scout
.ToThinkAbouts:
Have ZombieScout make a hash-y report, & feed it to ZombieSpy, so the Spy knows where to focus.