The Elements of (Ruby) Style

I’ve been working with Ruby a lot lately, and have come to enjoy the expressive succinctness of the language. It is credited as a language that strives to make programming enjoyable, and minimize the amount of work the programmer must do. To accomplish those goals, Ruby allows the flexibility to do things in many different ways. This approach, in contrast with the “one right and obvious way” mentality, allows the programmer to use a style suited to their way of approaching the problem at hand. The resulting syntactic flexibility is a double edged sword, however, and can lead to concise and beautiful code in the right hands, or utterly incomprehensible code in the wrong ones.

In that regard, Ruby seems quite similar to English. Both are complex, multifaceted languages, which take time to perfect, and must be studied before they can be applied effectively. One of the seminal works regarding English writing style is a book by William Strunk Jr. and E. B. White called “The Elements of Style”. Instead of a comprehensive guide, the short book presents a few powerful rules that greatly enhance the quality of most writing. This post is my attempt at creating a similar style guide for Ruby. For a more wholistic Ruby style guide, see: https://github.com/bbatsov/ruby-style-guide.

Elements

Create Importable Scripts

class Utility
    # utility class logic
end

if __FILE__ == $0
    # code in this block is only executed if the file is run from the command line
    Utility.new(opts).run
end

Prefer Iterators (and blocks) to For Loops

# perform an action a certian number of times
5.times {|index| ... }
(1..10).each{|i| ...}

# looping through collections
wines.each {|wine| wine.pour} # use each for side effects
wines.map {|wine| wine.vintage} # use map to transform an enumerable
wines.select {|wine| wine.type == :merlot}
wines.reject {|wine| wine.price > 30}
wines.include?(my_favorite_wine)

Prefer String Interpolation

s = "strings"
# Bad
puts "Concatenated " + s + " example"
puts "Hello World"

# Good
puts "Concatenated #{s} example"
puts 'Hello World'

Favor End of Line Modifiers

# Bad
if !wine.corked?
    drink(wine)
end

# Good
drink(wine) unless wine.corked?

Utilize Method Qualifiers

cellar.empty? # should return boolean value
cellar.clear! # modifies the state of the cellar

Favor “Or Equal” Idiom over nil Checks

# Bad
if a.nil?
    a = b
end

# Less Bad
a = b if a.nil?

# Good
a ||= b

Favor Block Yielding Methods

# Bad
begin
    file = File.open("/tmp/filename", "w")
    file.write("some text")
rescue IOError => e
    # need to ensure that the file handle gets closed even if an error occurs
ensure
    file.close
end

# Good
File.open('/tmp/filename', 'w') {|file| file.write("some text")} # file handle closed once block exits

Prefer Hash for Optional Args

# Bad
def foo(required_param, opt_arg1 = 'default1', opt_arg2 = 'default2', opt_arg3 = 'default3')
    # ...
end

# Good
def foo(required_param, optional_params = {})
    opts = {:arg1 => 'default1',
    :arg2 => 'default2',
    :arg3 => 'default3'}.merge(optional_params)
    # ...
end

Forgo Explicit Returns

# Bad
def fib(n)
    return 1 if n < 2 # usage of return here is ok
    return fib(n-1) + fib(n-2) # the return here is unnecessary
end

# Good
def fib(n)
    (0..n).inject([1,0]) { |(a,b), _| [b, a+b] }.first
end

No Empty Parentheses

Proper Block Semantics

# Bracket style block
wines.map{|w| w.name}.sort

# Do style block
cellars.each do |cellar|
    cellar.wines.each do |wine|
        wine.pour! if desired_wines.include?(wine)
    end
end

And, Or vs &&, ||

# boolean expression example
if condition1 && condition2
    do_something
end

# control flow example
buy_wine and open_wine and drink_wine

# example of the differences
a = true && false
#=> false (a == false) # the && operator takes precedence
a = true and false
#=> false (a == true) # the = operator takes precedence

Prefer Static Methods, and a Functional Approach

class Test
    # static method
    def self.method1
        ...
    end

    # instance method
    def method2
        ...
    end
end


keys = [k1, k2, k3, ...]
values = [v1, v2, v3, ...]

# Bad
result = {}
keys.each_with_index do |key, index|
    result[k] = values[index]
end

# Good
result = Hash[keys.zip(values)]

Prefer Self-documenting Code to Comments

Favor Small, Succient, Well Named Methods

Summary

Quality Ruby code should be simple, readable, and DRY. The rules above are solid guidelines that will lead to better code if followed properly. That being said, as with any set of rules, there are always exceptions, so be judicious in their application. As Ralph Waldo Emerson once said,

“A foolish consistency is the hobgoblin of little minds”