I was wondering whether anyone had done any work on a Lint style tool
for Ruby programs?
A google for "Ruby Lint" didn't turn up anything obviously useful.
Short version of this reply...
* "ruby -w"
* Unit tests and .respond_to? and kind_of? blur the boundary
between static analysis and dynamic testing.
Long winded version of this reply...
A very interesting question, what static analysis do we need for Ruby?
"ruby -w" does a lot for you, don't leave the command line without it.
Unit tests really do catch a lot of problems and are so easy to do.
I tend to unit test and cross brace with a smattering of asserts in my
code at critical places..
For example this gives me similar sort of safety that static typing
would have, but with the flexibility of duck typing...
def dostuff(duck)
raise "The duck '#{duck}' should be able to :quack" unless duck.respond_to? :quack
do_deeper_stuff(duck)
end
def do_deeper_stuff(duck)
do_really_deep_stuff(duck)
end
def do_really_deep_stuff(duck)
duck.quack
end
This example is too trivial to justify it, but in more complex code it
can be a good way of forcing such type errors to the surface even when
they are not exercised by your unit test.
eg. The unit test may not be extensive enough to force control (in a
more complex example) all the way down to "do_really_deep_stuff" but
the assert catches the type error on _all_ runs.
The other trick is Design by Contract style invariants.
A lot of these suggestions are dynamic run-time stuff, not lint
static analysis, but ruby's introspective abilities like respond_to? and
kind_of? and unit tests makes that boundary a bit fuzzy.
class MyClass
def invariant
unless BIG_HAIRY_EXPRESSION_THAT_IS_ALWAYS_TRUE_IF_THIS_OBJECT_IS_SANE
pp self
raise "Invariant failure"
end
end
def initialize
do stuff
invariant
end
def complex_thingy
invariant
do complex stuff
invariant
end
def hairy_stuff( inputs)
raise hell unless ASSERT_STUFF_ABOUT_INPUT
do hairy stuff
invariant
end
end
The another habit is to use rubyish style that resolves a lot of lint-able problems.
C++ uses the "Resource Acquisition Is Initialization" idiom to minimise such things, the Ruby equivalent is
using_resource do |resource|
do stuff with resource
end
A lot of lint-able problems like the evil C switch statement has
simply been eliminated by Matz's excellent design.
The vast majority of the bugs left in my code are either in the
"ruby-lint --read-bosses-mind --pendantic mycode.rb" category or in a failure
handling case.
I'll admit it.
My unit tests are fairly good at testing that my code does what it
should do, but I'm pretty lousy at imagining and testing ways in which
external entities fail.
Hence I would say most of my latent bugs are in the area of failure
handling.
Would lint help there? Maybe, but more creative use of mock objects in
my unit tests would help more.
I'm looking at Needle as a very promising way of doing that.
http://needle.rubyforge.org/
John Carter Phone : (64)(3) 358 6639
Tait Electronics Fax : (64)(3) 359 4632
PO Box 1645 Christchurch Email : (e-mail address removed)
New Zealand
The universe is absolutely plastered with the dashed lines exactly one
space long.