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
