eigenclass logo
MAIN  Index  Search  Changes  PageRank  Login

Ruby, SEX and STDs: infectious practices

Let's get past the shame and stigma and talk about the ailments of your code. Yes, you could be infected right now without knowing it. Anecdotal evidence indicates that maybe as many as three in four Rubyists are.

It might be endogenous (bad laziness), or maybe you caught it via risky SEX*1. Fortunately, the condition is easy to diagnose, even if the treatment isn't effortless.

Virical nature

Say we're writing sqlitespace.rb*2, a small library which might be released for external consumption at some point in time, which implements persistent DRb TupleSpaces atop SQLite.

sqlitespace.rb requires sqlite.rb, which happens to be infected. At this point, sqlitespace.rb is at great risk, and very likely to be contaminated too.

Diagnosis

Just run your code with ruby -w or better yet set RUBYOPT to -w, and inspect the result:

#<Benchmark::Tms:0xa7cbdfd4 @total=0.23, @cutime=0.0, @label="", @stime=0.01, @real=0.22431206703186, @utime=0.22, @cstime=0.0>
./sqlitespace.rb:46: warning: `&' interpreted as argument prefix
./sqlitespace.rb:91: warning: useless use of a constant in void context
./sqlitespace.rb:125: warning: `*' interpreted as argument prefix
./sqlitespace.rb:216: warning: `*' interpreted as argument prefix
/home/batsman/usr//lib/ruby/site_ruby/1.8/sqlite/database.rb:243: warning: `*' interpreted as argument prefix
/home/batsman/usr//lib/ruby/site_ruby/1.8/sqlite/statement.rb:103: warning: `*' interpreted as argument prefix
(eval):2: warning: `*' interpreted as argument prefix
(eval):2: warning: `*' interpreted as argument prefix
(eval):2: warning: `*' interpreted as argument prefix
(eval):2: warning: `*' interpreted as argument prefix
(eval):2: warning: `*' interpreted as argument prefix
(eval):2: warning: `*' interpreted as argument prefix
(eval):1: warning: method redefined; discarding old delete
/home/batsman/usr//lib/ruby/site_ruby/1.8/sqlite/resultset.rb:73: warning: instance variable columns not initialized
/home/batsman/usr//lib/ruby/site_ruby/1.8/sqlite/resultset.rb:73: warning: instance variable types not initialized
/home/batsman/usr//lib/ruby/site_ruby/1.8/sqlite/resultset.rb:73: warning: instance variable columns not initialized
/home/batsman/usr//lib/ruby/site_ruby/1.8/sqlite/resultset.rb:73: warning: instance variable types not initialized
/home/batsman/usr//lib/ruby/site_ruby/1.8/sqlite/resultset.rb:73: warning: instance variable columns not initialized
/home/batsman/usr//lib/ruby/site_ruby/1.8/sqlite/resultset.rb:73: warning: instance variable types not initialized
[... repeated thousands of times ...]
Using SQLite 2.2.2
#<Benchmark::Tms:0xa7bb1aa0 @total=4.01, @cutime=0.0, @label="", @stime=0.31, @real=4.15142703056335, @utime=3.7, @cstime=0.0>

Here you have, lots of warnings. So many that executing with $VERBOSE=nil becomes very tempting.

But public code health is everybody's business, and we must all fight this temptation.

Think about what will happen if we choose to ignore (or to never be shown) the warnings. sqlitespace.rb will add to the pile of infected, noisy code. Now imagine SQLite is fixed and runs cleanly, but sqlitespace.rb isn't. At that point in time, somebody writes a library that requires sqlitespace.rb, and he faces the same situation we did before: he's got a noisy library, and will be likely to disable warnings, easily making his code relatively noisy.

It's clear by now that ''shipping a library that issues lots of warnings is likely to result in even more "noisy" code being written''.

How bad is the situation in practice? Here's a sample run of rake -T in a Rails application directory; all it does is list available tasks:

   $ rake -T
   (in /home/batsman/src/rcov/pimki)
   /home/batsman/src/rcov/pimki/config/boot.rb:42:Warning: require_gem is obsolete.  Use gem instead.
   /home/batsman/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/validations.rb:334: warning: `*' interpreted as argument prefix
   /home/batsman/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/validations.rb:363: warning: `*' interpreted as argument prefix
   /home/batsman/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/migration.rb:224: warning: instance variable @ignore_new_methods not initialized
   /home/batsman/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/migration.rb:224: warning: instance variable @ignore_new_methods not initialized
   /home/batsman/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/connection_adapters/abstract/connection_specification.rb:41: warning: method redefined; discarding old allow_concurrency=
   /home/batsman/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/connection_adapters/sqlserver_adapter.rb:456: warning: method redefined; discarding old remove_column
   /home/batsman/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/connection_adapters/oracle_adapter.rb:119: warning: (...) interpreted as grouped expression
   /home/batsman/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/request.rb:171: warning: method redefined; discarding old relative_url_root
   /home/batsman/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/cgi_ext/raw_post_data_fix.rb:57: warning: ambiguous first argument; put parentheses or even spaces
   /home/batsman/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/cgi_ext/raw_post_data_fix.rb:8: warning: method redefined; discarding old initialize_query
   /home/batsman/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/session/active_record_store.rb:129: warning: private attribute?
   /home/batsman/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/session/active_record_store.rb:179: warning: method redefined; discarding old connection
   /home/batsman/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/prototype_helper.rb:641: warning: ambiguous first argument; put parentheses or even spaces
   /home/batsman/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/prototype_helper.rb:874: warning: `*' interpreted as argument prefix
   /home/batsman/usr/lib/ruby/gems/1.8/gems/actionmailer-1.2.5/lib/action_mailer/vendor/tmail/facade.rb:486: warning: method redefined; discarding old create_reply
   /home/batsman/usr/lib/ruby/gems/1.8/gems/actionwebservice-1.1.6/lib/action_web_service/protocol/xmlrpc_protocol.rb:6: warning: discarding old message
   /home/batsman/usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/initializer.rb:581: warning: method redefined; discarding old []=
   /home/batsman/usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/initializer.rb:590: warning: method redefined; discarding old []
   /home/batsman/usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/initializer.rb:595: warning: method redefined; discarding old keys
   /home/batsman/usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/initializer.rb:600: warning: method redefined; discarding old find_pair
   /home/batsman/usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/initializer.rb:607: warning: method redefined; discarding old []=
   /home/batsman/usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/initializer.rb:611: warning: method redefined; discarding old []
   /home/batsman/usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/initializer.rb:615: warning: method redefined; discarding old method_missing
   rake clean                              # Remove any temporary products.
   rake clobber                            # Remove any generated file.
   rake db:clear                           # Clears the development database
   [...]

The mere fact of requiring ActiveRecord and friends will clutter the screen with lots of warnings. How can anybody be expected to run his code with $VERBOSE=true, if the few warnings in his code he wants to avoid are going to be drowned in a sea of warnings generated by Rails?

Fight it

The first and most effective measure is setting RUBYOPT to -w.

Even if that's not an option because your code requires infected libraries, you can at least try to make sure you're not contributing to further spread the problem. You can also set $VERBOSE=false temporarily while requiring the libs to get rid of some syntactical warnings, if few warnings are generated afterwards.

Most warnings can be anticipated and corrected even without $VERBOSE's help. They typically fall into these categories:

  • syntactical issues
    • lack of parentheses with *args or &block
    • spaces inside parenthesized argument list; missing parentheses again: foo(bar 1)
  • uninitialized instance variables
  • method redefinition
  • constant reassignment

The two last ones can be avoided by removing the pertinent methods (Module#remove_method) or constants (Module#remove_const).

If you get rid of such trivial warnings, the ones you do see will often point you to possible bugs.



it's not a bug! it's a feature! right? - RemVee (2007-02-12 (Mon) 09:56:44)

I ran the test suite of one of my projects and noticed many many "uninitialized instance variables" warning and realized I consider this be a feature rather that a possible bug. I mean, it's very nice Ruby lets you skip on defining your instance variables and return nil when they are referenced for the first time. Why should I want to have "@foo = @bar = nil" in my initializers, does it buy me some kind of safety?

mfp 2007-02-12 (Mon) 10:07:27

It can help you find typos; if you always do

 @foo = nil

in #initialize, you'll notice right away when Ruby complains

warning: instance variable fo not initialized

Ppl have been asking for something similar for locals for a while too.

Anyway, the point is: the fewer stupid warnings, the better, it'll let you see those that actually matter.

A configurable system for all this would be most useful. Oh, and maybe per-file $VERBOSE?


No Title - Ryan T. Mulligan (2007-02-12 (Mon) 10:23:44)

Thanks doctor. They didn't teach us this in SEX Ed class, in Highschool.


Prudes! - Jeremy Kemper (2007-02-12 (Mon) 12:30:46)

I'll keep writing @foo ||= 'bar' all the live-long day, thank you very much! Please don't use ugly workarounds purely to cut warnings from otherwise idiomatic code.

If you like, enable warnings for the first load of your Rails app's files: set Dependencies.warnings_on_first_load = true. This keeps non-app warnings quiet so you can get on with your code already.

mfp 2007-02-12 (Mon) 13:06:39

I hear there's a residual amount of non-Rails Ruby code out there };-). How does

Dependencies.warnings_on_first_load = true

exactly help me when I'm writing a library (this is the situation this posting applies to)?

I've read active_support/dependencies.rb as well as your changeset, realizing that Rails normally disables warnings.

Doesn't the fact that there are so many warnings that Rails chooses to hide them by default a further indication of the importance of the problem, namely lots of code out there that generate way too many warnings? (Ideally, there should be no need for that)

I also write @foo ||= 'bar', but often prefer @foo = nil in #initialize, it helps me e.g. when I want to make sure I'm not missing a part of the object state in some operation.

drbrain 2007-02-12 (Mon) 16:04:54

= with an ivar doesn't warn:
 $ ruby -w -
 @foo ||= 'bar'

Since OP_ASGN_OR does a defined? check for you.

Using undefined ivars is unidiomatic, since ruby warns you when you run with -w.


Taint $SAFE - Dan (2007-02-13 (Tue) 00:45:33)

Could a similar argument be made to make libraries Taint safe? ($SAFE level 1 at the minimum)

mfp 2007-02-17 (Sat) 07:15:36

It'd seem so, there's no way I can make my lib $SAFE=1-friendly if it dependson another which isn't.

Ozzy Osbourne 2007-07-10 (Tue) 12:00:55

Ozzy Osbourne

ZZ Top 2007-07-11 (Wed) 01:57:33

ZZ Top


-w - Seth (2007-02-13 (Tue) 06:50:14)

It would be nice if you could define a list of files that you wanted to have checked with -w. That way you could ignore the externally written libraries you includes.



Goal - G (2007-04-19 (Thr) 06:28:48)

I don't actually care about many of the arguments why/where here- I just want to know that when I run "ruby -w", the warnings that come up are warnings I should care about.

If ActiveRecord spews warnings in normal operation (as it does), that's BAD.

I want to see MY warnings and errors, not normal use of ActiveRecord.



Last modified:2007/02/12 09:22:08
Keyword(s):[blog] [ruby] [frontpage] [warning] [verbose]
References:[Towards compatibility with Ruby 1.9: Rails, Rake, RubyGems...]

*1 Software EXchange

*2 I hope you don't mind, Ara, I'm taking it as an example because it's what made me think of this as something infectious