back

Ruby beginner tip: 2 key ways to write better methods

In development, few things make me cringe more than long methods. While they are faster, most people found that most of the time it’s just not worth the speed boost to write confusing code.

As people like Rein Henrichs have suggested, it should only take 3 seconds to figure out what a method does and only a few more to figure out how it works, at least in most cases in Ruby.

Long methods end up looking like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

def long_method

  # Get user info
  user_stuff = user.stuff
  if user.tall?
    # tall code
    return if user.anxious? && user.trainwreck
    if user.funny?
      # lots of code
      return if trainwreck
    end
  end
  
  # add payment info
  if user.paid?
    # lots of code
  end

  # bill user
  if payment.huge_trainwreck_of_methods && lots_of_conditions && other_things
    # lots of code
  end

end

Here is a quick overview of how to avoid writing long methods.

Long methods have several problems:

1) They are hard to test.
The more cases and things that happen in a method, the more you have to test, because you have to test all the cases if care if your code works or not.

If you have a 4 line method that does one thing, it takes only a line or two to test, if you even unit test it at all. If you’re using cucumber, you often don’t even have to test it at all, as long as its results are tested in cuke.

2) They are hard to read, and require comments
If your method does more than one thing, you often end up writing comments to explain what’s happening. When I’m reading long methods, I find myself having to keep track of things like which if case i’m inside of, when exactly thing line of code is going to be executed, if there’s a possibility that the method has already returned at this point, etc. With Ruby, by composing methods well, we can be so close to English that it’s not really necessary to make people worry about these things.

Long methods that are hard to read break a lot and can’t be reused.

They break a lot because when other developers go in to change them it’s likely that they won’t fully understand the vagaries of how your method is working and will break something. Even with 100% coverage unit tests, it’s a pain to break a unit test in a long method because it often means you’re in for a good amount of code shuffling to get the functionality change that you need plus a passing test. In these cases, imo just rewrite the method.

They can’t be reused because, in the worst case, if you have a method like do_site that is 100 lines and you’re calling it once in a view, it’s probably too specific to be reused anywhere else, but it’s likely that many of the little things that it does could be reused if they were smaller, discrete methods, or even turned into a plugin and used on all your sites.

The problems of long methods, and how to fix them, have been discussed a lot by Rein Henrichs, in this video and in other similar talks.

The clear solution to the above problem is:

1
2
3
4
5
def shorter
  get_info
  add_payment
  bill
end

It’s now totally clear what’s happening in the method, we basically no longer need to unit test it if it’s tested in cucumber, plus we’ve eliminated the need for comments.

For people interested in this stuff, I recommend Refactoring

Of course, there’s another side to this.

If you have a method that is just one line, for example

1
2
3
4
5
6
7
8
9
10

def short
  info = get_payment_info
  add_payment(info)
  bill
end

def get_payment_info
  user.payment_info
end

Then it’s actually clearer to just inline it into the method that’s calling it.

1
2
3
4
def short
  add_payment(user.payment_info)
  bill
end

Again, this stuff is gone over in much much more detail in the refactoring book. The goal of this post is just to provide a 10 second overview.

How to get started:

Choose a long method in your app that causes trouble often. Consider using Flog

Write tests for it
Break if up into specific parts
Test it
Write tests for the methods that you will create from the parts
Make those parts separate methods
Test it

February 11, 2009