Lia.Skalkos

Life in code.

Minority Report:
Ruby Exceptions & Errors

Not too long ago in one of our labs at Flatiron, we were asked to write some code that made this test pass:

it “should raise an error for invalid types” do expect { RPSGame.new(:dynamite) }.to raise_error(RPSGame::PlayTypeError) end

After puzzling over this for a little, and reading some documentation, I got the test to pass by writing the following code:

class RPSGame attr_reader :type

class PlayTypeError < StandardError
end

def self.valid_types
  @@valid_types = [:rock, :paper, :scissors]
end

def initialize(type)
  @type = type
  raise(PlayTypeError.new) if !self.class.valid_types.include?(@type)
end

end

What was this neat little piece of code doing? Basically, raising an error if the user tried to enter an invalid action. I thought it was interesting, but soon moved on to other parts of the lab. However, I never forgot about the interesting little piece of code I had encountered. Ruby exceptions still feel inherently different to me than other parts of the language. They seem to be a little exotic, like rare birds in forest full of, I don’t know, carrier pigeons. I guess this feeling makes sense, since exceptions tend to only come into play when things in our program go wrong. Also, there are a couple of special methods associated with them that not every class of object has. So, with that little intro here are 7 things that are helpful in understanding exceptions:

  1. First and foremost, exceptions are Ruby objects.

    Like nearly everything in Ruby, exceptions are objects. This is great in that they can contain information about themselves, and can have a real presence within our programs. They can be passed around and instantiated whenever necessary.

  2. Exceptions are for exceptional circumstances.

    You probably don't want to use an exception in place of a well thought-out program and appropriate logic constraints. Say, in an application that already has built-in form/params logic, trying to guide the user towards a different input type. Instead, you probably want to make use of exceptions where it's reasonable that an unlikely but highly detrimental situation to your program could occur. It's nice here to spend some time thinking about what kind of tool exceptions are and where they would work best.

    Imagine we have an app that uses a 3rd party API. What happens if the connection times out, or the 3rd party's server is down, or for whatever other reason, their DNS isn't working? It's unlikely to happen, but because our application has this dependency, it's reasonable to have a contingency plan. This use case is a good example of where exceptions make a good fit.

  3. Exceptions can be raised.

    The raise method is from the Kernel module. By default, raise will create an exception of the RuntimeError class. To raise an exception of a specific class, you can pass in the class name as an argument.

    raise "Here is a regular RuntimeError"
    raise StandardError.new("Here is a StandardError")

  4. When an exception is raised, it halts the program.

    As a Ruby programmer, you encounter this on a day-to-day basis. Here's an example:
    "10" + 3
    TypeError: no implicit conversion of Fixnum into String
    There's no way to continue from here. Basically, this is where our program has ended up.

  5. Exceptions are associated with begin/rescue blocks.

    Begin/rescue blocks function a lot like if statements, in that if an exception is thrown in the "begin" part, you can rescue from it and essentially branch your program. Unlike an if statement, however, they can be a little more granular about what type of exception to rescue from.

  6. Exceptions have methods.

    This ties into #1. They basically are backtrace, message, and inspect.

  7. Make sure you specify the Exception type you are rescuing from.

    Probably the most important point for last. Errors inherit from the class Exception.


    As you can see, Exception has a big family tree. When you rescue from an Exception, you want to be as specific as possible about the type, so that you don't accidentally rescue from an exception you weren't anticipating.

    This wouldn't be fun, if say, you were trying to rescure from a connection error, and instead rescued from a NoMethodError. You have then unintentionally branched your program into the wrong type of logic.

Sources:
http://daniel.fone.net.nz/blog/2013/05/28/why-you-should-never-rescue-exception-in-ruby/