01 - Source Code Layout

Nearly everybody is convinced that every style but their own is ugly and unreadable. Leave out the "but their own" and they're probably right...
-- Jerry Coffin (on indentation)

  • Use UTF-8 as the source file encoding.

  • Use two spaces per indentation level (aka soft tabs). No hard tabs.

    # bad - four spaces
    def some_method
        do_something
    end
    
    # good
    def some_method
      do_something
    end
    
  • Use Unix-style line endings. (*BSD/Solaris/Linux/OS X users are covered by default, Windows users have to be extra careful.)

    • If you're using Git you might want to add the following configuration setting to protect your project from Windows line endings creeping in:

      $ git config --global core.autocrlf true
      
  • Don't use ; to separate statements and expressions. As a corollary - use one expression per line.

    # bad
    puts 'foobar'; # superfluous semicolon
    
    puts 'foo'; puts 'bar' # two expressions on the same line
    
    # good
    puts 'foobar'
    
    puts 'foo'
    puts 'bar'
    
    puts 'foo', 'bar' # this applies to puts in particular
    
  • Prefer a single-line format for class definitions with no body.

    # bad
    class FooError < StandardError
    end
    
    # okish
    class FooError < StandardError; end
    
    # good
    FooError = Class.new(StandardError)
    
  • Avoid single-line methods. Although they are somewhat popular in the wild, there are a few peculiarities about their definition syntax that make their use undesirable. At any rate - there should be no more than one expression in a single-line method.

    # bad
    def too_much; something; something_else; end
    
    # okish - notice that the first ; is required
    def no_braces_method; body end
    
    # okish - notice that the second ; is optional
    def no_braces_method; body; end
    
    # okish - valid syntax, but no ; makes it kind of hard to read
    def some_method() body end
    
    # good
    def some_method
      body
    end
    

    One exception to the rule are empty-body methods.

    # good
    def no_op; end
    
  • Use spaces around operators, after commas, colons and semicolons, around { and before }. Whitespace might be (mostly) irrelevant to the Ruby interpreter, but its proper use is the key to writing easily readable code.

    sum = 1 + 2
    a, b = 1, 2
    [1, 2, 3].each { |e| puts e }
    class FooError < StandardError; end
    

    The only exception, regarding operators, is the exponent operator:

    # bad
    e = M * c ** 2
    
    # good
    e = M * c**2
    

    { and } deserve a bit of clarification, since they are used for block and hash literals, as well as string interpolation. For hash literals two styles are considered acceptable.

    # good - space after { and before }
    { one: 1, two: 2 }
    
    # good - no space after { and before }
    {one: 1, two: 2}
    

    The first variant is slightly more readable (and arguably more popular in the Ruby community in general). The second variant has the advantage of adding visual difference between block and hash literals. Whichever one you pick - apply it consistently.

  • No spaces after (, [ or before ], ).

    # bad
    some( arg ).other
    [ 1, 2, 3 ].size
    
    # good
    some(arg).other
    [1, 2, 3].size
    
  • No space after !.

    # bad
    ! something
    
    # good
    !something
    
  • No space inside range literals.

      # bad
      1 .. 3
      'a' ... 'z'
    
      # good
      1..3
      'a'...'z'
    
  • Indent when as deep as case. This is the style established in both "The Ruby Programming Language" and "Programming Ruby".

    # bad
    case
      when song.name == 'Misty'
        puts 'Not again!'
      when song.duration > 120
        puts 'Too long!'
      when Time.now.hour > 21
        puts "It's too late"
      else
        song.play
    end
    
    # good
    case
    when song.name == 'Misty'
      puts 'Not again!'
    when song.duration > 120
      puts 'Too long!'
    when Time.now.hour > 21
      puts "It's too late"
    else
      song.play
    end
    
  • When assigning the result of a conditional expression to a variable, preserve the usual alignment of its branches.

    # bad - pretty convoluted
    kind = case year
    when 1850..1889 then 'Blues'
    when 1890..1909 then 'Ragtime'
    when 1910..1929 then 'New Orleans Jazz'
    when 1930..1939 then 'Swing'
    when 1940..1950 then 'Bebop'
    else 'Jazz'
    end
    
    result = if some_cond
      calc_something
    else
      calc_something_else
    end
    
    # good - it's apparent what's going on
    kind = case year
           when 1850..1889 then 'Blues'
           when 1890..1909 then 'Ragtime'
           when 1910..1929 then 'New Orleans Jazz'
           when 1930..1939 then 'Swing'
           when 1940..1950 then 'Bebop'
           else 'Jazz'
           end
    
    result = if some_cond
               calc_something
             else
               calc_something_else
             end
    
    # good (and a bit more width efficient)
    kind =
      case year
      when 1850..1889 then 'Blues'
      when 1890..1909 then 'Ragtime'
      when 1910..1929 then 'New Orleans Jazz'
      when 1930..1939 then 'Swing'
      when 1940..1950 then 'Bebop'
      else 'Jazz'
      end
    
    result =
      if some_cond
        calc_something
      else
        calc_something_else
      end
    
  • Use empty lines between method definitions and also to break up methods into logical paragraphs internally.

    def some_method
      data = initialize(options)
    
      data.manipulate!
    
      data.result
    end
    
    def some_method
      result
    end
    
  • Avoid comma after the last parameter in a method call, especially when the parameters are not on separate lines.

    # bad - easier to move/add/remove parameters, but still not preferred
    some_method(
                 size,
                 count,
                 color,
               )
    
    # bad
    some_method(size, count, color, )
    
    # good
    some_method(size, count, color)
    
  • Use spaces around the = operator when assigning default values to method parameters:

    # bad
    def some_method(arg1=:default, arg2=nil, arg3=[])
      # do something...
    end
    
    # good
    def some_method(arg1 = :default, arg2 = nil, arg3 = [])
      # do something...
    end
    

    While several Ruby books suggest the first style, the second is much more prominent in practice (and arguably a bit more readable).

  • Avoid line continuation \ where not required. In practice, avoid using line continuations for anything but string concatenation.

    # bad
    result = 1 - \
             2
    
    # good (but still ugly as hell)
    result = 1 \
             - 2
    
    long_string = 'First part of the long string' \
                  ' and second part of the long string'
    
  • Adopt a consistent multi-line method chaining style. There are two popular styles in the Ruby community, both of which are considered good - leading . (Option A) and trailing . (Option B).

    • (Option A) When continuing a chained method invocation on another line keep the . on the second line.

      # bad - need to consult first line to understand second line
      one.two.three.
        four
      
      # good - it's immediately clear what's going on the second line
      one.two.three
        .four
      
    • (Option B) When continuing a chained method invocation on another line, include the . on the first line to indicate that the expression continues.

      # bad - need to read ahead to the second line to know that the chain continues
      one.two.three
        .four
      
      # good - it's immediately clear that the expression continues beyond the first line
      one.two.three.
        four
      

    A discussion on the merits of both alternative styles can be found here.

  • Align the parameters of a method call if they span more than one line. When aligning parameters is not appropriate due to line-length constraints, single indent for the lines after the first is also acceptable.

    # starting point (line is too long)
    def send_mail(source)
      Mailer.deliver(to: '[email protected]', from: '[email protected]', subject: 'Important message', body: source.text)
    end
    
    # bad (double indent)
    def send_mail(source)
      Mailer.deliver(
          to: '[email protected]',
          from: '[email protected]',
          subject: 'Important message',
          body: source.text)
    end
    
    # good
    def send_mail(source)
      Mailer.deliver(to: '[email protected]',
                     from: '[email protected]',
                     subject: 'Important message',
                     body: source.text)
    end
    
    # good (normal indent)
    def send_mail(source)
      Mailer.deliver(
        to: '[email protected]',
        from: '[email protected]',
        subject: 'Important message',
        body: source.text
      )
    end
    
  • Align the elements of array literals spanning multiple lines.

    # bad - single indent
    menu_item = ['Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam',
      'Baked beans', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam']
    
    # good
    menu_item = [
      'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam',
      'Baked beans', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam'
    ]
    
    # good
    menu_item =
      ['Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam',
       'Baked beans', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam']
    
  • Add underscores to large numeric literals to improve their readability.

    # bad - how many 0s are there?
    num = 1000000
    
    # good - much easier to parse for the human brain
    num = 1_000_000
    
  • Use Rdoc and its conventions for API documentation. Don't put an empty line between the comment block and the def.

  • Limit lines to 80 characters.

  • Avoid trailing whitespace.

  • End each file with a newline.

  • Don't use block comments. They cannot be preceded by whitespace and are not as easy to spot as regular comments.

    # bad
    =begin
    comment line
    another comment line
    =end
    
    # good
    # comment line
    # another comment line