00:00:17.240
I am obdi and this is a talk about
00:00:22.480
failure hopefully it is a not a not a failure of a talk so to begin with uh I want to uh
00:00:31.240
Define some terms what do we mean by failure one way to look at failure is
00:00:38.440
from the uh the design by contract uh view of programming uh this
00:00:45.640
is the the view of programming that was espoused by bertr mayor uh he kind of encoded it into a programming language
00:00:51.360
called eiel and this is relevant to us because Ruby's exception system is in
00:00:56.719
many ways influenced by Eiffel so in the the design by contract view of
00:01:04.360
software every method has a contract it may be an implicit contract or it may be
00:01:10.200
an explicit contract but it has a contract with its caller saying uh you will give me certain inputs I will give
00:01:16.799
you certain outputs failure is when code is unable
00:01:23.360
to fulfill that contract uh and this could happen for a
00:01:28.560
number of reasons the uh the the codee the method could simply be called with
00:01:35.079
invalid arguments this is technically a failure of the caller but it might be the method's job to report it could be a
00:01:41.960
simple mistake in the code like using a a string key when when a symbol key was
00:01:49.759
expected it might be a case that was simply um not planned for uh when when
00:01:56.759
designing the software or it could be some failure in
00:02:01.840
a system that's completely external to this software that it has no control over like a a web service returning a
00:02:09.119
500 a I want to start off by just taking you
00:02:14.599
on a whirlwind tour of Ruby's exception handling life cycle and uh I'll go
00:02:20.120
through some things you probably know and maybe some things that you don't know so it all starts uh with raising an
00:02:27.120
exception every exception starts with called to raise or to fail raise and fail are synonyms
00:02:33.319
in Ruby uh in recent years raise I think has become more common more commonly
00:02:38.760
used than fail but uh I was I was discussing this with Jim wrick uh and he
00:02:45.800
has an interesting convention where uh he actually uses fail primarily so he uses fail to indicate a failure of the
00:02:53.480
code and then he only uses raay to when he is re raising an exception that he
00:02:59.680
has has previously rescued so I kind of like this convention I've started to use it a little bit more in my own
00:03:06.440
code uh raise with no arguments equivalent to raising a um a runtime
00:03:12.799
error uh raising we can raise of course with a spring which we'll raise a one time error with that string as this
00:03:18.040
message we can raise uh with an explicit error class and uh uh we can also raise uh with we can
00:03:27.080
supply another argument to customize the back Trace uh when we uh when we raise
00:03:33.439
an exception and this is useful when writing things like assertion methods
00:03:38.760
because we want that stack Trace to point back to where the assertion failed not where the assert method was
00:03:47.959
defined now something uh one thing uh some people don't realize about rays in
00:03:53.000
Ruby is that it is a method it is not a keyword so um and that means that like
00:03:58.519
any method in Ruby we can redefine it so here's kind of a stupid example uh
00:04:04.959
we could make all exceptions instantly
00:04:10.360
fatal uh a possibly more useful uh if reimplementation of rays uh is the
00:04:17.359
Hammer Time Jam that I wrote uh so this attempts to be a list or or small talk
00:04:23.600
style uh exception or error console for Ruby so when an exception is raised
00:04:28.720
instead of just being just just unwinding the stack it'll it'll instantly pop up a interactive console asking you if you want to if you want to
00:04:35.000
proceed if you want to ignore the errors if it never happened if you want to debug uh into the source and stuff like
00:04:42.960
that what does raay actually do well it it turns out that raise breaks down into
00:04:49.160
four steps so it goes through uh it first it gets the exception object then
00:04:54.240
it sets the back Trace uh then it sets the global error info variable and
00:04:59.840
finally it uh it it starts unwinding the fal stack let's take a look at these in a little bit more detail uh first of all
00:05:07.160
getting the acception object when you look at the way Rays is called you might think that it's it's defined kind of
00:05:14.400
like this first example where you say uh you know you say raise an exception class in a message so you think okay
00:05:19.800
it's probably saying uh that class do new and then providing the meth the message not actually what ruby does what
00:05:26.160
ruby does is it calls whatever you give it it calls the dot exception method on
00:05:31.639
that object to get the exception object and as it turns out in Ruby uh this
00:05:37.120
method is is defined in two places it's defined on the exception class and it's
00:05:42.400
defined on instances of the exception class but just because Ruby only defines it in one place doesn't mean we can't
00:05:48.520
Define our own here's an example of that uh where we we take uh HTP responses and
00:05:55.919
we give them the ability to turn themselves into exceptions uh which could be you know could be
00:06:01.800
customized a customized exception for that particular type of response and then client code which which can call
00:06:07.840
can do an HTTP request and then uh it can decide whether it wants to to raise
00:06:12.919
that that response as an exception and it doesn't have to explicitly call uh anything to convert it into an exception
00:06:18.960
because raise does that automatically so you can kind of think of exception as similar to 2s or 2 a or 2 I it's kind of
00:06:26.800
like the coercion method to uh to coer an object into an
00:06:32.639
exception step two in Rays is setting the uh back Trace which it does with the set back Trace method on um on
00:06:42.080
exception step three we it sets the global error info variable so this
00:06:47.720
variable is is spelled dollar bang out of the box but if you require the
00:06:53.880
English uh library then it's Alias to the somewhat more readable error info
00:06:59.599
Global This Global always contains the a reference to the currently active
00:07:06.080
exception if any so what this piece of code demonstrates is that prior to
00:07:11.680
raising an exception the error info variable is nil during the raise of that exception
00:07:18.000
so when we raised it and then we're res we're rescuing it the uh variable is the
00:07:23.560
that variable is set to that exception the exception that was raised and then after we've handled the exception after
00:07:29.080
the end of the rescue block then that variable is nil once again and step four
00:07:36.479
uh in raising an exception is to start unwinding the call stack and uh I'm not going to get into the technical details
00:07:42.400
of that once we've raised an exception we
00:07:47.599
handle it with rescue rescue with no arguments uh rescue with no arguments will catch any
00:07:53.000
exception right right uh or wrong rather with no
00:07:59.879
arguments will C capture a anything descended from standard error this
00:08:04.960
leaves out a whole slew of exceptions and this is this is a common like uh point of beginner confusion because uh
00:08:11.159
things like no memory error things like the load error not implemented error and a whole bunch of others uh will go flying right by a be
00:08:18.400
rescue uh you can give you can give rescue just a name but no class it'll assign the exception the uh the
00:08:25.919
exception to that variable you can of course Supply class to to say what class
00:08:30.960
of Errors you want to catch you can also Supply a list of classes uh to say uh
00:08:36.279
catch any of these now when you look at the
00:08:43.080
syntax of a rescue Clause uh it it looks a little familiar it looks kind of like
00:08:50.200
um looks kind of like a case Clause right um and this is not a coincidence
00:08:56.080
Ruby's Logic for matching an exception with a class is actually very similar to
00:09:02.959
its case matching uh it's its case matching logic which if you know how case works it uses the three equals
00:09:09.880
operator you know three equals in a row to do its its matching uh exceptions are matched the exact same
00:09:17.160
way this is interesting knowledge because it means that we could theoretically do uh Dynamic exception
00:09:24.880
matchers which aren't just classes but which are you know which implement the the threequels to do some logic and uh
00:09:31.720
and and just provide those instead of instead of classes now there's one little hiccup in this Ruby has a kind of
00:09:38.519
arbitrary requirement that whatever you pass to rescue must be a class or a module it can't just be any arbitrary
00:09:44.800
object but as long as it's a class or a module uh it just has to implement the three operator so here's some code which
00:09:51.000
generates an anonymous module and uh and sticks a threequels operator on it which
00:09:56.519
uh which is based on some which is based on some uh some pattern matching code and it matches on on the message and now
00:10:02.600
we can say rescue errors with message and rescue any error whose me whose
00:10:08.839
message matches that regular expression uh we there we also have an
00:10:15.120
Ure clause which is where we put code that will always be executed whether an exception is raised or not this is a
00:10:20.760
good place to put cleanup code now Les Hill has documented an interesting uh
00:10:25.880
Corner case with Ensure if you have an explicit return return inside your return your insure clause and an
00:10:32.720
exception is raised that exception will be thrown away without a trace uh it'll just it'll return normally so probably a
00:10:39.320
good idea to just avoid having explicit returns inside your inore Clauses Ruby is one of the few languages
00:10:46.279
that gives us a retry capability so uh here's a really simple demonstration of
00:10:52.160
retry we Define a tries counter uh then we we increment it we try to do some
00:10:58.880
operations which may raise an exception and uh and then in the rescue
00:11:04.320
we look at the tries counter if it's under three we try again we try again by saying retry and what that does is that
00:11:09.800
throws execution back up to right after that uh that first begin block and uh
00:11:15.600
and then tries again and then after three tries uh this code gives up now you could do the same thing uh with a
00:11:23.279
with an explicit Loop for tries but what I find is when I have code that I've already written which hits some kind of
00:11:30.160
external buggy external service or something like that and I realize after the fact that it's that this service
00:11:38.160
that is hitting is unreliable it's often easier to put some retry logic in than to rewrite that code uh with a with a
00:11:46.600
loop what happens when we raise an exception while handling another
00:11:53.200
exception depends on how raise is called so if you call it with uh with a Brand
00:11:58.760
New Me message then uh it will it will just
00:12:04.040
substitute the new exception the new exception will be raised the old one will be thrown away there is no way to
00:12:10.320
get an a reference to the original exception and this tends to complicate debugging a lot uh because you'll you'll
00:12:17.120
Trace back to the origin of an exception and then you'll disc discover that it was raised while handling another
00:12:22.880
exception and you have no idea what that that uh original exception was so please
00:12:30.000
avoid doing this instead use nested exceptions nested exception is simply an exception which carries with a reference
00:12:36.079
to the original exception if any Ruby doesn't have these out of the box but it's very very easy to Define your own
00:12:42.279
here's here's an example it just uh defines a it has a slot in it for the original and you can pass that original
00:12:49.199
that original error in when you're raising it so um we' we've been a little
00:12:55.040
bit clever though with this definition of of a ased exception because if you
00:13:01.160
look at the second example we're raising we're raising the a second a second
00:13:07.680
error but we're not explicitly providing the original to it but it'll actually pick up the original
00:13:14.480
anyway the way it does that is we've said that the default value for that original attribute is dollar bang it's
00:13:21.920
the error info variable which as you recall it always holds a reference to the currently active exception so this
00:13:27.880
way it kind of match you know magically picks up from the environment the exception that was active when it was
00:13:36.639
raised you can rease uh you can re simply rease the exception that you rescued this will throw the exact same
00:13:44.240
object um up the call stack without changing it anyway you can supply a uh a new method
00:13:52.000
you can supply the original exception and a new message and this actually creates a new exception object it doesn't set the message uh it just
00:13:58.360
creates a new exception object but it duplicates that old object so any kind of state that the old object had with it is going to be is going to be duplicated
00:14:05.040
and copied it just uh changes the message on the new one this is actually kind of useful uh
00:14:11.480
so imagine a case where you have a data file and you're loading some data in by
00:14:17.560
it's the data file is just lines of Ruby code and you're loading it by going going through the file line by line
00:14:23.800
evaluating the line and assigning the result to to an right index let's say there's a error some in that file here's
00:14:31.040
the here's the error you get says compile you know syntax error compile error uh and it's just it's just
00:14:39.639
pointing to the first line of that inval statement no idea where in the actual
00:14:45.160
data file that syntax error is but what we can do is we can wrap
00:14:51.680
that that eval in some exception handling code which will it'll rescue any any exception and then it will use
00:14:59.079
this ability to rewrite the message to add some contextual information so in
00:15:04.360
this case we're adding the path and the line number of the file uh where that
00:15:09.880
that error occurred and then just and then it reases the exception but with a new message um
00:15:29.240
that's a good point uh so so in this particular in this particular case yes you you could pass the the line number
00:15:36.279
uh and to uh to eval and and it would accomplish the same goal however um so
00:15:41.880
so that that's for this case but for in general um if you have if you have code
00:15:48.880
um you know where a lowlevel exception may bubble out and you have the ability to it at a higher level to attach extra
00:15:56.600
context which will make debugging that error um easier than uh this is a handy
00:16:04.920
technique uh you can also if you need to you can rewrite the um the back trace on when you re ra this
00:16:12.680
probably an a good area to tread lightly though if you re if you call raise
00:16:18.079
inside rescue with no arguments whatsoever it's just going to re raise the currently active
00:16:23.440
exception um now some languages don't allow any of
00:16:28.560
this some languages you just can't you can't raise during a during handling
00:16:33.800
another exception and there's some there are some good reasons for this I mean um you know as we've as we've seen
00:16:40.079
sometimes ra sometimes raising during a a rescue can complicate debugging uh it
00:16:45.560
can also result in resources not being properly cleaned up if we wanted to uh we have some more fun with overriding
00:16:51.560
rays and Ruby and we could Define a raise which does not um which does not
00:16:57.959
allow re raising within a rescue and uh here's how we use it so we can include it into object and uh when
00:17:05.760
we try to raise a second error while rescuing a first error it just ends the program um however what we've also done
00:17:11.799
is we provided a an error handled method where we can signal that yes you know explicitly yes we have handled it and
00:17:17.720
now we're raising a new one and what this does is it just sets that error info uh variable back to nil so it is it
00:17:24.720
is mutable and uh and then it continues on
00:17:32.160
if an exception is not rescued in Ruby eventually it'll bubble up to the top of the call stack and Ruby will will cause
00:17:39.120
the program to exit um however it won't do this until it has executed various
00:17:45.520
exit handlers uh so I'm not going to go over the the details of the differences between trap and at exit and end uh but
00:17:53.720
the point is all these things can be will be called uh before the program actually
00:17:59.840
exits even in the case of exiting du an exception and and this is kind of Handy
00:18:05.039
um if you ever had an application where you wanted to put some crash logging in
00:18:11.120
it you wanted to log um unhandled exceptions but you didn't there what for for whatever reason there it was not
00:18:16.480
easy to wrap the entire application in a big begin rescue end block we can still
00:18:21.679
tag on a uh a crash logger without having to wrap the whole application we
00:18:27.159
can do this by finding an ad exit block so that'll be that'll be executed at the when program exits and we can use that
00:18:34.159
that handy dandy error info variable to check to see if the program is exiting
00:18:39.440
because of an error and if it's exiting because of an error we write out we write out some information to a log file
00:18:45.320
here so we're writing out some information about the error and in this case we're also writing out the versions of all the ruby gems that were loaded at
00:18:51.440
the time of the error I'm sure you can think of lots of other things you could throw in a in a uh a log file that major
00:19:07.039
all right so that's that's the mechanics um let's talk a little bit about how we respond to failures in
00:19:14.919
programs one way to respond to failure is simply uh to return some sort of error value in Ruby programs more often
00:19:22.520
than not this error value is is simply nil uh a related approach uh which can
00:19:31.159
sometimes uh work better is to return some kind of benign value so you return
00:19:36.400
something which more or less behaves like the object that was expected out of that method uh expected as a return
00:19:43.960
value but it just has some placeholder values and and this is useful sometimes uh when you don't want to when you don't
00:19:50.120
want to force the caller to check the return value U maybe the maybe the method isn't you know too vital and you
00:19:55.600
don't want to bring down the program because because it failed
00:20:00.919
uh you may find yourself uh wanting to log exceptions to some kind of uh some
00:20:06.679
kind of external service you might want to log them to a file you might want to log them to an external service um I
00:20:11.720
think I forgot to rewrite HP toad as uh as air bra but um and this is this is a
00:20:18.200
a great thing to do but you need to be careful that you don't make things worse when you do this so I'll tell you a
00:20:23.760
little story about this um I worked on a project once where we had a bunch of we had a a distributed Cloud uh we had we
00:20:30.799
had you know some master systems and we had cues and we had lots of workers pulling jobs off the cues and processing
00:20:36.799
them and sometimes the the jobs would fail and we had written a little really basic uh error reporting system which
00:20:43.520
would just email us we use our Gmail account to send us an email with the error information and then the you know
00:20:49.480
and then the it would take the next job off the queue and keep going so this worked okay uh but one day we rolled out
00:20:57.600
uh an update with which caused the number of failed jobs to go up quite a
00:21:03.240
bit and so suddenly we're getting you know hundreds and hundreds of of error
00:21:08.520
reports and as a matter of fact we got so many error reports going out to our Gmail account that uh Gmail frauded our
00:21:16.720
Gmail account and the result of This was um
00:21:24.200
the result of this in the code was exceptions it was uh SMTP exceptions
00:21:30.720
being raised that code had never been written to anticipate SMTP exceptions and so as a result the workers instead
00:21:38.360
of instead of reporting the error and then moving on to the next job they would simply fall over and die because
00:21:44.320
the the exception would would Bubble Up and Kill the whole worker but that wasn't the worst of it
00:21:49.400
so we had workers dying but that wasn't the worst we had other systems unrelated systems that also reported their status
00:21:56.360
using our Gmail account and so they were emailing us and then
00:22:01.840
and they hadn't been written to handle SMTP exceptions either and so they were falling over and dying too so we had
00:22:07.200
completely unrelated services to these workers falling over and dying because we had failed to isolate our our
00:22:14.919
exception handling code so it's just something to be aware of uh one strategy
00:22:22.039
for dealing with this this tendency of failures to multiply like this uh is the the circuit breaker pattern which uh
00:22:28.720
niggard describes in his wonderful wonderful book release it I don't have time to go over the pattern in detail
00:22:35.000
but I do recommend if you have uh Distributing systems like this you check that
00:22:41.279
out let talk a little bit about how you structure uh your failure handling
00:22:47.039
strategy in your libraries or applications first of
00:22:52.480
all when how do you make the decision to raise an exception versus um versus
00:22:59.960
doing handling the error some other way the U the simplest the simplest rule
00:23:08.440
for this um but but one of the harder ones to interpret is well exceptions
00:23:13.760
should be exceptional you know only raise exceptions for truly exceptional cases but this can
00:23:21.360
be a little bit hard to can sometimes be a little bit hard to decide what warrants an exception what doesn't um
00:23:28.159
one thing that is not that I don't think is exceptional is invalid user input humans
00:23:34.000
make mistakes they f finger keyboards they misund understand uh uis so I think uh you know I used to I
00:23:42.000
used to look at active record and see how it would not you know by default when you call raise it doesn't or when you call Save sorry uh it doesn't raise
00:23:48.640
an exception uh if the data is invalid and I used to think that doesn't seem right that's not like pure o uh correct
00:23:54.760
and then I thought about it some more and I realized invalid data in that context in the
00:24:00.279
context of of like a web form is not unexpected this is not an unexpected circumstance so these days I actually
00:24:06.400
think it does the right thing because because that's unexpected thing to happen um and and so it actually kind of
00:24:21.320
you really do want to break out of multiple levels of execution the way an exception does but it is not an
00:24:28.440
exceptional case it's not an unexpected case Ruby actually has has a construct just for that it's
00:24:35.200
called throw and catch and a lot of people coming into Ruby get that kind of confused with the raise and rescue um
00:24:41.000
but this gives us a way that is not part of the exception system at all to to bomb out of a piece of code and and and
00:24:47.320
go up to a higher level um you can see an example of this in Sinatra so Sinatra
00:24:53.799
you can say last modified in in an action and it'll check to see if the browser has latest version of of the the
00:25:00.799
the page that it's serving and if so it'll just stop right there no more of that action will be will be executed the
00:25:07.679
way that's implemented is it throws the halt symbol up the call chain and then a
00:25:13.760
middleware that's higher up the call chain catches that halt symbol and uh
00:25:19.000
and ends the request right there so it it avoids any expensive processing that might have been further down the
00:25:24.640
action another question to ask yourself am I prepared to end the program any
00:25:30.880
exception not handled can end the program or in the case of an application server can at least end the request and
00:25:38.120
sometimes just looking at it from this perspective just looking at it from the perspective of do I am I prepared to end
00:25:43.840
the program over this can change your your your way of thinking about a particular case you can think you know
00:25:49.520
what that case actually is it's a secondary functionality maybe we should just fall back in some way and not
00:25:56.000
actually uh not actually raise an exception there question number three I'd like to
00:26:02.720
ask is can I punt the decision into somewhere else so this this question of what
00:26:10.120
constitutes an exception continually plays Us in in programming and and you know you can ask
00:26:16.600
uh is an end ofile a failure is a Miss Missing hash key a failure is a 404 from
00:26:23.399
some web service of failure um and the answer to all of those is it depends
00:26:29.039
it depends on the context it depends on the circumstance but when you raise an exception you force the issue you say I
00:26:34.679
know that this case is unexpected I know this cases is exceptional um and
00:26:40.440
particularly when you do that in library code sometimes um you can be kind of asserting an opinion which which may not
00:26:47.000
match reality so whenever I have a case like this uh where I'm really not sure
00:26:52.880
of what decision to make I like to see if there's a way I can PT the decision somewhere else and um um and you can do
00:26:59.600
this in Ruby and an example of this that I really like is the fetch method uh
00:27:04.960
that a lot of the that arrays and hashes and lot of the Ruby uh containers have where you can specify a fallback action
00:27:12.200
or a fallback value uh if the key that it's looking for is not found and so for an optional key we
00:27:19.559
could specify just a default value that it'll fall back to for a required key in a hash we could specify instead of a
00:27:25.080
default value we can say raise raise some and uh and this way the caller decides
00:27:33.279
on a caseby Case basis whether that missing key repres represents an
00:27:39.039
unexpected case or just a an ordinary expected case uh and we can do this in
00:27:44.720
our own codes it's very simple to do this kind of fallback action where we uh we take a block and we let the caller we
00:27:52.159
pump that decision up to the caller caller decides uh how to handle the case where uh you know where
00:27:58.679
where a value is missing or something didn't work question number four am I am I
00:28:06.919
throwing away valuable Diagnostics so um this is so this is the case where for
00:28:14.360
example where you have some process that that you know maybe you're provisioning a server or something takes 15 minutes
00:28:20.960
um and accumulates all kinds of context along the way and all kinds of logs and
00:28:27.120
uh and and objects are created and then one trivia little error causes an exception to be raised and it blows up
00:28:33.399
through the call stack and throws away all that contextual information cleans it up and and you're left with a very
00:28:38.880
little idea of what was actually going on at the time that the exception was raised and you also don't have any kind of intermediate output or anything like
00:28:45.120
that so this is a case where it might make sense not to raise an exception it might make sense to have just some kind
00:28:50.760
of some way of collecting uh error Flags or or error info uh along the way
00:29:00.080
no uh finally uh a last question I'd like to ask is would continuing forward result in a less informative exception
00:29:07.600
so sometimes I mean particularly when you you're like when you have input to a method you have a case
00:29:14.600
where where it's just you know certain values are simply unacceptable maybe like a nil value for an argument is is
00:29:20.840
not acceptable and if you let it proceed further down into that method it might raise an exception which is pretty uncr
00:29:29.080
uh but if you catch it uh right at the top there if you raise an exception right at the top there with the guard Clause you can uh you can be a lot
00:29:36.440
friendlier to to the caller so that's that's a case for failing
00:29:42.320
early in my code I like to uh as much as possible isolate the exception handling
00:29:49.360
code from the mainline business logic um if you you've probably seen
00:29:58.120
some code that looks like this maybe um you've seen code that looks like this only times 10 so where you have uh begin
00:30:06.000
rescue and then inside that there's another case let begin rescue and then inside that there's another another case
00:30:11.480
all these things that could go wrong and it tends to make the code really hard to
00:30:17.760
follow because you're you're mixing up the the business logic with all these sort of T all these sort of tangents on
00:30:23.679
oh if this goes wrong we have to do this oh if this goes wrong we have to do this uh very hard to follow that kind of
00:30:29.159
logic also in my experience code like that tends to be um tends to be bug
00:30:35.600
written and hard to debug um I've actually started thinking of the begin keyword and Ruby as kind of
00:30:42.080
a code smell because Ruby gives us this neat um this neat syntactical
00:30:48.799
idiom where if you only have one level of failure handling in your method uh
00:30:54.760
just just a top level fa failure handling you can leave out the begin clause and you can
00:31:01.240
just have beginning beginning of your method and then you can have rescue uh
00:31:06.519
and then if you want you can have an inure and then you can have end and so what that does I I I like this because
00:31:11.639
it kind of neatly divides the method into sections you say here's our here's our Mainline business logic up to this
00:31:16.720
point below this this is all this is all error handling and then if needed below this this is house cleaning um and it's
00:31:23.399
it's really nice division um I think it it makes code code easier to follow and it it tends to result in in better
00:31:29.679
Factor code too uh if you're trying to move code in this Direction one thing you can do is
00:31:37.240
use something I call a contingency method so let's say you have let's say you have uh some code that does a lot of
00:31:43.760
IO and every single time you do an IO operation you wrap it with a begin rescue end where you rescue IO error or
00:31:50.440
you rescue system call error and uh and you you handle it pretty much the same way every time uh but each time you do
00:31:56.880
some IO you have to gra it with that because you know sometimes this is going to fail uh what you can do is you can
00:32:04.000
Factor those begin rescue ends out into a contingency method whose only
00:32:09.440
responsibility is implementing your failure handling policy for that code
00:32:15.600
and so now You' you've got you've kind of separated things out into business logic and IO failure handling policy and
00:32:21.720
you can now change those independently um and I think it reads Elan here as well
00:32:30.639
Pop Quiz what parts of this code can raise an exception just yelling
00:32:39.440
out that's right all of it the answer is all of it because in Ruby there's no
00:32:45.039
there are certain exceptions which can be raised at any point in the code so things like no memory error if you run
00:32:50.880
out of memory that's going to be that can be raised anywhere um signal exception somebody presses control C on the keyboard that signal exception pop
00:32:58.120
up anywhere and um this is a little scary
00:33:03.519
uh because sometimes we have methods which are kind of important and we don't you know methods like that like
00:33:09.200
especially like exception reporting methods that we really uh we don't want them to to mess things up further
00:33:15.080
exception is raised um so critical methods need known
00:33:20.399
exception semantics and um and there's actually kind of a a there's a there's
00:33:26.120
an old um there's an old list of of guarantees this has been around for a while I came
00:33:31.880
cross it in the in the C++ Community uh that kind of divides up the the
00:33:37.840
different uh levels of exception safety that a method can have so you start at
00:33:42.880
the lowest level you start with the weak guarantee which just says uh this method promises that the object will be left in
00:33:49.039
a consistent state if an exception is raised might be a different state but it would at least be a consistent state it won't have like null pointers or
00:33:55.240
something like that uh then you have a strong guarantee it's basically a roll back guarantee it says the object will be rolled back to its beginning state if
00:34:02.279
an exception is raised and finally strongest guarantee is the no throw guarantee where you say I promise no exceptions will be raised from this
00:34:08.879
method it is useful at least when thinking about some of your more critical methods to think about them in
00:34:16.440
light of these three guarantees and and think you know
00:34:22.000
what what guarantee should this method uh try to try to promise and and then
00:34:29.960
think about you know how what can I do to ensure that that that it is actually guaranteeing that uh there are some
00:34:36.079
automated methods for verifying your uh your exception guarantees and verifying the method actually does implement the
00:34:41.839
strong guarantee and stuff like that but unfortunately I don't have time to go over them uh in this talk here's an an
00:34:50.159
pattern sometimes you encounter some code that just raises some like client
00:34:55.919
uh Library code or something that just raises all kinds of exceptions uh with no Rhyme
00:35:01.480
or Reason and you finally just wind up wrapping the thing in rescue exception and uh and throwing every exception
00:35:09.640
away unfortunately this sort of this is a huge source of bugs I can't tell you
00:35:15.520
how many times I have debug situations where it turned out that the reason things were uh things weren't working
00:35:22.400
right or or failing to fail properly even it was because they um
00:35:29.280
they did one of these they just threw away all exceptions um if if because of
00:35:34.440
the way the code that you're you're wrapping is written you can't guarant you can't rely on any particular exception class being raised uh if
00:35:42.079
nothing else try and match on the error message and uh and that way you can
00:35:47.200
maybe limit some of those uh some of those surprises when you're writing Library
00:35:54.240
code um you're there's kind of a conundrum around how to structure the exceptions that come out of your library
00:36:00.240
code so on the one hand it would be nice if you could just raise uh you know if you have an i people expect an IO error
00:36:06.760
if you have a system you know and Ruby has sort of built in argument errors um
00:36:11.880
Range errors things like that Ruby has these nice built-in error classes that people understand uh and people you know
00:36:17.079
know how to how to know about them and know how to deal with them so it' be nice to be able to raise these sort of
00:36:22.240
classic Ruby exceptions but on the other hand it would also be nice to be able to say
00:36:29.040
all of my exceptions inherit from this one Library exception class and that way you know and if you're using my library
00:36:35.119
you know you can just rescue that one exception class and they will all be caught and so these these two things
00:36:41.200
seem kind of at odds with each other but um there's actually a way to to resolve
00:36:47.960
that so uh it's using something that that I'm I'm referring to as module
00:36:53.040
tagging so what you can do is you can define a module whose only job is to act as kind of a type tag uh it doesn't
00:36:59.720
actually have to contain anything it's just a type tag what you do is is at the top level of your library you just you
00:37:05.920
you wrap your code in Clauses that look like this where you rescue any exception that
00:37:11.720
comes out and you tag it with that mod that module you just extend it dynamically extended with that module
00:37:16.960
and then you re erase it and that way uh your client code uh can kind of haveit cake and E too it can it can choose
00:37:23.680
either rescue specific exceptions you know uh range eror um or uh or it can
00:37:31.880
um uh or it can rescue your sort of global uh Library exception uh I will
00:37:39.119
I'm going to get to some question time uh
00:37:44.599
shortly one of the the um questions the the big questions that
00:37:51.960
I've sort of gone through in every project that I've done is how I
00:37:57.839
structure my exception hierarchy and there are a lot of ways you could go about this you could you could base your
00:38:03.839
structures on the your your exceptions on the module that they come from so you have different software modules you can
00:38:09.760
say you know this module has its own exception class and this module has its own exception class uh you can base them
00:38:16.599
uh more vertically you can base them on like the layer that an exception comes out of so you can say you can have your lowle exceptions and your high level
00:38:22.760
exceptions um you can even do it by severity if you wanted to uh and and
00:38:27.880
this is sort of Bugg me every time I've done a project it feels like I make the same decision uh over again and I'm
00:38:33.720
never never quite sure if I if I made the right decision uh but what I've started to do recently is I've started
00:38:39.359
instead of looking at it um from the point of view of the architecture of the program I've started to look at it from
00:38:44.680
the point of view of of the the end result why do we break
00:38:52.240
down our exception classes at all why do we raise why do we raise different exception classes why don't we just raise runtime error all the time uh well
00:38:59.480
one reason is we might want to tag some some special information onto an exception but the biggest reason is so
00:39:05.680
that we can structure our rescues to to rescue different types of exception
00:39:11.040
differently and so I've started to sort of look at things backwards and work backwards and say um what are the actual
00:39:17.560
what are the ways that that exceptions um are handled differently so what
00:39:22.760
different ways do we do we typically uh see exceptions handled and I I in in a
00:39:28.400
lot of apps they basically break down into three categories I find so you have your your uh user
00:39:33.599
error um user error like you know a 404 404 or 403 403 page or a invalid data or
00:39:41.079
something like that user error says um you have you have done something wrong you
00:39:46.680
have used the system wrong the only way that you can address it is by changing what you what you did and trying again
00:39:53.599
uh then you have another category of logic errors where the message there is we have done something wrong and there's
00:39:59.960
absolutely nothing you can do about it except maybe record plug and finally you see your your transient errors which
00:40:06.480
says uh you didn't do anything wrong and our code isn't broken uh but we're just
00:40:11.960
experiencing heavy load or we're doing some maintenance or something like that and so what you can do uh is you can
00:40:18.119
wait and try again later and so sort of you sort of see this this breakdown along the lines of what the user how the
00:40:24.720
user whether the user is a is a human or whether client code should um should
00:40:30.720
respond and when you work backwards from that you come up with three basic exception classes uh user error logic
00:40:38.960
error and transient error and I've started to to break my exception hierarchies down uh into these classes
00:40:44.599
and and in my experience I think it it it uh it captures the important
00:40:50.440
differences uh between exceptions in most
00:40:55.720
cases all right uh so I thought of trying to summarize all that um but there's no way so
00:41:02.599
hopefully uh hopefully you got something uh from from part of that talk uh hopefully you learn something
00:41:09.319
new um uh when I was putting together a talk about exceptions I discovered I had
00:41:14.440
so much material I couldn't possibly fit it all in a talk so I wrote a book um and uh there's a there's a uh a discount
00:41:21.920
code you can use um and uh that's it thank you very
00:41:56.240
much for