hardscrabble

eighty character lines

06 Sep 2015

Last month we talked about RuboCop, which analyzes your Ruby code and nitpicks it. One of its most difficult to follow suggestions is to keep your lines of code no longer than 80 characters.

The creator of rubocop, bbatsov, explained his perspective on his blog:

We should definitely have a limit – that’s beyond any doubt. It’s common knowledge that humans read much faster vertically, than horizontally. Try to read the source code of something with 200-character lines and you’ll come to acknowledge that.

I’m totally on board with the short lines train. For me, it only gets tricky when dealing with nested stuff (examples to follow) which add a lot of space to the left of the first character of code. For example:

module MyGreatGem
  module SomeOtherNamespace
    module OmgAnotherNamespace
      module LolYeahOneMore
        class SomethingGreat
          class SomethingOk
            class MyGreatClass
              def initialize
                puts "OMG I only have 64 characters to express something on " \
                     "this line! And now it's more like 'these lines' haha"
              end
            end
          end
        end
      end
    end
  end
end

Often strings are the first thing to get chopped up, as in that example.

The only approach I thought of to deal with that is to organize my code differently to not use many nested namespaces. That’s probably not the worst idea, honestly, but I’m writing this post to share an interesting style I observed in the wild (read: on github) that takes a whole nother approach:

# Excerpted from:
# https://github.com/net-ssh/net-sftp/blob/ebf5d5380cc533b69b308baa2e396e4a18abc900/lib/net/sftp/operations/dir.rb
module Net; module SFTP; module Operations
  class Dir
    attr_reader :sftp

    def initialize(sftp)
      @sftp = sftp
    end
  end
end; end; end

Huh! That’s a style I hadn’t seen before. RuboCop has many complaints about it, and I don’t totally love the style, but it’s a very novel and neat way to do it, and it certainly frees up columns to spend on your code if you’re planning to stick to an 80 character limit.

One possible alternative is to define your namespaced class using this shorthand:

class Net::SFTP::Operations::Dir
  attr_reader :sftp

  def initialize(sftp)
    @sftp = sftp
  end
end

If you do that, you get 2 extra characters on each line. Sweet!

One problem: it sort of doesn’t work, at least not in the same way.

If you just look at that example, and imagine that you’re the Ruby interpreter trying to figure out what this code means, how are you supposed to know whether Net, SFTP, and Operations are supposed to be classes or modules? You have to already know by them being previously defined. If they haven’t been defined yet, you are well within your right to raise a RuntimeException to complain that this constant hasn’t been defined yet, rather than try to guess.

Both of the earlier longhand examples were explicitly explaining what the type of each namespace constant is. That pattern works whether you’re defining the module or class in that moment, or “opening” a previously defined module or class to add something new to it. This shorthand, while optimal for line length, only works when opening previously defined constants.

One downside of this approach is that, by relying on all of the namespaces being predefined, it becomes harder to test this class in isolation (it’s probably possible to do it through some gnarly stubbing but, harder). You’re also introducing some requirements about the order in which the files from your code need to be loaded, which feels kind of fragile.

One possible upside comes to mind. When you follow the typical pattern of writing out all the namespace modules and classes, you introduce some room for error: what if in one file you write class Operations by mistake (instead of module Operations)? You’ll get an error. That’s not too bad, honestly.

I think 80 is usually enough but if you’re doing too many contortions to stay in that box, try like 90 or 100, you’re still a good person.