Ruby Deeper Impressions

For two weeks (ending on this past Wednesday afternoon), most of my days and nights were occupied with a self-administered crash course in the Ruby programming language, outside of the Rails framework. I had struggled somewhat with Objective-C in January, partly because of the massive combined burden of learning the language, the Cocoa framework, the Xcode IDE, and the odd but brilliant Interface Builder. So, I wanted to try and attack Ruby in isolation.

Generally speaking, I like Ruby a lot. It is unnerving to work in an environment in which you can’t know if your code is remotely likely to work or not until you run those lines of code, but as I mentioned previously, thorough unit tests and a code coverage tool address that need.

I worked pretty hard to stylistically lean toward functional programming, and I think that worked pretty well. Whereas in Perl and Java I would have avoided the terse way of doing things in favor of clear (if lengthy) variable names and pseudocode in comments, in Ruby it seemed that in most cases, the extremely compact but algebraic way of doing things was equally readable and a lot simpler to understand.

Example (sadly, word wrapped due to this blog’s layout constraints, though it wasn’t in the original code):

  def total_pct_full
    100 * non_ignored_filesystem_statuses.inject(0.0) {|sum,fss| sum += fss.used_k} /
      non_ignored_mount_total_k
  end

Assuming you know what inject does (and it’s part of Array, so you really ought to if you know Ruby at all) and that you know that the application lets you configure it to ignore certain filesystems on each backed-up remote server, it’s pretty obvious what this method is telling you, and how it computes that.

I did find myself tugged back into the Java-ish way of thinking, which is to say that I still find static type checking and even a bit of Design by Contract to be comforting. Realizing that this might actually be a bad fit, though, I wrote a simple mixin module called ArgChecks that has a few methods like this:

  def arg_max(max, *args)
    args.each do |obj|
      raise ArgumentError, "Argument exceeds maxmimum value "+
        "(max is #{max.to_s})" unless max >= obj
    end
  end

You would call it (and its pals) like this:

  # A silly contrived example
  def pct_to_f(pct)
    arg_type Integer, pct
    arg_max 100, pct
    arg_min 0, pct
    pct/100
  end

I realized pretty quickly that the only place this was really necessary was right before I was about to do something where problems of nil-ness, wrong type, invalid range, wrong set of hash keys, etc. would actually cause something bad to happen, as opposed to checking if nil == firstname all the way down the call graph. That’s beyond defensive/paranoid programming, and well into OCD territory.

Still, even though I was arguably bringing some C++/Java baggage with me into Ruby land, Ruby allowed me to do it with a fair amount of elegance. My test classes were made elegant by another bit of Ruby goodness, using blocks:

  def with_empty_archive(nickname, &block)
    begin
      create_archive(nickname)
      archive = ba_setup(nickname)
      yield(archive)
    ensure
      cleanup_archive
    end
  end

That means I can do things like this in other tests:

    with_empty_archive(source.nickname) do |archive|
      agent.perform_backup
      assert_in_delta(12.0, archive.total_mb, 1.0)
    end

Pretty tidy!

Really, the main complaint I have from my fortnight of Ruby development is that the Ruby Gems way of thinking doesn’t at all fit with the Debian/FHS way of thinking, so the package that Ubuntu installs for Ruby Gems kind of sucks, and it doesn’t really work correctly when you want the magic of “gem install xxx” to get some free code onto your system. I’m not sure if there’s anyone particularly at fault here, or if it’s just a philosophical rift that makes sense from both sides of the fence, but it’s inconvenient. On my to-do list for further Ruby work is an item called “reinstall Ruby Gems from source”.

No Comments Yet

You can be the first to comment!

Speak Your Peace