tags:

views:

3455

answers:

14

I was having a discussion with some programmer friends who said that they see Ruby programmers (in particular) producing a lot of code that's "too clever". So I'm wondering what would that look like? I'm referring to the unnecessary use of an obscure language feature in a context in which something straightforward would have worked just as well or better. Know any good Ruby examples of this?

+3  A: 

The output phase of yaml.rb; that's why I co-authored zaml.rb. The standard yaml version does all sorts of metaprogramming (it was originally written by why-the-lucky-stiff, who I generally admire) but by replacing it with a straight forward hierarchical version that directly maps to the class tree we were able to eliminate several O(n^3) cases, resulting in a factor of ten speedup for cases of interest, fix several bugs, and do so in a fraction of the code.

Plus, even people who aren't ruby gurus can see what it does.

MarkusQ
+33  A: 

After giving a straight answer to your question, I'd like to also dispute the premise; whenever a group of programmers characterizes the users of another language in this way, the odds are that they are telling you more about themselves than about the community they are describing.

You could, for example, accuse c programmers of being too obsessed with low level details, or haskell programmers with being blinded by their desire for functional purity; perl mongers for brevity, etc. But you would, IMHO, by getting the causality backwards when you do so.

When I want to write a program that is best expressed in a certain style, I try to choose a language that supports that style. Sometimes you want a tool that lets you do unusual things, and for such a task having a language such as ruby is as valuable as having mathematica for math or javascript for browser manipulation in your toolkit. If I want to play with typography I hop into postscript because that's what it's best at.

It's like saying "Have you ever noticed that people who use power drills are always poking holes in things?" It's true, but it kind of misses the point.

MarkusQ
2 answers? Nice concept!
Joe Philllips
+1 Very wise answer.
Andrew Hare
So, are you saying that Ruby's code is "too clever" by design? I'm not sure if that was the intention of the language designer. I look code which is "too clever" as being a symptom of that intermediate stage where programmers master a languages syntax, but not the tools, idioms, or proper technique.
Juliet
@Princess Hmmm. I don't see how you come by that definition. If you've just got the syntax, you can't really wield the language well enough to be "clever" at all. I more see "too clever" as an accusation leveled by people who _haven't_ mastered the idioms at those who have.
MarkusQ
+1 for drills and "missing the point". I found that rather funny.
SirDemon
+2  A: 

It depends. (I love "it depends" questions)

It depends on the knowledge of the writer and reader. I used to think the use of Symbol#to_proc in Rails was unnecessarily arcane, for example, preferring

a.map { |e| e.downcase }

to

a.map(&:downcase)

Now I'm happy when I read it, although I still don't think to write it.

There are areas of libraries (Rails and others) where I have felt excessive and self-indulgent metaprogramming may have occurred but again the division between "too clever" and "really very clever indeed" is often paper-thin. DSLs are a good area: the ways in which "macros" are made available within classes (think of all that declarative goodness in things like ActiveRecord::Base or ActionController::Base) is very hard for a relative novice to understand and would probably seem like over-cleverness. It did to me. Now I find myself referencing the same code for guidance as I implement similar capabilities.

Mike Woodhouse
D'oh! Not clever enough.
Mike Woodhouse
+5  A: 

The double-bang: !!something

I'm not gonna write what it does. Forget that you ever saw this syntax.

Commander Keen
I have seen the double-bang used if you want a non-zero expression to become, specifically, the value 1 in C. I guess it doesn't bother me too much.
Peter
Not particular to Ruby, and really - how else would you cast to a boolean?
troelskn
troelskn: Ruby treats nil and false as false - everything else as true. I don't think you need the double-bang, and IMO even the ternary operator ( statement : true ? false ) is more readable.
Commander Keen
+5  A: 

Many of the examples in this article would seem to qualify:

21 Ruby Tricks You Should Be Using In Your Own Code.

The title of the article was a bit of a giveaway, given that it reads "Should" instead of "Should Not". Code "should" be transparent. Code "should not" be tricky.

Richard
+2  A: 

I'm not sure if this qualifies as "too clever," but I have seen code that made me wonder if the author was either a genius or an idiot. One developer seemed to have a rule that no method should have more than two lines of code. That pushed the call stack very deep and made debugging rather difficult. The upside is that his overall design was very abstract and even elegant from a distance.

+6  A: 
class Tree
  def initialize*d;@d,=d;end
  def to_s;@l||@r?"<#{@d},<#{@l}>,<#{@r}>>":@d;end
  def total;(@d.is_a?(Numeric)?@d:0)+(@[email protected]: 0)+(@[email protected]: 0);end
  def insert d
    alias g instance_variable_get
    p=lambda{|s,o|d.to_s.send(o,@d.to_s)&&
      (g(s).nil??instance_variable_set(s,Tree.new(d)):g(s).insert(d))}
    @d?p[:@l,:<]||p[:@r,:>]:@d=d
  end
end
Burke
*laugh* While you're at it you could also `alias gb instance_variable_set` (with `gb` standing for "give back").
MarkusQ
I was actually going to, but I only used it once, so it was fewer characters to do it this way :)
Burke
Posted in: http://stackoverflow.com/questions/715951/if-this-code-is-not-a-joke-how-on-earth-does-it-work
strager
It's apparently too clever for ruby:$ ruby test.rbtest.rb:9: syntax error, unexpected ']', expecting tSTRING_CONTENT or tSTRING_DBEG or tSTRING_DVAR or tSTRING_END @d?p[:@l,:]:@d=d ^(Damn, SO is going to re-wrap my lines isn't it? Oh well.. strangely fitting.)
Bill K
Yep, I didn't realize I could paste code by indenting 4 spaces, and a bunch of stuff got stripped out because of less than signs. fixed now.
Burke
+2  A: 

Cucumber (or RSpec Stories)

Quoted from the above RSpec Stories link:

Based around plain text descriptions of application behaviour, it lets you write integration tests with good reuse and good diagnostic reporting.

For example, here's a story I wrote to check the login process.

Story: login as an existing user
    As an unauthenticated user
    I want to log in to Expectnation
    So I can see my account details

    Scenario: login details are correct
            Given an event provider
            And my [email protected] account
            When I log in with email [email protected] and password foofoo
            Then I will be logged in
            And I will be shown the account page

The words such as "Given", "When" and "Then" are cues to the story runner to execute some code. Behind the story sits a collection of steps. Here's a couple of steps from this test:

  Given "my $email account" do |email|
    @user = find_or_create_user_by_email({:email => email,
      :password => 'foofoo',
      :password_confirmation => 'foofoo'})
  end

  When "I log in with email $email and password $password" do |email, password|
    post '/user/account/authenticate',
      :user => {:email => email, :password => password}
  end

Notice how a clever bit of string matching allows you to pass parameters from the story prose.

With a small bit of bolting together, the prose stories are then run as code and the tests executed.

dbr
Just as a note, RSpec Stories have been replaced by Cucumber: http://cukes.info
Greg Campbell
True, thanks, added into answer
dbr
+1  A: 

method_missing can be abused and it's one of those things that may cause you to pull your hair out when you have to fix a bug 3 months after you've written code.

+2  A: 

Take a look at the source of Markaby. Insanity.

+4  A: 

Any use of metaprogramming without having thought damn hard about whether there's a better way to acheive this using the normal, non-'meta' idioms of the language, I tend to find annoying.

An obsession with "DRY" (don't repeat yourself) where some fiendish piece of metaprogramming spaghetti is invoked to avoid repeating yourself, say, twice in a simple and actually-more-straightforward-and-readable-than-the-alternative fashion.

Any use of 'eval' in particular. As metaprogramming goes, this one should be your absolute last resort after trying everything else. eg a lot of rubyists appear not to have heard of Class#define_method.

Matt
Roland
Actually I dislike that meta programming is so easy in Ruby, it's terribly hard to follow and completely impossible for a GUI to parse--but then Ruby isn't really designed for 80 person teams at NASA, it's good for it's target audience.
Bill K
If you are coming to Ruby from Perl, eval(...) is a quite natural thing to do for on the fly code generation, or to pull in previously unknown modules at run time, then check for errors.
Roboprog
a lot of the metaprogramming ills can be prevented with good comments explaining what the magic code does, and providing an example.
Maximiliano Guzman
If you use meta programming to redefine the way string operates system-wide, no amount of documentation will save someone trying to figure out why his strings aren't working as he expected.
Bill K
+1  A: 

compare:

if MODELS.keys.inject(true) {|b, klass| b and klass.constantize.columns.map(&:name).include? association.options [:foreign_key]} then 
  # ... 
end

1 line (if), 132 chars, 132 avg len, 22.9 flog

vs

fk = association.options[:foreign_key] 
columns = MODELS.keys.map { |key| key.constantize.columns.map { |c| c.name } } 
if columns.all? {|column| column.include? fk} then 
  # ... 
end

4 lines, 172 chars, 43 avg chars, 15.9 flog

much faster too.

Original author actually argued maintainability for the first version.

zenspider
+1  A: 

You shouldn't have to go from method to method to method to try to figure out what in the hell something is doing, for the sole purpose of not repeating a few lines of code. Being too focused on the LOC count and ultra-skinny methods might feel cool at the time but is time-consuming for someone else trying to debug or follow the code (and that someone may be you months later).

insane.dreamer
A: 

Recently uncovered this monster:

def id
  unless defined?(@id)
    @id = if id = local_body.to_s[/(?:#\s*|@[[:punct:]]?)#{URL_REGEX}/,1]
            id.to_i
          end
  end
  @id
end

Not that I disagree with caching a calculation, it could just be far more clear.

Ryan Neufeld
BTW it is supposed to grab an id out of a URL in a comment on the first line. Rewriting it as we speak.
Ryan Neufeld