 So my name is Alex, and we will talk about error handling. First of all, who is Alex, and why we will talk about error handling here. I work in software development for about 18 years, and almost all of my experience is about networking. So I developed every scene starting from drivers, VPN, antiviruses, and there is still cloud, so basically. But everything is about networks. And except networks, I also very love programming languages. So I would say my three most loved programming languages are C++, Go, and Python. And when you work with lots of different programming languages, when you need to learn them quite often because of different things and so on, mainly I would say one of the most important questions is, come on, wake up, what's going on? Wait a second, it's fell to sleep. Yeah, you know. Wake up. Wait a second. I think I will need to click it sometimes, and then it will not sleep. Okay, so, and one of the most important questions when you are learning new language or dealing with it a lot, how to handle errors? Because, yeah, we have our main flow, which is our main, which everything is right. But we also have lots of subflow, which are dependent on our errors. And almost all language have own right way of handling errors. So I wouldn't say that if you know how to handle error write in C sharp or Java, you will do it right in Go. So it's all on Go is own world, and we should do error handling in Go as it's supposed by design in Go language. So, as we have several beginners here, I decided to include these slides just for refresh and memory for others as well. So, first of all, error in Go could be absolutely everything. The only requirements is object which you use as an error should support error interface, which is also very simple as almost all interfaces in Go. There is one function which converts your object to string. Also, by notation, error should be last object in your return values. If you return multiple values, error should be last one. And error could be the only one, or you may have a function which doesn't have errors at all. That's also absolutely valid for Go. And basically, you can split away how we are dealing with errors in Go into three different subsets. The first subset is error inlining. I really hate when someone doing it. The second one is error constant set. That's the most common way in Go. And I named this component-based approach. We will see later why. So, that's also quite famous way of dealing with error in Go language. Error inlining. I suppose everyone saw something like this. You have a function that's function just create error on the flight. Like, I cannot do this. And it's fine until you have just hello world style application and maybe you have very tiny Amazon Lambda function. So, something is really tiny. What you are not going to support a lot, change a lot, just did once and forget about it. Basically, why? It works because error in Go, as I mentioned before, it's absolutely everything. And in this case, we have object. Object is not nil. So, yeah, we have error. We can do something for error handling. We can, let's say, print it. But the problem with such approach is if you have two different errors and full function can return you not just one error, but two, how you will differentiate between these errors? How will you decide what you should do if error had happened? How you will choose your direction of error handling? That's why this approach is absolutely not maintainable in big systems. So, a constant set. That's a very popular way. And I also use that a lot. Basically, it really works. And a lot of companies doing like this. So, you have a set of constants. Okay, variables, but you are using them as constants. Which defines your error. Let's say in our previous case, like 42 is not allowed, we just create such our constant. And later in a function, instead of creating an error on the fly, we will return this error. That's very good because later you can check what had happened there. Do we really have this error 32? Or maybe we had some different. Because in this case, we will handle not all errors because we are not comparing this with Nail, we will check one particular error. That's great. So, it's significantly improved when compared with previous approach in terms of supportability and how easy you will change your application in the future. But what if you have multiple subsystems? Let's say you can have different sources from your error. You may have extra information in your error. And here we have just constant. But let's say you want to add different error codes. You want to add information about subcompany and to regenerate this error. And you have no way to embed this information. Yes, you know about error, but you have no way to embed extra information. So, this approach, I named company-based approach. Basically, if you will look deeply into Go HTTP component, they use more or less the same way. And the idea is let's embed more information into your error code. Basically, not just constant, which is great, but let's do more, let's do better. And on compared with previous ideas, you will need to do extra exercises here. It's not so simple, so you will need to do extra steps. In my example, I decided to create errors based on components. So, we will have set of components and set of error codes. So, we have component 1, component 2, error code 1, error code 2. It's like just for illustration purposes. You may use not component, but just something else. The idea is you have complex error, which includes more information than just one string. So, and this is our complex error. It inherited, okay, not inherited, but includes and that's error. And two extra functions in our error interface code, which we had here, and component. So, our error structure will also have component code and let's say message. So, we have complicated error, which not only tell us error had happened, but it tell us significantly more why, what we have, all details which we may need to deal with error right. And talking about implementation, to implement error implementation is significant. It's very simple, it's very cool. So, the only thing you need is convert your error to string, because quite often you print your error into stream or log file, whatever you want. And so, this is implementation for our component and code parts. How to use it? That's very good. So, that's very powerful in terms of usages. So, from our function instead of returning just an error or just an in place constructed error, we will create structure. Structure include information about components, structure include information about error, and some string. Basically, when we work with this error, like here, so we call function and we check. First of all, okay, that should be, yeah, that's my bet. So, this error should be our simply base. So, what we have is, first of all, we check that error happened, then we are trying to convert our error to object which we are expecting. Basically, this function in theory, marathon, anything else, not just error which we are expecting. So, we are doing conversion. We convert it. Yep, we have component-based error. Conversion was successful. We check that conversion was successful. That's very important because we are doing all these tests in runtime. And then, best part is we can differentiate our tests. We can handle all errors for one particular component or we can handle all errors for one particular error code or like this example for combination between error code and component. So, that's a way how you can embed more information and decide what you should do with your error based on this information. And if you will try to print it, yep. Okay, come on. No, I catched. And if you will try to print it, you will have all information like, all information based on this print statement. So, this is a way how we can embed more information into our error and dealing with this information during error handling. Basically, initially, I use in my project a lot. This way, this is very good until you have reasonably small application. In my case, I didn't feel anything goes wrong until, I would say, 50,000 line of code and project, small project. After that, I start feeling that's bad. It's difficult. It's really difficult to handle. I have multiple subcomponents. How can I know which subcomponents tell me about error? That's why I am talking about this as a component-based error, because it was important who exactly generate error. Because I may have a different component which will generate basically same error that I should behave differently depending on component. That's why I introduce this idea. Okay, wait. Yep. What I really do not like in error handling in Go is situation like this. You have function, function return error, but nothing stops you from just silently ignoring this situation. Even in such old languages and C++, compiler may warn you about this. Okay, if you ask it's a code, but it's possible. In Go, you have no way to have any notes, any warnings from compiler in such situation. So it's just nothing. And that's why I believe it's very important to always use linters, because otherwise it will be just ignored. But in Go, error is just return type. It's not an exception. Your application will not stop working after that. But this error could be really critical for your application. So linters is as a must. When you start a new project, first what you have to do is attach linter to it. It's up to you how you will do it. You can call all linters you want by hand. That's a strange way, but it's still a way. Or last best, I would say, at the moment, way is go-lang-cl-linked. That's new tool which replace go-metal-linker. I used go-metal-linker for, I don't know, four years. And this replacement is quite cool, because it's significantly faster. About four, maybe five times faster than go-metal-linker. If you had issues like, I'm tired to wait for two minutes until go-metal-linker complete check my code, two minutes could be the crazy to 30 seconds. That's reasonable from my point of view. Really reasonable. And panics. So basically when you open any books on Go, you may find something like, yeah, we have panics, but that's not an error. It shouldn't use it. And, yep, panic, not an error. But let's look into it a bit. So panic, first of all, it's just internal function, which accept absolutely everything. Interface and go-lang-linked, it like void pointer on COC++, absolutely everything. So in same time, when you call this function, you want to tell something like, I have no idea how to deal with this situation. I don't have any nice, graceful way to deal with it. Let's just stop my current go-routine and maybe someone higher on the call stack will know how to deal with it. I don't know. So we're just trying to forcefully stop our go-routine and ask someone on different level of call stack to deal with this situation somehow. So, but at the same time, for dealing this situation somehow, we need recover function. Oops. Come on. Huh. Really about errors, you know? Just... It's testing. Yeah. So, recover. Recover is a way how to recover from panic. You can call recover function as much as you wish, but it will do absolutely nothing. It will not even restore from panics, except you call recover from defer. Differ is a way to tell go compiler, please generate... Please call this something, some function at the end of current function, like this. So, we have function f, and we have defer statement. The first statement, just do one thing. It's called println with message. So, what defer means here? When you will be here, means function scope complete, defer will be called. Differ doesn't work for any inner subscope. It just functions code. That's quite important. And so, in this example, in function f, we have defer, means it will be called at the end of the function, execution, even after this, after panic message. We have panic, and as I mentioned before, panic accept interface. So, string is a very good candidate for interface. It will be accepted. And we have defer. So, what defer is done here? In defer, we will try to recover from our panic. If panic is not nil, means panic really happened, because defer will always called. As I mentioned here, defer always called at the end of the scope. So, at the end of the scope, we check. Do we have panic? We are restoring from panic, and a few messages. So, output of these will be like this. We are calling f, means this line. We are calling f, message. Panic in, panic in. You see, we didn't print anything here. So, defer is not printed yet. So, calling f, panic in, defer in f. This is our defer. So, it means this line plan for future, this line executed successfully, this line executed successfully. We have a panic, this line never executed. And then we have our defer message. That's basic idea how defer works. So, and recover from panic from here. We successfully recovered from panic. That's how the idea of panics. And so, as I mentioned before, regarding all books, all tutorials, panic, that's not an exception. Panic is something like, I cannot proceed. I have no idea how to deal with the situation. Please do something with it. Also, in a lot of places, you can find some recommendation like, would better never include panics into your library. If you create new library, please avoid to include panic. And that's quite reasonable, because you have no way to tell library consumer that your function may panic, except adding into documentation. But if someone don't expect panic, and it means they didn't add anything like this, application will just crash. And if you include new library, did a call, and application crashed after that, instead of returning error for you, you will, as developer, be quite disappointed by such situation. That's why would better avoid panic if you create new library. When do you use panic and when do you use log fatal? You know, that's interesting part here. Basically, after all this error is not a panic. Panic is not an error. Don't use panic. Last application, which I participated in development on Go, it was not huge, but quite big, about 100,000 lines of code. And we didn't introduce panic at all. So zero panics. At that moment, I thought that's right way, because everyone told me, don't use panics. But while I was working on this presentation, actually I thought it before last week. And while I was working on this presentation, I decided to check. We have very famous application on Go language. Like Terraform, EDCD, Kubernetes. And one million line, half of million line, three million line. And they are really huge. And panic counts. But most interesting, this column, how many lines we have per panic? Just 605. Yeah, really small count of lines here. Means very famous applications are using panics a lot. So maybe all these tutorials and documentation will tell you, don't use panic, avoid it, until you really need it. Maybe it's not right. So this is really interesting. Because you see they have exact same. Looks like these guys are known something. Yes, these guys are known something. So if you want to search for panic by yourself, you can do something like this on Linux, on Unix, OK. Or something like this on Windows. That's magic I spent quite a lot of time trying to find how to do it on Windows. OK, so panics are not evil. And famous projects are using panics. And if you have workflow, when you really don't have idea how to proceed, do not afraid to use panic. Use it, nothing bad with it. And let's talk about Go 1.13. I think Go 1.13 is very important in terms of our handling because it gives us new way to deal with errors. And that's cool because it's really a lot of new things in Go world. Because Go world usually doesn't change a lot. It's very constant. And changes are really tiny. So these tiny changes for error handling are quite useful. So you have error constant. And let's say this function returns you this error. But you want to extend this error. You want to extend return error by your information because you know bit more. You have wider content. And you want to add more information into error. So by special term, person W, you can basically wrap this error by extra message. They will be kind of chained together. It means you can use this term as much time as you wish. Let's say you may have FMT or F and just chain these calls as much time as you wish. And let's say for this case, if we will print this returned error, you will have something like this. So this part of message and this part of message from here. So they are chained together. Quite useful. But quite often you want to know what had happened before because here we returned extra information. But our original error already hidden. But it could be very important to see original error. And this is a way how to deal with it. So same situation. We are wrapping our original error into new message. And this is a way how to check what really happened. Errors is function will look all chain of this object and search for one particular error permission. If this error is in the chain, this function will return true for you. So quite nice. But bad thing about it, you may have quite long chain and you may have different error before, but it will search for just exact error. So it will check whole chain. But definitely better than we had before. So as this for complicated error handling, if you create your own struct, here is permission error, but in my previous slide it was company error. So you create own struct for dealing with errors. And you wrap it and return. This way will help you because you should compare with exact object. But you don't have object here. You create new object on the fly. So you should somehow be able to extract this object and as gives you this possibility. So you create object which you point to object which you are expecting and trying to extract it. If you are successful, this object will be filled with this data and error as will return true for you. So basically this is a way how to deal with complicated error objects. So, yep. I think initially I had planned to include go to error handling, but first of all it's not existed yet. So 1.13 error handling is just is an s. No, they have a bit more. No, not s and s. The most important one is this basically. First and w. This is a way how you are chaining it. So this is a way how to... I thought it was %s. No, the difference is it's w. It's a new term and by only this term you can join errors together. No, it's not doing a substring search. Yes, because... Yes, right, because they add one error to a second error just chaining them. And they had some more, I think one or two functions more, but I didn't find a really interesting example for them and that's why I just wasn't them. So most important is %w is and s. So that's the most important part of new error handling approaches. And based on my experience, yep, it should change a lot in our life. So feel free to use it if you can update your Go compiler to 1.13. Yep. Okay. So that's all from my side. If you have any questions, I'm ready to answer. Yep. Okay, so since errors, typical errors are not enforced, I mean, error handling is not enforced by Go compiler. Yes. Because you can always ignore that. Yes. So don't you think panic along with different recover can be actually used smartly to just move the error handling? No, I don't think so. Basically, in this case, you will just overuse panics. And the really best way is... Or is it? Yeah. Linter's. It works well. And you know, when you work on Go application, I believe you have to use Linter's anyway, because they help a lot. They tell you not only about ignore error, they tell you sometimes about best practices, sometimes about other errors like code application, basically, or like your code is too complicated. There are lots of variants when it could be useful. And no, panic is not replacement. So I think we have these statistics, so maybe it would be reasonable to follow these top projects way, and not overuse panics. But Linter, that's must have, definitely. It's like Java... You will have stacktrace in case of panic. You will have it. But usually you don't want to see stacktrace. Because if you see it, it means everything is very bad. Actually, you can. It's in a runtime cycle. You can extract it. You can extract it. But by default, you don't have it. And based on my experience, usually you don't need it. So in panics, yeah, definitely, application crashed. You have your call stack. But otherwise, it's quite fine. You do not have call stacks. You need call stacks if your room is like a teenager's bedroom. But in a nicely organized house, you know exactly where the pipe is broken. Yeah, because let's say in Go language, because the language is very concurrent, you usually have logs. Because quite often, you just cannot check what's going on by debugger. I would say it's a very regular situation in the Go language world. So the only way to you to have understanding what's really going on in your application is logs. And if you have proper logging, if you have enough tests, you just don't need call stack. That's okay. Yeah, you know, he's tired. Okay, that's fine. Yep. Thanks for the good upload on one of the teams is an SNW. I don't know. Thank you.