Object Oriented Scraper Backed With Tests Pt. 5
Last night I got the Crawler
passing its test for #get_words_by_selector
. This morning I realize that when someone sends in a junk selector I want to raise an exception of some kind. Since I don’t know much about Ruby Exceptions I’m doing a little digging…Ruby has both throw
/catch
and raise
/rescue
so what’s the difference between throw/catch and raise/rescue in Ruby?
Throwing exceptions for control flow
There’s a great guest post by Avdi Grimm on RubyLearning which covers this topic in depth. To summarize throw
/catch
is mainly used when doing exceptions as control flow. In other words, if you need to break out of a deeply nested loop or some other expensive operation you can throw an exception symbol which can be caught someone high up the call stack. Initially this rubbed me the wrong way since I know that things like goto
and labels
are a bad practice. Someone else raised this point in the comments to which Avid responded:
There is a fundamental difference between throw/catch and goto. Goto, in languages which support it, pays no attention to the stack. Any resources which were allocated before the goto are simply left dangling unless they are manually cleaned up.
throw/catch, like exception handling, unwinds the stack, triggering ensure blocks along the way. So, for example, if you throw inside an open() {…} block, the open file will be closed on the way up to the catch() block.
Raising exceptions for everything else
With throw
/catch
out of the way that leaves raise
/rescue
to handle everything else. I’m willing to bet that 99% of error code should probably be raising exceptions and throw/catch should only be used in situations where you need the control flow behavior. With that knowledge in hand I need to decide between one of Ruby’s built-in Exceptions or defining one of my own. Let’s define one of our own so we can get that experience under our belt.
Creating an exception subclass in Ruby
One tip I picked up while doing my research into raise
and throw
is that any exception that doesn’t subclass StandardError will not be caught by default. Here’s an example to illustrate:
We’ll call our Exception SelectorError
to indicate that the provided selector did not return any results. For reference I often refer to this chart on RubyLearning when I want to see a list of all the available Exception classes. In our case we’ll just inherit from StandardError.
I don’t think we actually need to do much more than that. The ability to pass a payload message should come from the super class so I think we’re good to go. Here’s our updated spec:
Initially the test fails so now we need to update our Crawler
to check if nothing was returned and raise the custom exception.
Here’s our updated Crawler
with additional require and updated method.
All tests passing, we’re good to go :)
You should follow me on Twitter here.
- Mood: Alert, Awake, Anxious
- Sleep: 8
- Hunger: 3
- Coffee: 0