Transcript from the "Error Handling" Lesson
[00:00:00]
>> The next topic I'd like to discuss is Error Handling. As we kind of mentioned, at the very beginning error handling is one of those particularly unique functions within GO because GO is going to treat every error as a value rather than an exception. And there are two kinds of errors that we've kind of looked at so far, that we've seen happening in our console and haven't talked a lot about.
[00:00:18] And that's an error and something called a panic. An error indicates that something bad happened to your application, something went wrong. But it might be possible to continue running the program. We need to handle it. We need to figure out what we wanna do about the thing that went wrong.
[00:00:31] For instance, a function that intentionally returns an error that you're kind of waiting for like hey, Go make this string uppercase. And if you happen to do something that's not uppercase, let me know so I can modify that in a different way. Panic, on the other hand happens at runtime and it's going to be something that was fatal to your program and your program has to stop execution.
[00:00:51] For instance, or your user tries to open a file that doesn't exist or you're trying to hit an endpoint that doesn't exist, something that you kind of can't control from the code side, but that your application needs to handle regardless. So let's start with the error type here.
[00:01:06] And the error behind the scenes is coming from the error type which is an interface in Go, which we'll talk about in a little bit. But you'll see at the top right hand side here, it starts with the word type. It is an error type and then it's an interface similar to like type struct or type string.
[00:01:25] And an interface, which we'll discuss more in depth in just a little while, has a series of methods that you can call on that type. So if we have an error type you'll see that last line that is front print line and then err which represents our error.
[00:01:41] And we can call .Error on that variable which we're gonna look at here in the next slide. And in the middle we have our err variable being defined as the result of whatever function that may or may not return an error. So let's hop over to our errors.go file and do some coding.
[00:02:08] Cool so let's talk about this first example here. Our first function is greater than 10. And it takes a number and it may or may not return an error. 9 to 11 is checking to see if that number is less than 10, return, let's create a new error.
[00:02:23] So we can use our errors package from Go and instantiate a new error that something bad happened. If the number is not less than 10, we can return nil. So error is one of those kind of unique types where we can return an error but we can also return nil which is why a lot of our error checking begins with if error is not nil.
[00:02:44] Let's scroll down to our main function here. And I just looked this up. We're instantiating a variable nom which is set to 9. And then because r is greater than 10 potentially returns an error. Our variable in the left hand side is err. And then we're calling is greater than 10 and passing in our num, which is 9.
[00:03:06] The next thing we're doing is handling that error. So if our error is not nil, meaning our error or our function actually returns some sort of error type we want to handle that error. So here I'm just going to print to the console, a formatted error message that says, hey, the thing you did is not greater than 10 and let me know what number that was.
[00:03:31] So in our terminal here, we're going to run the code that is just so far which is in section 8 in the code directory and errors.go. So you'll notice that when I pass in the number 9, and we look back up at our is greater than 10 function.
[00:03:46] The number 9 is less than 10, which means we hop into our if block and we return an error, errors.New. To demonstrate that the err simply is just a value, I'm also going to print to the console, just what is err? Let's just look at that. And so our error is something bad happened.
[00:04:12] And once again, if we scroll back up to our function here, we're saying, hey error's package, create a new error, and the context of that error is something bad happened. If we wanna get a little more extreme, instead of just printing out that error we can cause a panic.
[00:04:30] So we can use the built in Go function panic and pass panic our error, run this again. You'll see that now we have this panic err something bad happen and it stops execution of the whole program. If I wanted to say like okay go ahead and panic then also do something else.
[00:04:52] You're noticing that I'm already getting that little console error and saying unreachable code. That's because like after this function panics, you're done. You can't accomplish anything else at this time. And so here it's a little bit contrived, we have an error. But a more concrete example might be like, hey, if you just tried to do something, and the user doesn't exist.
[00:05:11] Alert the user or update a different part of the database to reflect that the user doesn't exist. Here we're just simply creating a formatted error message. But that allows us to actually modify what the behavior program is doing based on what error just occurred. Panic, on the other hand, kills the program.
[00:05:41] And then the last thing I'd like to talk about real quick is this log library. And so when we call log.fatalline, we're going to actually log to wherever we're keeping track of. Our error logs, what just happened in our programs. A lot of times you'll be sending this somewhere else where you have your, logging functionality to keep track of any user errors that as your application is running.
[00:06:02] In which case you kind of get a different formatted version. It gives you the timestamp. A lot of times you can specify kind of what exactly happened when that error happened in terms of user interaction and stuff like that. Cool, so let's say we have this is greater than 10 function and then the next thing we want to do in our application is open a file.
[00:06:27] But our open file function, which is right here, it's gonna to look a little similar to r is greater than 10 where the only thing is going to return, if it returns anything, is an error type. And so here I'm introducing the OS package and OS is for operating system.
[00:06:41] It handles things like opening files, saving files, etc. And so here are OS.open is gonna return two different things. And if we wanna review kind of what that means, if you've never looked at the OS.open function before we can use Go doc and check hey, what does OS.open do?
[00:07:02] So in open here It takes a name which is a string, it's going to return two things either a pointer to a file and an error. If it's successful methods on the return file can be used for reading. If not, there will be an error. Which means we need to create two variables on the left hand side to handle the two different return values we could get from our .open method.
[00:07:26] And then we wanna say, hey, if the error is nil, return the error. Otherwise we return nil. The other thing you'll notice is this differ function that we're gonna talk about shortly and you'll see that we're calling F.Close. So we have our open, and now we have our F.Close, assuming F returns something valuable, we could then close that file, do something else with it.
[00:07:50] And this defer function will come into play in just a little bit. But for right now, keep an eye on the fact that this function returns an error if it's going to return anything at all. From our open files, let's hop back down to main. And uncomment this guy.
[00:08:11] Cool so we're saying okay, cool. So we've got this error up here for if is greater than 10 returns an error. We have an error down here for if open file returns an error. But we're getting a linting error. Also, if we hover over this, we're seeing that there are no new variables on the left side of colon equal sign.
[00:08:28] So here's one of those situations where like, we do want to handle two very specific errors in two different ways that both happen in this main function. But we can't have the same variable err on both sides. A couple different things we could do. We could change the variable name, we could call this, some other error and then check that here.
[00:08:57] But conventionally, recall that the if block kinda contains its own scope. So we can refactor this and clean it up a little bit to continue to use the conventional err without having to refactor, or sorry, rename the variable down here. So let's do that on this guy. I'm gonna bring us back to our just print line error here.
[00:09:17] And so, recall that with the if block the first thing we can do is set an actual variable semicolon. And then what does that function or variable actually result in. And we did this earlier when we were talking about maps where if you're going to look up a key in a map and it returns nothing, you can check to see if that okay variable returns true or false.
[00:09:44] So it's going to be a similar structure to that. So I'm going to comment out this guy and pop it into our if block. So I'm going to say if error, which we're assigning to a value of is greater than 10. And passing it number semicolon, grab that variable, check the conditional.
[00:10:04] If it returns true, fire off what's in this if block. So at this point, we now no longer have access to our error outside of our if block, which is the beauty of it. So now it's kind of scoped to within this specific case of us handling the error based on what comes back from our is greater than 10 function.
[00:10:27] So because we're handling it here, I don't really need to just print it out to the console, I can kill that. And then we can do the same thing down here. So we can say, if err. And set that equal to our openFile function, semicolon, change this back to err.
[00:10:43] And I can kill this line as well. We'll change this back to err. And clean up the comments. So from what became probably twice as many lines of code. Essentially what we're doing is we're wrapping each of these pieces of functionality that have to do with our application into two different blocks that are scoped within our if statements.
[00:11:11] And this allows us to both execute code and if it goes great cool if not, we have two different cases where we're handling that error in a more encapsulated way to use more conventional idiomatic GO.