29 Jul, 2007

Published at 06:38PM

Tagged with exceptions, programming, rails, and ruby

This post has 20 comments

Raising custom exceptions in Rails

I’ve grown to not like the if/else block at all. Here’s an example of what I’m not fond of:

1
2
3
4
5
6
7
8
9
10
11
12
13
def do_some_work
  unless feeling_lazy?
    if current_user.is_boss
      do_boss_work
    else
      do_work
    end
    flash[:notice] = "Glad you're working hard."
  else
    redirect_to lazy_page_path
    flash[:error] = "You're too lazy to work here."
  end
end

The if/else statements just clutter the code. It disguises the logic by its ugliness. When appropriate, I’ll avoid if/else by raising exceptions to handle the “else” condition. That way, I can assume everything works how I want it to, and just raise if it doesn’t. Something like this is much better, in my opinion…

1
2
3
4
5
6
7
8
def do_some_work
  raise if feeling_lazy?
  current_user.is_boss ? do_boss_work : do_work
  flash[:notice] = "Glad you're working hard."
rescue
  redirect_to lazy_page_path
  flash[:error] = "You're too lazy to work here."
end

That’s much easier on the eyes. But I like to raise application-specific exceptions instead of the generic “rescue everything” solution. The way I’m currently doing this feels a little, I don’t know, sloppy. What I usually do is put a separate module in the lib folder, then include it in ApplicationController so it’s available everywhere. Something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
# lib/exceptions.rb
module Exceptions
  class TooLazyToWork < StandardError; end
  class NotTheBoss < StandardError; end
end

# using the custom exceptions
def do_some_work
  raise Exceptions::TooLazyToWork if feeling_lazy?
  ...
rescue Exceptions::TooLazyToWork => exception
  flash[:error] = "You can't work: #{exception.message}"
end

I’m not doing anything with the class except inheriting from StandardError (or Error, RunTimeError, etc)—that’s what feels sloppy. Is there a better way to achieve this? If you notice something that could be (or should be) done better (or differently), I’d appreciate the advice.

Comments

Ahsan Wednesday, 22 Aug, 2007 Posted at 01:53AM

I use the same approach … I just wanted to note that you can save your self some typing by adding include Exceptions to the controller, and just do:

raise TooLazyToWork

Ryan Wednesday, 22 Aug, 2007 Posted at 04:23AM

Alright, thanks… I actually am including the module in the corresponding controllers, I just left that part out.

Glasheen Tuesday, 03 Feb, 2009 Posted at 09:27AM

This post was helpful – thank you.

andy Thursday, 16 Apr, 2009 Posted at 11:59AM

Exceptions are called exceptions for a reason, they should only happen during “exceptional” conditions and should NEVER be used as normal flow control.

Exception handling adds a ton of overhead to the code. Your examples went from a predictable if/else block (certain processors will be able to ‘guess’ the right jump to make, meaning there is no delay) to something that takes many more cycles.

Ryan Thursday, 16 Apr, 2009 Posted at 12:37PM

Andy -

I haven’t verified (or realized) the extra overhead that exception handling causes, so I’ll have to take your word for it. However, it’s irrelevant to me. Exception handling in this manner is a cleaner way to write code, and to me, cleaner, more readable code will always trump those extra nano seconds.

Besides, if I were that concerned with speed and optimization, I probably wouldn’t be using a dynamic language like Ruby in the first place :-)

andy Saturday, 18 Apr, 2009 Posted at 12:33PM

I hear you about the dynamic language part, but I want to stress that this isn’t an order of nanoseconds we’re talking about. Sure, if this is an oft-unused code path we’re talking about, you can get away with it, but if this is in a portion of code that gets hit a lot, then we’re talking orders of magnitude slower.

For example, I wrote a simple app that follows your two examples above, both count the number of even numbers between 0 and 100,000. below are the ‘time’ results:

> time ruby if.rb
Found 50000 even numbers

real 0m0.065s
user 0m0.036s
sys 0m0.008s
-———————————-
> time ruby ex.rb
Found 50000 even numbers

real 0m1.110s
user 0m0.540s
sys 0m0.040s

as you can see, the code that uses exceptions takes over 17 times longer! just another thing to think about in this wonderful world of coding :)

Luke Friday, 15 May, 2009 Posted at 10:50AM

prolly better to go with andy’s approach on this one. “It disguises the logic by its ugliness” is purely subjective here. more lines of code doesn’t always equate to ‘ugly’. sometime, more is more. don’t let Ruby spoil you to the point where you completely disregard performance and clarity of purpose in your code.

anyway, good post. it takes some gusto to put your code out in the world and ask people to comment…

Paul Friday, 26 Jun, 2009 Posted at 01:34AM

Your “we hate IE6” code is borked!!!! I’m using IE8 and it gave me hate messages :(

Ryan Friday, 26 Jun, 2009 Posted at 09:07AM

Hmmm… thanks for the heads up. Must be something with IE8 that confuses jQuery.

Walt Gordon Jones Tuesday, 21 Jul, 2009 Posted at 07:56PM

I realize this is an older post, but I thought i’d mention… In Matz’s recent Google Tech Talk on Ruby 1.9, he specifically warns that perf in exception handling (for 1.9) has been sacrificed to some extent in favor of gaining better perf in the normal code path.

http://www.youtube.com/watch?v=oEkJvvGEtB4

His almost exact quote was: “This makes sense because exceptions are exceptional.”

I definitely use patterns like “rescue nil” that maybe abuse the intent of exceptions a little bit, but it’s good to be thoughtful in hot code paths.

Akira Wednesday, 22 Jul, 2009 Posted at 01:51AM

“Besides, if I were that concerned with speed and optimization, I probably wouldn’t be using a dynamic language like Ruby in the first place :-)”

I really don’t understand Rails guys, javascript guys make code looks like a giant one line snake to get better performance, web design guys push the limits of image quality to get better performance, but Rails guys…they want to code less and don’t bother if the page will took more to load? I have a teacher that will throw my work if the code looks like a spiral stairway, but when i graduate and got into work in web development…they don’t want clean code, they want performance or all companies in the world would need to use blue genies to serve all the apps out there.

Jarrod Friday, 18 Sep, 2009 Posted at 10:23AM

Akira… what is more expensive in the “real world” – another box in the cluster or another developer sitting at a desk? They don’t want to “code less” they want to code BETTER. You exhibit the typical “know it all” attitude from a overly eager college student who has not ever had a real job in the software industry and does not understand the MASSIVE overhead of maintaining bad, hard to read code. Rails developers care about “coding less” because that is usually the cleanest and most maintainable way to go about things.

I am sure there are companies out there who don’t place value on code quality – simply on performance, but those places usually resemble dilbert’s office and is no where i would want to spend my waking hours.

Jen Thursday, 24 Sep, 2009 Posted at 05:05PM

Jarrod – I think your argument is way off and makes you come across as being very defensive. In all actuality, I’ve been working with Rails since the get-go and can tell you there are many ways to achieve both clean code and efficiency. Somewhere along the line a lot of Rails developers (probably those that came from PHP or no programming background at all) decided that since certain things you could do with ActiveRecord might be more readable, that it’s okay to ignore standards or care less about optimization. For instance, I look at Rails apps that others have developed all the time where they don’t use :include on their finders to eager load associations. It probably has the largest inverse proportion of ease-of-implementation to benefit-of-use of any AR utility out there. Super simple, requires no ugly coding mess, yet reduces number of queries exponentially. So I do have to agree that a lot of Rails developers (not all, of course) completely for-go the concept of optimization because they don’t take the time to fully understand the tools they’re using, and that is a cause for concern no matter how sleek a person’s code may “look”.

Ryan Thursday, 24 Sep, 2009 Posted at 06:33PM

I still tend to use this pattern occasionally. I realize (mainly thanks to Andy in comment #6) that this can indeed slow down code execution. And I would completely consider an alternate route if it came down to the point where I had to squeeze every last bit of performance out of an application.

But, typically, I’m not to that point with any of the applications that I work with.

From my experience, if I set out to improve performance on any Rails application, it always seems to start at the db level with query optimization and ensuring things like proper use of :include (as Jen suggested in comment #13). Then I typically review how I’m caching things, and improve upon that if need be.

I appreciate the awareness of how costly something like exception handling in this manner can be, but I honestly can’t say that I’m willing to change those types of things without first tackling performance issues in other areas. I’m not trying to be ignorant about the issue, it’s just at matter of preference. And those who disagree will always have the satisfaction of knowing that their app will be that much faster than mine :-)

I think the “more readable code vs. performance” argument will never be resolved, and surely not here within these comments. But nevertheless, it’s interesting to hear other perspectives on the matter.

Navin Samuel Sunday, 11 Oct, 2009 Posted at 12:14PM

Code readability is as important as performance and one should try to find the perfect balance between the two. The if/then/else loop can always be restructured to get cleaner, faster executing code and should not be discarded just to make your code look prettier. As other readers have pointed out, the usage of exceptions as a getaway has serious performance consequences and should be avoided as much as possible. “Exceptions” should be exceptions. Period.

Alex Cox Tuesday, 16 Mar, 2010 Posted at 06:43PM

Personally I think the use of an exception makes it harder to follow the logic when reading the code for the first time in this case. In a way it’s a nice idea, and I really appreciate the desire to get more concise and readable. But concise, readable code isn’t just shorter or written on fewer lines. It actually relects logical elegance, in exactly the same way that a good essay would. If your thinking can be made clearer in the code, that makes it easier to maintain as well. Given the code example above, something like:

def do_some_work
  if !feeling_lazy?
    do_work_for(current_user)
    flash[:notice] = "Glad you're working hard." 
  else
    redirect_to lazy_page_path
    flash[:error] = "You're too lazy to work here." 
  end
end

where the user-specific logic is moved into another method that wraps the functionality of the other two, is much easier for me to parse because it combines three things that go together (those two ‘work’ methods, and the user that is associated with them) into one semantic space. Furthermore, the two ‘work’ methods are likely to contain opportunities for code deduplication as well, given their titles and association with the user object.

Jay Saturday, 15 May, 2010 Posted at 09:31AM

Hi, Where do I add additional error comment in exceptions.rb?

The code returned error page alright, but I would like to show more details to the error. E.g.

Exceptions::TooLazyToWork get back to your cubicle now!

Ryan Saturday, 15 May, 2010 Posted at 11:22PM

Jay -

When you rescue an error you’re rescuing an instance of that error, and like any instance of any object, it has methods. That said, you could do something like this:

1
2
3
4
5
6
7
module Exceptions
  class TooLazyToWork < StandardError
    def message
      "Get back to your Cubicle!"
    end
  end
end

Then you could have something like this:

1
2
3
4
5
6
7
8
begin
  raise Exceptions::TooLazyToWork if feeling_lazy?

  # do some other stuff

rescue Exceptions::TooLazyToWork => e
  flash[:error] = e.message # "Get back to your Cubicle!"
end

Does that solve your problem?

Graham Ashton Friday, 02 Dec, 2011 Posted at 06:54AM

The Ruby community totally have the wrong end of the stick with exception handling. It’s thought of as an anti-pattern, which is utter cobblers.

In the context of the code presented in the post Andy’s points in the comments are irrelevant. In order to be able to measure a difference between if/else and and exception handling you need to be running the exception handling code at least 1000 times while running a single request.

I wrote about the cost back in 2009 (see http://effectif.com/ruby/cost-of-exception-handling), in order to highlight the fact that exceptions aren’t cheap if you call them a lot. In that post I caught an exception 1 million times and saw a script that took 6 seconds to run. Do the maths; handling a couple of exceptions is going to cost you, er, yeah right.

If your code raises and catches exceptions many thousands of times during a request, the point becomes valid, but that is a relatively rare occurrence. I’ve only encountered that happening once in five years of Rails programmer (and I wrote the blog post above as a result).

If you want to learn more about the pattern Ryan used above (and you should, it’s excellent), google for EAFP, or read about Python exception handling (http://en.wikipedia.org/wiki/Python_syntax_and_semantics#Exceptions). Follow the links to Grace Hopper, and think about the context in which your code is running before coming out with daft generalisations like “exceptions … should NEVER be used as normal flow control”. Context is everything. Ryan’s code is really clean.

Ryan Tuesday, 06 Dec, 2011 Posted at 05:19PM

@Graham Ashton -

Thanks for the response, I appreciate it. And over the years, my findings have been exactly the same. I’m not willing to trade the cleanliness (in my opinion, at least) of handling exceptions this way for picking up an extra nanosecond here and there.

Do you have something to say about this post?
Retype the image to the right Spam Hint: Are You Human? Textile Formatting Tips

or

Ryan Heath | Site Management A Ruby on Rails production.

This site is a Formed Function. Formed Function LLC | @formedfunction | Get in Touch