Pippi is a utility for finding suboptimal Ruby class API usage.
Checksets
Pippi has the concept of "checksets" which are, well, sets of checks. The current checksets are listed below.
Maybe we should have a dedicated "test" checkset? Let me know what you think at https://twitter.com/tcopeland, thanks!
Basic
- SelectFollowedByFirst
- SelectFollowedBySize
- ReverseFollowedByEach
Buggy
- AssertWithNil
- MapFollowedByFlatten
Checks
AssertWithNil
Don't use assert_equal with nil as a first argument; use assert_nil instead
For example, rather than doing this:
x = nil ; assert_equal(nil, x)
Instead, consider doing this:
x = nil ; assert_nil(x)
### MapFollowedByFlatten
Don't use map followed by flatten; use flat_map instead
For example, rather than doing this:
```ruby
[1,2,3].map {|x| [x,x+1] }.flatten
Instead, consider doing this:
[1,2,3].flat_map {|x| [x, x+1]}
ReverseFollowedByEach
Don't use each followed by reverse; use reverse_each instead
For example, rather than doing this:
[1,2,3].reverse.each {|x| x+1 }
Instead, consider doing this:
[1,2,3].reverse_each {|x| x+1 }
SelectFollowedByFirst
Don't use select followed by first; use detect instead
For example, rather than doing this:
[1,2,3].select {|x| x > 1 }.first
Instead, consider doing this:
[1,2,3].detect {|x| x > 1 }
SelectFollowedBySize
Don't use select followed by size; use count instead
For example, rather than doing this:
[1,2,3].select {|x| x > 1 }.size
Instead, consider doing this:
[1,2,3].count {|x| x > 1 }
Usage
Inside Rails tests
See https://github.com/tcopeland/pippi_demo#pippi-demo
From the command line:
Assuming you're using bundler:
# Add this to your project's Gemfile:
gem 'pippi'
# Run 'bundle', see some output
# To run a particular check:
bundle exec pippi tmp/tmpfile.rb MapFollowedByFlatten Foo.new.bar out.txt
# Or to run all the basic Pippi checks on your code and exercise it with MyClass.new.exercise_some_code:
bundle exec ruby -rpippi/auto_runner -e "MyClass.new.exercise_some_code"
Ideas for other problems to detect:
# Don't use select followed by compact, use select with the nil inside the block
# unnecessary assignment since String#strip! mutates receiver
# wrong
x = x.strip!
# right
x.strip!
# Use Pathname
# wrong
File.read(File.join(Rails.root, "config", "database.yml")
# right
Rails.root.join("config", "database.yml").read
# Use Kernel#tap
# wrong
x = [1,2]
x << 3
return x
# right
[1,2].tap {|y| y << 3 }
# Rails checks
# No need to call to_i on ActiveRecord::Base methods passed to route generators
# wrong
product_path(@product.to_i)
# right
product_path(@product)
# something with replacing x.map.compact with x.select.map
Here are some things that Pippi is not well suited for
Use self.new vs MyClass.new. This is not a good fit for Pippi because it involves a receiver usage that can be detected with static analysis.
wrong
class Foo def self.bar Foo.new end end
right
class Foo def self.bar self.new end end
TODO
- Clean up this initial hacked out metaprogramming
- Do more checks
- Make writing rules nicer, without some much dorking around with methods. "select followed by first" could be specified with something like "Array#select => #first" and the rest left up to the framework.
Developing
To see teacher output for a file tmp/baz.rb:
rm -f pippi_debug.log ; PIPPI_DEBUG=1 bundle exec pippi tmp/baz.rb DebugCheck Foo.new.bar tmp/out.txt ; cat pippi_debug.log
When trying to find issues in a project:
# in project directory (e.g., aasm)
rm -rf pippi_debug.log pippi.log .bundle/gems/pippi-0.0.1/ .bundle/cache/pippi-0.0.1.gem .bundle/specifications/pippi-0.0.1.gemspec && bundle update pippi --local && PIPPI_DEBUG=1 bundle exec ruby -rpippi/auto_runner -e "puts 'hi'" && grep -C 5 BOOM pippi_debug.log
# or to run some specs with pippi watching:
rm -rf pippi_debug.log pippi.log .bundle/gems/pippi-0.0.1/ .bundle/cache/pippi-0.0.1.gem .bundle/specifications/pippi-0.0.1.gemspec && bundle update pippi --local && PIPPI_DEBUG=1 bundle exec ruby -rpippi/auto_runner -Ispec spec/unit/*.rb
Credits
- Thanks to LivingSocial for letting me develop and open source this utility.
- Thanks to Evan Phoenix for the idea of watching method invocations at runtime using metaprogramming rather than using
Tracepoint. - Thanks to Michael Bernstein (of Code Climate fame) for an inspirational discussion of code anaysis in general.