Exceptions in Ruby
Why do we do exception handling? Sometimes things don’t execute the way they are designed to, for reasons outside of our code’s responsibilty. Maybe your code relies on something external, for example, scraping, an external server, an API. If your code uses external input, exceptions are a way of handling any exceptions to the expected outcome.
In many ways, exceptions are similar to conditionals. The exception handling block in Ruby, the begin...rescue
block, is not unlike an if...else
conditional statement; they both control flow. Exceptions account for the possibility of something happening and accounting for that, not unlike conditional statements.
Let’s look at a simple example method in Ruby:
1 2 3 |
|
When we run plus_one("22")
, as you can guess, we’re going to get the following error returned:
1
|
|
If we wanted to avoid that TypeError
from happening, which would break our program at runtime, we could write a conditional that expects input that’s not a number and sanitizes it:
1 2 3 4 5 6 7 |
|
But what if you wanted to know about that error, not just go ahead and fix it? Or maybe you really cannot fix the input yourself, because it’s coming from somewhere else. Or, most commonly, you just need to handle the error and keep things running.
That’s where exception handling comes in:
1 2 3 4 5 6 7 |
|
The begin...rescue
block works as such that when begin
is triggered, it’s going to try to execute whatever code is in that block, and, if for whatever reason it can’t (for example, a TypeError
), it’s going to fall down to the rescue
block and execute the code in that block instead. This way, our program won’t crash if there’s an error.
Our contrived example doesn’t really lend itself to anything other than what it is, but the fact that exception handling prevents our program from crashing is very important. If our code relies on data that is external, we don’t have full control over it. It can change. That’s the power of exception handling: our program will not break if what it relies on changes.
So our begin...rescue
block is pretty cool, but we can take exception handling even further if we need to.
In our above begin...rescue
block, num += 1
will only be called if an error isn’t raised during its execution. What if, regardless of that code being run, we want something else to always execute? That’s where the ensure
block comes in.
1 2 3 4 5 6 7 8 9 |
|
Ensure happens after both the begin
and rescue
blocks are ran, and will always execute, hence the name ensure.
Exceptions like begin...rescue
are great for just that, throwing exception messages. While they act like conditional expressions, they really should never be used for managing conditional flow. They should be reserved for providing information when something went wrong, handling what happens when that error occurs, and keep things flowing. The reasoning for this isn’t a stylistic choice: figuring out where and why something went wrong takes time. Exception handling is slow, and conditional flow, part of everyday development, should be fast.
Up next, I’ll talk about another option for programmatic flow in Ruby: try...catch
.