These past few lessons I have felt very lost on how to really check my code. I feel that the in class version of the course would fill in these gaps, but doing it online I am struggling. When there were problems with Rspec I was able to solve the problems and make sure my code worked. In this new prompt though I don’t even know how to run it to see what errors it is coming up with.
Error Handling Funtime
Oh noes, the clever TAs at App Academy made this “super useful” library, but it keeps throwing ugly error messages that are hard to understand. Let’s revamp the library to throw more descriptive errors and prevent incorrect usage.
Learning Goals
- Know how to
raise
andrescue
an exception - Be able to explain how an exception bubbles up after it is raised
- Know when to use
ensure
andretry
- Be able to choose an appropriate exception class
Phase 1: Setup
Download the project skeleton. You will primarily be working in super_useful.rb
to improve errors in our library. The user’s script, aptly named user_script.rb
, will be using the functions and classes defined in super_useful.rb
.
Phase 2: Make convert_to_int
more flexible
Overview
Sometimes we want to return something from our function, even if the desired operation is not possible. This might mean handling potential errors in our own code and returning a suitable replacement, such as nil
or -1
(often used when trying to find the index of a particular element).
Instructions
We want our dear user to be able to call convert_to_int
with no error being raised on invalid input. Update convert_to_int
to rescue
any errors and return nil
if our argument cannot be converted.
If we are handling the error thrown by Integer(arg)
, which [ StandardError
subclass] should we be catching? Next, update convert_to_int
again to only rescue the correct exception type.
NB: rescue
only rescues StandardError
and its subclasses. Any other Exception
subclass is a system error and implies that something rather serious has gone wrong and our code should stop executing.
Recap
Many times we will want to ‘protect’ the user from potential errors our code might throw. In this case we are protecting our user from only from errors we expect. It is always wise to raise and rescue more specific errors as the errors they raise are more descriptive (helpful with debugging) and it prevents the catching of errors that should ‘escape’ up to the calling function (catching Exception
will even ignore system errors).
Phase 3: Make friendly monster (maybe) let you try again
Overview
Sometimes when an error is thrown we would like to try the failing operation again (hopefully with different input ). This is often the case with user input and text parsing. Let’s try to make friendly monster happy by allowing us to retry feeding it a fruit when certain errors are thrown.
Instructions
Friendly monster is really friendly and really likes coffee, so he’d like to give us another try, but only when we give him "coffee"
.
First, handle the errors being thrown by #reaction
in #feed_me_a_fruit
.
Note that #reaction
throws errors receiving an argument that is not in FRUITS
. Next, let’s differentiate the errors thrown so our calling function, #feed_me_a_fruit
can try to feed friendly monster again, but only when they’ve given it coffee.
Now that we have different error types being thrown by #reaction
we can do a little conditional logic in #feed_me_a_fruit
to retry
the failing block of code again, but only if it is a coffee-related error.
Recap
Being able to rescue and retry failing code gives us even more control over the flow of our program. Handling different errors separately gives us even more control.
Phase 4: Ensure BestFriend
is a real best friend
Overview
Another use case for raising errors is to enforce correct usage of code. For example, if a function requires its arguments to be of certain types in order to execute properly, it might be best to check their type before executing any logic. This is useful because it allows us to inform the user that they are not using our function properly, rather than a runtime error being raised which may seem like a bug in our code or be more difficult to debug.
Instructions
If we look at user_script.rb
, we see that our dear user thinks you can be besties if you’ve known each other less than a year. We do not agree. Friendships, like a fine wine, need as least five years to mature. Update BestFriend#initialize
, in super_useful.rb
, to raise an descriptive error when yrs_known
is less than 5
.
Test your code, then assume the role of our dear user and update our call to BestFriend#new
to create a real friendship ( yrs_known>= 5
).
Our dear user also thinks it’s okay to leave name
and fav_pastime
empty when creating a new instance of BestFriend
. But it’s not okay. It leaves #do_friendstuff
and #give_friendship_bracelet
sorely lacking. Poorly formatted text just makes us seethe with displeasure. Update the initialize
method to raise descriptive errors when given strings of length <= 0
.
Test your code, then again assume the role of our dear user and update our call to BestFriend#new
.
Recap
Raising errors for invalid arguments can ensure that our code is used the way we want intend. However, be aware that the types of inputs we can receive are numerous. We don’t want or need to be checking against every possible type for each argument we receive.