Exceptional Errors
DRAFT WIP - Raw Draft
( Pic - Lemur with dynamite plunger )
Sooner or later your program is going to go boom. That's just a fact of programming, doesn't matter if you're brand new or someone like Matz. Bugs and breaks happen, so we need to get you ready to deal with them.
Without a Trace
( Pic - Phantom object missing, Lemur trying to grab )
One of the more common ones you'll find is the NoMethodError
:
2.4.2 :001 > nil.method_that_doesnt_exist
NoMethodError: undefined method `method_that_doesnt_exist' for nil:NilClass
from (irb):1
from /Users/lemur/.rubies/ruby-2.4.2/bin/irb:11:in `<main>'
( P - Questioning Lemur, or not there Lemur )
As the name implies, there's no such method there! What we just got for output is called a stack trace, and it's telling us that the error happened on line 1 of our IRB session.
What happens if we try it with a method? Let's say we have an add method:
def add(a, b)
a + b
end
What happens if we call it with a nil
?
2.4.2 :006 > add(1, nil)
TypeError: nil can't be coerced into Integer
from (irb):5:in `+'
from (irb):5:in `add'
from (irb):6
from /Users/lemur/.rubies/ruby-2.4.2/bin/irb:11:in `<main>'
Turns out we can't call +
with a nil
because a Number has no idea of how to add nil
to something. Notice in this trace how it tells us that the error happened in the add
method as soon as +
was called. Because there was more than one step to get here it shows us what it did beforehand.
( Pic - Square in a round hole )
Really though you can't call a lot of things with nil
which is why most programmers try and avoid using it when possible.
( Pic - Glitter Lemur flipping out emerging from pool of glitter )
nil
is everywhere and it gets into everything. It's like the glitter of programming, and there's no getting rid of it. I used glitter to make that picture and I'm still trying to get it out of the carpet by my scanner, no fun indeed.
Reasonable Defaults
Programmers prefer to use what are called reasonable default values. That means that when returning from a method they want a value that makes sense when they got nothing, and nil
is not a very reasonable default in most cases.
Consider select
. When nothing is found it returns an empty array, which is a reasonable result considering nothing was found. If you returned nil
from it you wouldn't be able to chain any other methods on it afterwards.
Getting a Raise Out Of You
( How do we raise an error, why do we? )
Some times we want to raise an error ourselves to warn people that something went really wrong and we don't quite know how to fix it.
All we do to raise an error is, quite simply, to use raise
:
2.4.2 :007 > raise 'an error'
RuntimeError: an error
from (irb):7
from /Users/lemur/.rubies/ruby-2.4.2/bin/irb:11:in `<main>'
( Pic - Horrified Lemur )
Now your program will raise an exception right there and stop the whole thing. Good thing is our REPL won't go and exit on us if we do that, because it knows how to rescue
, but that's for next section.
So where do we choose to raise an exception? Consider our find function from earlier:
def find(array, &block)
array.each { |n| return n if block.call(n) }
nil
end
If we don't find
something, is that cause for us to break the entire application? Well in this particular case it depends on who's calling it.
( Pic - Dynamite Lemur vs Sherlock Lemur )
Sometimes we'd want to raise an error, but applying that to the general case doesn't make much sense. Instead we chose to return nil
and let the caller decide what they want to do with it.
... ( Need to fill out rest, explain to avoid raising unless necessary. Too many use it for flow control instead of actually exceptional cases )
To the Rescue
So now we have an error, great. Once it came up, our program crashed. Not so great. How do we stop that from happening?
We rescue it! Now where to begin:
begin
raise "a mess"
rescue
"Phew! We're safe"
end
( P - Rescue Lemurs )
Begin starts a section we can rescue, and like other expressions in Ruby it returns a value:
value = begin
1
end
It doesn't have to have a rescue on it, but if it does that can also return a value:
value = begin
raise "a mess"
rescue
"Other value"
end
Now we can do this in a method too:
def method
begin
raise "a mess"
rescue
"Safe!"
end
end
but Ruby is clever, it has a few shortcuts:
def method
raise "a mess"
rescue
"Safe!"
end
You can rescue an entire method and imply the begin!
If you really wanted to you could even inline it:
value = method rescue "value if exception was raised"
Debugging with a Crowbar
( Introduce Pry )
( Pic - Lemur hand with crowbar )
Doctor Documentation
( Using Pry to read documentation of certain methods or other pieces of Ruby )
( Pic - Doctor Lemur reading book )
At the Source
( Using Pry to view some of the source around errors )
Step by Step
( How to breakpoint, step, next, debug, etc )