GopherCon 2016: Dave Cheney - Dont Just Check Errors Handle Them Gracefully

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
so thank you good morning and thank you thank you for coming to my talk so there's a popular Japanese proverb adapted from a Chinese fable whose English translation goes something like this a frog in a well does not know how great the ocean so the story is the Frog lived down in a well where he had all that he could and one day a tortoise came by and told him about the sea the sea and said the Frog I why do I need therefore I have everything I need am i well it's paradise and what is this proverb trying to teach us if you're the Frog maybe you think you know all that there is to know you know your surroundings intimately yet you're unaware of just how small your home is compared to the whole world outside and you don't know how limited your knowledge is and so the lesson here is that you should question your environment and certainly question your assumptions and don't assume that you're right just because nobody has proved you wrong and proverbs like this a very popular in Japan so popular they often have have a shorter form and so one person will say to another a frog in a well and maybe maybe they understand that advice and if they do they understand it not from just those four words but because they understanding the meaning behind those words now last year rod pike gave a talk at gopher fest entitled go proverbs and I was inspired by the English translation of say GU Kensuke whose famous book on the game of Go and in his talk Rob Pike asked are they go proverbs and the answer is yes of course there are there are indeed go proverbs and you probably recognize recognize them and just like the story of the frog to understand these go proverbs it's not sufficient to just to just memorize the words you must understand the lesson that the proverbs are trying to teach you now I don't have time today to discuss everyone on this list instead I want to talk about one aspect of gos design and relate these ideas to some of the go proverbs as I understand them so what to go programmers mean when they say to each other errors adjust values when we say errors are just values what we actually mean is any value that implements the error interface is itself an error but just saying errors are just values to someone who is still just a student of go it's not useful to understand the proverb the student must understand the underlying message that it tries to teach and I think this is a good place to start a discussion on what I think it means to understand goes error-handling fossa fee now I've spent a long time thinking about the best way to handle errors and go programs what I really wanted was there to be one single way to handle to do error handling something that we could teach to all go programmers by rote just like we teach the mechanics of how to use a slice or we teach the syntax of the language now unfortunately I'm pretty sure that there is no one single way to do error handling but at the same time at the same time I don't believe it's an infinite field of possibility I believe goes error handling can be classified into three core strategies the first carrier error handling is why they call Sentinel errors the name descends from the practice in computer programming of using a specific value to signify that no further processing is possible and so to with go we use specific values to signify an error examples of this include the classic IOA of' or low-level errors like the constants in the cisco package and there are even sentinel errors that signify that an error did not occur like for example go builds no go error and the path file path skip derp error which use when using file path dot walk now using sentinel central error values i think is the least flexible error handling strategy because the caller must compare the result with pre declared variable and use the Equality operator this presents a problem when you want to provide more context as returning a different error would break that check even some things well meaning is using the fault air F to add some context to the error will defeat that call as a quality check instead the call will be forced to look at the string output of the errors error method to see if it matches a specific string and as an aside I believe that you should never inspect the output of arrow dot error because it's because it exists for humans not for code that content of the string belongs in a log file or printed out on screen or return to the user in some way to give them an explanation but you shouldn't try and change the behavior of your program by inspecting that error string now look I know that sometimes this is impossible like this this is slightly aspirational to say these things and certainly this advice does not apply as absolutely when you're writing tests but nevertheless comparing the string form of an error in my opinion at least I think it's a code smell and you should try and design your programs to avoid needing to do this Sentinel errors have other drawbacks for example if your public function or method returns a particular Sentinel value and that value must be public obviously and because it's public it should be documented and all of this adds to the surface of your API and also if your API defines an interface which which is specified to return a specific Sentinel error then all those implementations of that interface will be restricted to returning only that error even if they had the opportunity to return something more descriptive we can see an example of this with IO dot reader function stop like iota Copy require that the reader implementation no matter what it is returns exactly io AOF to signal to the caller that there's no error there's just no more data but the worst problem with central error values is that they create the dependency between two packages so to check if an error is equal to EOF obviously the calling code must import the IO package this this specific example doesn't sound so bad because we do this all the time it's very common but imagining the coupling that would exist when many packages in your project export their own set of error values and all the other packages in your project must import them to do those checks now having worked in a large project we've toyed with this pattern I can tell you from personal experience that the specter of bad design in the form of an import loop was rarely rarely far from our minds so my advice to you is to avoid using central error values in the code that you write there are a few cases where they're used in the standard library but I think it's a pattern that you should choose to not emulate if someone asked you to export an error value for your package I think you should politely decline and instead suggest an alternative method which I'll go into next arrow types the second form of NGO error handling I want to talk about today as the name suggests an arrow type is a type that you create which implements the error interface it's pretty straightforward this is an example of OS path error which is a type which tracks the underlying error that might occur in a file operation as well as the path that you were using and the operation that you're attempting to do because path error is public callers going to use a type assertion to extract extra context from the error if they need to as we see here with this switch but arrow types must be made public so the caller can use that type assertion and your if your code implements an interface whose contract requires a specific error type just like a specific Sentinel error that we talked about earlier all the implementations need to depend on the package that defines that error type again creating source coupling and it's this intimate knowledge of a packages type that creates this this strong coupling and makes for a brittle API so while error types are better than error values because they can capture more context about what went wrong error types share many of the problems of error values so again my advice is to avoid using arrow types or at least avoid making them part of your public API now I want to come to the third category of error handling and this is in my opinion the most flexible error error handling strategy as it requires the least coupling between your code and the caller now I call this opaque error handling because while you know that an error has occurred you don't have the ability to see inside it as the caller all you know is the result of the operation either worked or it didn't and if you adopt this position then error-handling can become significantly more useful as a debugging aid for example here in Bartok Fu's contract we've made no guarantees about what it will return in the context of an error so the author of bar dot foo can now annotate any error that it produces or is produced inside that method without breaking its contract with the caller it promised very little so it it has a lot of flexibility and this is all there really is to opaque error handling just return the error without assuming anything about its contents now there indeed a small number of cases that this kind of binary approach to error handling is not sufficient that's very true for example interactions with the outside world like network activity require the caller to investigate the nature of the error and decide you know is it reasonable that we retry this error sorry this operation in this case rather asserting the error is a specific type or a value we can assert that the error implements a particular behavior so let's talk about this example we can pass any error that we receive to our is temporary function to determine if the operation is possible to retry now if error does not implement the temporary interface that is it does not have a temporary method then by definition this error is not temporary and if the error does implement temporary we know a little bit bit more about it and perhaps the caller can use the temporary function to decide whether this error is very tribal or not but the key here is that all of this logic can be implemented without importing the package that created this error or indeed knowing anything about that errors underlying type all we're interested in is its behavior this brings me to the second go proverb that I want to talk about and that is don't just check errors handle them gracefully so here's a question can anyone tell me what is wrong with this piece of code just shout it out there's an obvious error or it could be made better yeah I heard it over there you can make you can just make this one line like it's it's too verbose but there's a more serious there's a more serious issue with this piece of code and to me the problem with this code is that if authenticate failed I cannot tell where in authenticate that failure happened we just have the error so forth indicate fails we're going to pass it back up to the caller of authentic a request and it's going to pass it up to its caller and it's going to pass it up to its caller and so on at the top of my program we're probably to print this error out to log it or something and find out what it is and the result be this there's no information in the file or which line the error occurred on and there's no stack trace in the call stack to tell me what happened now Donovan and Kernighan the go programming language recommends that you add context to arrow paths using form to RF but as we saw earlier this pattern is incompatible with the use of Sentinel errors and type assertions because you convert the error value to a string and you mix in that string with some other stuff and then you convert it back into a new error using form to F and so this breaks equality and destroys any context of that original error it's now gone because it's just made made into a string so I'm going to talk a little bit about how I add context errors and to do that I'm going to talk about a very simple package that I've been working on so this package only has five public functions and a few types and they really exist mainly as a place to hang the documentation the first function is new which just like the errors package in the standard library returns an error with the message that you provide you print it out that's what you get the second function is rap which returns a new error wrapping the error that you passed in and a message so if we print this one out good old good old K in D style and you're probably thinking at this point congratulations David like what's what's the point is exactly what we have now so let's look at this first example again but just pay attention to the formatting verb it's the same calderas new except this time when we print the error we're going to use % plus at plus V we get a full stack trace and this works because each time we call one of the functions in the aero package the error returned as well as containing the message and the other other information records the stack trace where it was created and now these error values also implement the font format interface which lets them know how to print themselves and so the choice of how you want to present that error is controlled at the time you print it using different formatting verbs now we've got a few other convenience methods in there like we have the classic errors dot error F if you want to do a formatted error and wrap F which is similar in function and now because we've introduced the concept of wrapping errors we need to talk about the reverse how to undo this process how to unwrap them and this is the domain of errors cause cause takes an error any error and undoes this wrapping process possibly recursively to retrieve the original error the underlying error now in operation whenever you need to you have a piece of code that needs to check and error imagines a specific value or type you should first unwrap it using the cause function so that's really the only change from the previous example we saw and lastly I want to mention that you should only handle errors once handling an error means inspecting the error and making a single decision now if you make less than one decision you're obviously ignoring the error and you can see this here the error from wr8 is simply being discarded everyone in this room knows that this is not great but making more than one decision in response to a single error I think is also bad in this example if an error occurs during write a line will be written to the log file with the file on the line close to where the error occurred like it's it's a few it's a few lines off but it's pretty close you'll be able to find it and the error is also returned to the caller who might also log it and return it to its caller who will log it and return it and log it and return it all the way up to the top of the program and so the result of this is that you get a stack of duplicate lines in your log file each telling you the same thing and at the top of the program all you get is the original error without any context the errors package gives you ability to add context to an error value in a way that's understandable by both humans and when you print them out and machines if you use errors caused to unwrap it to recover the original error in this example if there is an error in the call to W writer we're going to wrap it with the annotation right failed again the errors package makes this easy because as these functions are defined if you ever pass in nil they just unconditionally return nil so you don't need to add that extra if error not equal nil the stuff so this helps reduce the verbosity of having to check if the error was not nil just so that you can annotate it so to summarize errors are part of your packages public API treat them with as much care as you treat any other part of your public API for maximum flexibility I recommend that you treat all there is as a bake and when you're in a situation that you can't do that assert the error for its behavior not it not its hype minimize the number of Sentinel error values in your program and avoid overloading the error values to convey something which is not actually an error don't use this as a side channel and if you interact from package outside your repository outside code that you've you've written convert any errors that you receive to opaque errors by wrapping them because this will establish a stack Trace that you can use to follow when you need to debug your program and I think this advice also applies to interacting with the standard library and lastly use errors caused to recover the underlying error if you need to inspect it in conclusion proverbs are not rules and they're not laws they're just stories proverbs are a great way of encapsulating information and capturing the essence of a lesson or teaching a moral but they they can equally be bewildering to newcomers who haven't haven't been steeped in the law we have haven't learnt the meaning behind the proverb and I want to leave you to consider the meaning behind the other go proverbs thank you very much
Info
Channel: Gopher Academy
Views: 20,806
Rating: 4.9193549 out of 5
Keywords: golang, software development, gophercon, programming
Id: lsBF58Q-DnY
Channel Id: undefined
Length: 22min 13sec (1333 seconds)
Published: Mon Aug 22 2016
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.