BrandGhost

Fail Fast Or Get Defensive? - Handling Exceptions in C#

When it comes to programming, exceptions are inevitable. We're going to have to deal with error cases... But how we approach our error handling is up to us! Two very common approaches on opposite ends of the spectrum are getting very defensive with try catch blocks or failing fast. In this video, I'll elaborate on when and why you might want EITHER of these options, along with some other thoughts for your exception handling in C#.
View Transcript
when it comes to programming unfortunately exceptions are inevitable but we do have a choice how we can go handle those exceptions or whether we choose to do that in the first place hi my name is Nick centino and I'm a principal software engineering manager at Microsoft in this video I want to walk through some different situations we can consider when it comes to putting TR catch blocks around different parts of our code we'll look at a couple of different situations and how we might have dramatically different approaches for how we use a TR catch now a quick shout out to my video editor before I move along here because this is my fourth time recording this and his third time attempting to edit it because my mic has been an absolute mess so huge shout out thanks so much for putting up with me now let's go jump over to the code all right in typical Nick Fashion I have a contrived code example here we're going to be looking at something called NYX system and you'll notice that NYX system that's declared here on line 14 has an i cool client that's passed in why did I name them these things well that's because I'm terrible naming but that's beside the point so what we're going to look at here is that nyx's system is something that we have control over but I cool client I have declared it in this other project that we have here in the solution but for this example I want us to think that this is code that's not owned by us so that's important to keep in mind as we're going through this example because we truly do not have access to this code in the example and we kind of just have to live with whatever it's doing now in this code if we walk through the method do the thing again because my naming is terrible we're going to see that we have some console right lines and these are just in place here to kind of simulate that we're doing some work that's really not important for this example but the two important parts are these methods here that are some nice to have work that requires authentication and some critical work that also requires authentication and if we go to have a look at both of these methods they look basically the exact same right now because I want us to walk through how we might go handle exceptions in these situations this is an important part for us to kind of pause and start talking about some really different ways that people approach exceptions on one hand we have people that will say you want to be able to fail very early if something goes wrong that means that you would not be putting try catches in place if an exception occurs you want to make sure that it can Bubble Up and basically end your application right there on the complete other end of the spectrum we have people that get very defensive in their programming they will put TR catches around basic basically everything and then what happens is an exception is thrown and then the program can just continue to execute but like all things in software engineering there are pros and cons to both of these approaches and a good middle ground somewhere between these is more often what you need that does suggest that both of these options are not necessarily completely right or completely wrong but there's some truth and some use to both of them let's start on the one side where we're basically catching all of the exceptions right so one of the obvious cons here is that if something is going wrong and we're masking the problem so we're just catching exceptions maybe we're logging them or something maybe we're not even doing that we start catching these exceptions and truly something is going wrong what ends up happening is that as we continue along the stability of the system that we're dealing with can continue to decline so the expected behavior is sort of like becoming more and more unexpected the more and more issues that we're encountering but we keep saying hey look everything's good let's keep moving along one of the benefits of doing something like this is that if you have a lot of code that is best effort attempts at things or like we might seeing the examples coming up if you don't have full control over something you want to make sure that you can have TR catches in place to be able to continue on you don't want your entire application or service to fall down entirely just because one exception is thrown all right so now back to the entire other end of the spectrum we want to be able to fail early so this means we're not going to be splattering TR catches absolutely everywhere in the application and the idea here is that if we fail early we have a good signal for where something's going wrong we don't have to go investigate really far down the line and say hey wait we have to go look all the way back now to see where things started going wrong right there's no huge breadcrumb Trail it's just it went wrong here and having to navigate to the root cause is a little bit more easy this can really help with starting to pinpoint where issues are but of course one of the downsides here is that depending on what your application is like if you have an exception being thrown that's not truly an exception do you really want your entire application or service to fall to the ground now that we've talked about both opposite ends of these spectrums I want to walk through the code and see how we can start putting some code in place to talk about these a little bit more in depth we'll start with the critical work with off path so I want us to go look here on line 44 through 46 we're asking for this off token calling get off token a sync and if we look a a little bit lower we can see that this is really just calling that cool client calling the login async method I'm passing in Dev leader is the username and a really really secure password that's just password I don't recommend you ever do that now we have this as an example because if we're thinking about critical work that we want to do we might say hey look if anything goes wrong along here including being able to authenticate we know that if this fails we don't want to continue doing anything else in the application after this and that's because we could have some undesired Behavior some unpredictable Behavior and the main goal that we're trying to accomplish is basically just not going to happen if we don't put try catches along here anything that goes wrong will end up bubbling up that exception right to the top and I'll scroll there and we can see that I did wrap sort of this entry point in a TR catch so we might get some logging right at the top maybe you don't even need this and you want to see this kind of exception bubble right out up to you if you don't need special handling but the point here is that the application is no longer going to continue once we throw an exception on this critical path that's truly because there is no exception handling put in place here now there are some things we could do so if we did scroll back up to the top and we see the TR catch here in some situations depending on how you've structured your application you may want to put a TR catch here but you might want to consider rethrowing after you've done some logging or Telemetry right so if I put a TR catch here thank you very much cop pilot for predicting what I want to write we can see that in this case I might do some logging or Telemetry right this is just a console right line and then we just go rethrow that exception to let it Bubble Up We're intercepting something bad and going hey look like we need to make sure we're taking note of this and bubbling up the exception but something that you want to consider again this is based on the orientation and structure of your application is that if you do this at all levels so for example when I say levels we go right to the top we start off in the main entry point we go into do the thing imagine this had a TR catch that also had some Telemetry and logging and then we went into here right which also has it if we had an exception at this point in time what would happen is that we would catch it do some logging in Telemetry rethrow it then we bubble up if this also had a TR catch around it with logging in Telemetry we would go repeat the same thing and then rethrow it and then we get all the way back up to the top which basically if you assume that this is logging in Telemetry would repeat it so it's not that you cannot do that you just need to give consideration to how your application structured if you want to handle it more at a central or top level you can absolutely do that you could have it at a more granular level but you need to give this consideration and otherwise you might just have a ton of logs every time something goes wrong but this is really it for the critical path right we want to make sure that if something's going wrong we are able to basically allow it to terminate early because if we took this out right and we just catch the exception and allow it to continue on this is where we might say hey look like anything else after this it's unpredictable undesirable so we do want to let the application terminate now how does this change if we want to go consider some nice to have work and I want to pause when I say nice to have work because you might be saying Nick that sounds kind of stupid why would we write a program that just has some nice to have things and I probably could have picked a better name for this but I didn't really have a good way to generalize it there could be situations where you want to go par some data and you don't control the parsing that parsing May throw an exception and you may say hey if we couldn't extract the username or we couldn't extract the link out of something that's okay it shouldn't end our application but it did happen to throw an exception right maybe something you don't have control over so do you wanted to terminate your application or was it nice to have another example could be things and we're going to see this in a moment like being able to make a web request there's all sorts of transient errors that can occur when we're interacting with other systems so the first time you try something it would be nice to have it succeed the first time of course but it might not be required because you might have some retry mechanisms you might say that it's required to succeed after five times or something but to succeed the first time is just nice to have if you're using things like poly to be able to put retry policies in place that kind of thing like that's what I mean by nice to have stuff so just a couple of examples I'm not going to walk through all of them but I want to frame it up for you so that you can have some you know examples in your mind from your own code as I go through this okay so if something is nice to have work work right so whether it's retrying or something else like that the idea is that if it blows up one of the times we don't care we want to make sure that we can continue on because this was just nice to have and not critical and that would mean that we want to be able to put some TR catches in place so if I put a TR catch around here kind of like what we just saw in the other example we might do this thank you again co-pilot you'll see I don't know how it knows maybe it's just how I named it but it didn't put put the throw in automatically in here kind of curious because it's important that we don't rethrow in this case because that would make this code path again critical if it failed even though we caught the exception if we rethrow it it's going to make the rest of the app Bubble Up exceptions right so we don't want to rethrow it here we could consider logging in Telemetry again right at this point in time um and I want to touch on this because this could be very important if you don't have t or logging or something else this is going to be situational but you might have exceptions in some cases that are coming up more regularly and if you're not paying attention to that because this kind of thing can hide the impact of Errors coming up and you want to make sure if there's a trend that's changing you go hey wait like why are we throwing way more exceptions when we're getting the oth token is something actually going wrong here you can go dig into that if you have logging in telemetry one thing that people are probably looking at there's probably some of you that aren't thinking twice about this and some of you going oh Nick's doing this terrible thing you can't ever put just a normal exception base exception class here you need to catch the specific exceptions I want to talk about this because this is something that I think maybe people are taught something and they're not really kind of thinking about different sides of it so I'd like to go through this exercise together I did mention at the beginning of this video that icool client is yeah it's technically in a project in the solution but I want us to pretend that we don't have control over this right if we check the doc comments we can see that the only exception that this thing throws is an unauthorized access exception right so let's go back up here what we could do if we just pretend that the rest of this code that might be here doesn't throw exceptions we could say unauthorized access exception let's catch this one it's the only thing that get off token async can throw or otherwise you could basically do this thing where you could maybe return out of here I'm just restructuring this to give you an example you might put a try catch around this part as well but you have more granular TR catches that sort of thing but the point here is that we're just catching this very specific exception for many people they would say this is the air quotes right thing to do and why is it the right thing to do well if it throws other exceptions you might be in a bad spot with undesired Behavior we did look at this thing and if I go back down if I hover over it right we can see that the doc comment says unauthorized access exception is the only exception that can be thrown I wanted to bring up this example in particular because it came up in a YouTube comment where someone said hey can you talk to us about this a little bit more so we see that it only throws one exception right and if we jump into the code we can see that that's what the dot comment says and if we truly look at the code we can see that it only throws an exeption in one spot and it's the exception that we're interested in but here's what's not enforced in C if I put my cursor over get a sync on the HTTP client here you can see that it throws four exceptions an invalid operation HTTP request task canceled and URI format exception there is another four exceptions that this method can throw and I didn't even go look at the other one down here there's four more exceptions that aren't even documented on this and if we go back here scrolling all the way back up to our nice to have code if we only catch this one we'd be missing the other four now you could say well Nick just go add in the catch blocks for the other four and you can go do that but the point here is that if you're trying to be defensive in your programming and this is truly something nice to have if you need to keep building up the list of all the possible exceptions you may be in a state and I'm not telling you this is right or wrong just something to think about you may be in a state where you're trying to maintain this ridiculous list of exceptions that could be thrown and you're not even guaranteed that it's sort of all-encompassing up to you but sometimes I go back to saying you know what I'm just going to catch all the exceptions because I don't care which one's thrown there might be a bit of an inverse way to look at this though I might say hey look if I go to catch a um not this exception I want to do a task canceled exception or operation not operating system I want operation canel exception right someone cancels I'm going to return out just as an example right so I might have short circuiting code but I still want to make sure that I end up having one of these in a lot of my cases because I don't care what exceptions being thrown I just want to make sure that I can continue on another situation is if you're retrying things for example if we were to catch that unauthorized access exception it might not make any sense to keep retrying right if your credentials are wrong the first time they're probably wrong the next four times you try so maybe you have some way to go exit your Loop of retrying when it's unauthorized so just an example but I wanted to call out that it's not necessarily wrong to do this and if people would love to debate about that I can show you many examples of production code where if we didn't do things things like this we would be in really bad shape so it's not wrong you just need to give it consideration I'd like to make that point all right so now that we've looked at a couple of extreme situations right one where we have something that we always want to succeed something that we always want to have fail and looked at the different ways that we can catch exceptions I wanted to bring up one more example just kind of verbally here a lot of you are probably familiar with asp.net core right there's probably many of you that come to see my videos and you're here because you've seen other videos on asp.net core something like asp.net core is interesting because if you think about how errors are handled if we think about when the service is starting up if any exceptions thrown you probably want your whole web service to be tanked right you don't want to start it up if you're having configuration issues something else is going wrong basically it tries to start up and then says Nope like we're stopping right here and the reason that you do this is that the service is not able to properly handle web traffic in a known desirable manner because you don't know what went wrong anything could have gone wrong so that startup code path is kind of like our critical code path that we were just looking at right something goes wrong you want to stop right away make it known because if you were to have some big issue and someone's only going to find out once they start sending web requests that's kind of a crappy spot to be in you do want to terminate early and get some insight into it let's look at the opposite side of that if you think about the web routes that you have when people are hitting routes to go do any type of functionality on your service what happens if an exception is thrown on that code path does it mean that if any exceptions thrown your entire web service is brought down just because there was an exception on one of the routes no and what ends up happening is that the request ends up failing you might see a 500 error on the client side but your web service continues to run that's kind of like implementing this other part this nice to have code path that we looked at as well well I wanted to mention this because this is a true example of how both of these things can come together inside of the same application and infrastructure if you thought this was interesting and you want to see how you can approach unit testing these types of scenarios when exceptions are thrown and looking at mocks you can check out this video next thanks and I'll see you next time

Frequently Asked Questions

What is the main focus of the video regarding exception handling in C#?

In this video, I focus on the different approaches to handling exceptions in C#. I discuss the spectrum between failing fast, where exceptions bubble up and terminate the application, and being defensive, where we catch exceptions to allow the application to continue running. I explore the pros and cons of both approaches and suggest that a balanced middle ground is often the best practice.

Why is it important to differentiate between critical and nice-to-have work when handling exceptions?

It's important to differentiate between critical and nice-to-have work because the handling of exceptions can vary greatly depending on the context. For critical work, we want to fail early and stop the application if something goes wrong to prevent unpredictable behavior. In contrast, for nice-to-have work, we might want to catch exceptions and allow the application to continue running, as the failure of these operations doesn't compromise the overall functionality.

What should I consider when deciding whether to catch specific exceptions or general exceptions?

When deciding whether to catch specific or general exceptions, I consider the context of the code and the exceptions that might be thrown. If I know that a method only throws a specific exception, it might make sense to catch that one. However, if there are multiple potential exceptions that could occur, especially if they aren't well documented, I might opt to catch general exceptions to ensure that I don't miss any issues. It's crucial to balance being defensive while avoiding excessive complexity in exception handling.

These FAQs were generated by AI from the video transcript.
An error has occurred. This application may no longer respond until reloaded. Reload