Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fail vs. raise in Ruby : Should we really believe the style guide?

Ruby offers two possibilities to cause an exception programmatically: raise and fail, both being Kernel methods. According to the documents, they are absolutely equivalent.

Out of a habit, I used only raise so far. Now I found several recommendations (for example here), to use raise for exceptions to be caught, and fail for serious errors which are not meant to be handled.

But does it really make sense? When you are writing a class or module, and cause a problem deep inside, which you signal by fail, your programming colleagues who are reviewing the code, might happily understand your intentions, but the person who is using my code will most likely not look at my code and has no way of knowing, whether the exception was caused by a raise or by fail. Hence, my careful usage of raise or fail can't have any influence on his decision, whether she should or should not handle it.

Could someone see flaws in my arguments? Or are there other criteria, which might me want to use fail instead of raise?

like image 976
user1934428 Avatar asked Aug 11 '15 09:08

user1934428


2 Answers

use 'raise' for exceptions to be caught, and 'fail' for serious errors which are not meant to be handled

This is not what the official style guide or the link you provided say on the matter.

What is meant here is use raise only in rescue blocks. Aka use fail when you want to say something is failing and use raise when rethrowing an exception.

As for the "does it matter" part - it is not one of the most hardcore strictly followed rules, but you could make the same argument for any convention. You should follow in that order:

  1. Your project style guide
  2. Your company style guide
  3. The community style guide

Ideally, the three should be the same.


Update: As of this PR (December 2015), the convention is to always use raise.

like image 108
ndnenkov Avatar answered Nov 05 '22 05:11

ndnenkov


I once had a conversation with Jim Weirich about this very thing, I have since always used fail when my method is explicitly failing for some reason and raise to re-thrown exceptions.

Here is a post with a message from Jim (almost verbatim to what he said to me in person): http://www.virtuouscode.com/2014/05/21/jim-weirich-on-exceptions/

Here is the relevant text from the post, a quote attributed to Jim:

Here’s my basic philosophy (and other random thoughts) on exceptions.

When you call a method, you have certain expectations about what the method will accomplish. Formally, these expectations are called post-conditions. A method should throw an exception whenever it fails to meet its postconditions.

To effectively use this strategy, it implies you must have a small understanding of Design by Contract and the meaning of pre- and post-conditions. I think that’s a good thing to know anyways.

Here’s some concrete examples. The Rails model save method:

model.save!
-- post-condition: The model object is saved.

If the model is not saved for some reason, then an exception must be raised because the post-condition is not met.

model.save
-- post-condition: (the model is saved && result == true) ||
                   (the model is not saved && result == false)

If save doesn’t actually save, then the returned result will be false , but the post-condition is still met, hence no exception.

I find it interesting that the save! method has a vastly simpler post-condition.

On the topic of rescuing exceptions, I think an application should have strategic points where exceptions are rescued. There is little need for rescue/rethrows for the most part. The only time you would want to rescue and rethrow is when you have a job half-way done and you want to undo something so avoid a partially complete state. Your strategic rescue points should be chosen carefully so that the program can continue with other work even if the current operation failed. Transaction processing programs should just move on to the next transaction. A Rails app should recover and be ready to handle the next http request.

Most exception handlers should be generic. Since exceptions indicate a failure of some type, then the handler needs only make a decision on what to do in case of failure. Detailed recovery operations for very specific exceptions are generally discouraged unless the handler is very close (call graph wise) to the point of the exception.

Exceptions should not be used for flow control, use throw/catch for that. This reserves exceptions for true failure conditions.

(An aside, because I use exceptions to indicate failures, I almost always use the fail keyword rather than the raise keyword in Ruby. Fail and raise are synonyms so there is no difference except that fail more clearly communcates that the method has failed. The only time I use raise is when I am catching an exception and re-raising it, because here I’m not failing, but explicitly and purposefully raising an exception. This is a stylistic issue I follow, but I doubt many other people do).

There you have it, a rather rambling memory dump on my thoughts on exceptions.

I know that there are many style guides that do not agree (the style guide used by RoboCop, for example). I don't care. Jim convinced me.

like image 13
Karl Wilbur Avatar answered Nov 05 '22 05:11

Karl Wilbur