Ruby Stylista

Stylista-1.jpg

The thing is, like a lot of programming language fans, I'm fascinated by programming as communication, and therefore also by thing like style guides that bridge the gap between the formal requirements of the compiler and the cognitive needs of the programmer writing and reading the code. At best, consistent style makes code easier to produce and read in much the same way that Rails itself makes code easier to produce and read -- by suggesting a consistent placement for your code and reducing unneeded choice in the name of useful conventions.

For me, at least, there's also something of a pride in workmanship issue, in much the same way that a professional chef will take pride in keeping their kitchen area clean even in the midst of chaos.

So, thanks to those of you who took the time to comment on the style guide I posted last week. I am planning to put the guide up someplace more permanent where the conversation on these issues can continue, but in the meantime, I wanted to promote some comments and talk a little bit more about a couple of issues that were flagged by commenters.

Typos

I apologize again for a couple of typos and for an issue we have with our blog about indentation in code samples not showing up properly on Safari. Normally, it's not that big a deal, but it did make a couple of the examples I posted look totally nonsensical, which was... unfortunate. I've got a workaround now, though, so it should be better from here on out.

80 Columns and splitting lines.

A couple of commenters suggested that my 80 column limit was ridiculous on large monitors. (A couple also bucked the 2-space indent guide. Look, like it or not 2 spaces is clearly the community standard for Ruby -- don't do it if you don't want to, but at least realize that many Ruby programmers will find it weird-looking)

I should say, about the 80 column thing, that I use larger fonts and a narrower window in my code editor than pretty much any programmer I know without actual vision problems. Three reasons: 1) Seems to lower eyestrain over the day 2) Most of the code is in the left 40-60 columns anyway, so extending out to 120 columns or more seems a waste of space, but most importantly 3) it's a not-so-subtle guide to keep methods small, lines of code simple and distinct, and generally keep things in order. My goal is not to be able to see as much code as possible, but to be able to focus on the code I'm working on.

When you are breaking up code over multiple lines, it's much better to keep logical parts of the code together, rather than forcing every line as close to the 80 character boundary as possible. I mean, you could do this:

post :create, :relationship => { :kind => "relative",
    :user_id => @clark.id }, :person => {:first_name => "Martha",
    :last_name => "Kent"}

But isn't this a lot more readible...

post :create,
    :relationship => { :kind => "relative", :user_id => @clark.id },
    :person => {:first_name => "Martha", :last_name => "Kent"}

And and or or && and ||

There was some back and forth in the comments about when you should use the and/or versus the &&/|| form of boolean operator.

The issue here is binding and precedence. The &&/|| form has very high precedence, while and/or has very low precedence. So the following two statements are equivalent, since the || has top priority:

x = y || z
x = (y || z)

Also, the following two statements are equivalent, since the assignment operator has higher priority than and:

x = y or z
(x = y) or z

About 99 times out of a hundred, the || is what you want, while the or will cause a subtle bug that you will never find. Hence, the advice to just stick to &&/||.

If expressions, then and ternary

I was most surprised in the comments by the idea that then is "almost a deprecated keyword", but if that's the case, then I'll back off from using it in multiline epxressions. I also don't think I was completely clear on exactly what it is I do with if expressions.

For a conditional assignment that is simple and fits on one line, I use an if expression:

x = if current_user then 3 else 0 end

I prefer that to the ternary operator

x = current_user ? 3 : 0

On the ground that it's more readable and I can never remember which part of the ternary goes where.

If the expression or the values gets a little long for one line, but is still basically a simple assignment, then I split the line like this:

x = if a_very_long_boolean_expression_is_true
    then the_true_response
    else the_false_response
    end

I use then and keep both return statements in the same line as a marker to the reader that this is still basically a simple assignment. If you want to leave out the then, fine by me. I prefer that to:

if a_very_long_boolean_expression_is_true
  x = the_true_response
else
  x = the_false_response
end

Because I feel like it's easy for a later programmer (including myself) to break the parallelism. But I don't feel that strongly about it. If the assignment gets any more complicated, then I tend to break it out into it's own method, which also eliminates the duplicate assignment:

x = the_response

def the_response
  if a_very_long_boolean_expression_is_true
    the_true_response
  else
    the_false_response
  end
end

Obviously, there are a bunch of ways to format the separate method that might be preferable depending on how complicated it really is.

Symbol#to_proc

A couple of people pointed out that this is a Rails add on, and not a core Ruby thing until Ruby 1.9. Seriously, I forget that fact all the time. The Rails implementation is also kind of slow, if that bothers you, supposedly it's faster in Ruby 1.9

Quick hits

Three quick comments that I agree with, slightly edited. The first from Alin:

The Ruby way: return if x.nil? instead of
return if x == nil

Sean O'Halpin, with two nice clarifications of things I said:

The general rule for parentheses is to use them in functional expressions, leave them out in statements.

and:

To clarify your point: chaining is fine on enumerable methods that return enumerables - you’ll always have an enumerable to act on. Chaining is not such a good idea where any intermediate method may return nil (your second example)