12 ms·
Working with Errors in Go 1.13
- guessmyname 7y agoCheck the original proposal [1] and discussion [2] as well as the unofficial package [3][4]. [1] https://go.googlesource.com/proposal/+/master/design/29934-error-values.md https://go.googlesource.com/proposal/+/master/design/29934-e... [2] https://github.com/golang/go/issues/29934 https://github.com/golang/go/issues/29934 [3] https://github.com/pkg/errors https://github.com/pkg/errors [4] https://github.com/pkg/errors/tree/c978824 https://github.com/pkg/errors/tree/c978824
- unfunco 7y agoNot to be facetious but water isn't wet.
- haasted 7y agoI love Go, but error handling feels like hand-carried, checked exceptions. And now they figured adding a “cause” field is practical. I’m getting deja-vu with Java exceptions 15-20 years ago.
- _ph_ 7y agoI consider checked exceptions in Java quite a nightmare. You have to specify the classes of exception you might throw as part of your function signature and in all methods which might propagate. This is quite a buerocratic monster, which requires quite a bit of refactoring if your exception signature changes. Alternatively, all methods in the chain tend to catch all and then rethrow others. The described changes could have been added by anyone in earlier Go versions. These are not things which can be only implemented by the language or compiler implementor, these are just APIs, which are standardized.
- jayd16 7y agoYou could just use throws Exception instead and check the error like Go does. Its a code smell in Java but so is a million exceptions in your method signature. Most libraries and apis will use a base exception type so keep this to a minimum.
- haasted 7y agoYeah, there are plenty of ways to abuse and misuse those as well. On the positive side, it's possible to make one piece of error handling for e.g. opening a file and writing to it. Just handle IOException. In Go, I have to do manual error handling for each step that may go wrong, ie. the file does not exist, program doesn't have permission to write to etc. In many cases I just want to handle the simple case of "unable to write to file for some reason". My thoughts on Go error handling has made me conclude that there is probably no really good way to do it. Every approach will come with a load of downsides.
- BossingAround 7y agoGo’s treatment of errors as values has served us well over the last decade. Has it? As a Java developer who needed to write something in Go, this has been quite a frustration for me, personally. In Java, when I get a 100-line stacktrace error, my IDE analyzes it for me and I can click through the code and follow what's happening. In Go, I get a string from the developer. Hmm...
- halfer53 7y agoI agree, a simple string from the error doesn't help debugging at all. This happens a lot in production environment, in which developers are too lazy to write any useful debugging information, all we got left is a simple "get_foo() returns error" in the log, it doesn't help with the debugging at all !!!
- nomel 7y agoSound familiar. I had a boss that wanted to clean up our error messages and stack traces from our library/api and make them cleaner and more useful. To make it cleaner, he wanted to absorb the whole stack for any error raised in our library and only print the error text. To make them more useful and helpful, he wanted us also include some guides on how to debug and solve the problem, based on the error, in the error text. Fortunately I was able to convince him this would make things difficult for everyone, in a calm collective way.
- JulianMorrison 7y ago
- denormalfloat 7y agoIt's poignant to watch Go slowly realize the why other languages have more powerful, more useful error types. The `error` interface is anemic to the point of uselessness. Making it unwrappable is step forward, but all that does is progress it the level of 1995 Java with their "cause" field. It still can't express suppressed errors (like those from `defer f.Close()`). There is no standard mechanism to include an optional stack trace. Worst of all, rolling your own error type and trying to use it everywhere is near impossible. Every method signature has to use `error` due to lack of covariant return types, and heaven help you if you return a nil struct pointer, which will be auto promoted to a non-nil interface. Perhaps in Go 3 they'll get it right.
- jknoepfler 7y agoI honestly don't understand where you're coming from. Rolling your own error library is trivial. We have a great one at work that works great, prints stack traces, interops with normal error handlers, and does a lot of custom work for translating errors into external customer-visible messages and internal developer messages. We use it at every layer of a 50-60 grpc microservice ecosystem without much headache. Catching deferred errors is trivial, not sure what you mean there: defer func () { err := f.Close() ... }() Printing stack traces is like 4 lines, I was able to implement ours in 60 minutes of doc skimming. Now it'd be 5 minutes. I don't understand your last two sentences. It doesn't reflect anything I've run into in the real world, I don't think. I have a few gripes about golang, but the minimalist error interface is not one of them.
- mav3rick 7y agoEveryone should not have to have their own error library for basic functionality or custom foo to print stacktraces.
- jknoepfler 7y agoThe idea of carrying around bloated errors everywhere is absurd to me, especially in a microservice ecosystem where that all has to cross the wire, potentially multiple times.
- malisper 7y agoCan someone help me with what seems to be a basic problem? How are you supposed to get stack traces for errors in Go? You can wrap an error every time you return one, but this omits useful information like the line numbers. Not to mention, a significant amount of the code you are writing is going only to providing helpful error messages. Alternatively, it looks like there are libraries out there[0] that will include stack traces for you. It seems weird to me that it's necessary to use an external library to get what seems like it should be built into the language. Can anyone enlighten me? Edit: Forgot to include the reference: [0] https://github.com/pkg/errors https://github.com/pkg/errors
- alecthomas 7y agogithub.com/pkg/errors is a popular solution to this problem
- oppositelock 7y agoIndeed! In my opinion, Dave Cheney's error package is a better design than the Go 1.13 built-in mechanism, because it's more explicit. It's easy to forget a %w after being so used to %v, but errors.Wrap(err, ...) is pretty darn clear, as is errors.Unwrap.
- jniedrauer 7y agoAs far as I'm aware, there are only two native ways to make an error unique enough to trace: Wrapping it with a unique string, or throwing a panic. You might be able to do something clever with panics by throwing them and then catching them to resume the thread, but I've never seen this done outside a few places in the Go standard library.
- sjtindell 7y agoIIRC this is the suggested way to get a stacktrace. Panic and either recover or fail.
- 7y ago
- munmaek 7y agoThe way Go handled errors is why I made the decision to go with rust as my hobby/personal-open-source-projects language. It was just extremely tedious, despite Go being nice overall. I love how error handling is done in Rust. Yes, implementing the From<T> trait can get verbose, but wrapper libraries like Failure or Snafu exist now as well. Being able to propagate errors down the line seamlessly with the ? operator and Result<T, E> type is nothing short of phenomenal. It looks like the next version of Rust will seamlessly allow using ? on None types as well.
- richardwhiuk 7y agoLook at the failure crate. For lots of errors, just treating them as a generic failure makes code a lot better.
- jedisct1 7y agoWhich is supposed to be replaced by https://github.com/withoutboats/fehler https://github.com/withoutboats/fehler
- munmaek 7y agoFehler is highly opinionated and I'm not sure it's worth it. IIRC the author posted on the /r/rust subreddit and was unable to take criticism at all. I don't like having to put decorators on functions and manually throw!()'ing. I would rather be explicit about returning Ok(T) or Err(E).
- munmaek 7y agoI did mention Failure/Snafu, but there are two problems with it: 1) The state of "how best to handle errors" keeps on changing. It was error_chain, then err-derive, then Failure, now Fehler or Snafu.. I would like to see this space calm down a bit. 2) Failure/Snafu require additional dependencies. For minimal libraries it's really not necessary to pull in so much when a a little boilerplate suffices. [0] [0]: https://git.sr.ht/~andrewzah/hangeul-rs/tree/master/src/errors.rs https://git.sr.ht/~andrewzah/hangeul-rs/tree/master/src/erro...
- jniedrauer 7y agoStack traces are desperately needed to give errors context, and I was very disappointed to see that proposal dropped from Go 1.13. At the same time, I'm hesitant to use xerrors in production code because it's likely to change rapidly in the future. I had a situation recently where a fairly complicated API was returning an HTTP 403 in production. Once the error bubbled down to a level where it was actually logged, all that was left was "403 Forbidden". I spent days echo debugging and could not reproduce the problem. Finally, by chance, I happened on the problem while working on something completely unrelated. With a stack trace, 3 days of work would have taken 5 minutes. Wrapping would not help in this situation unless every single error was wrapped with a unique string, which frankly is ridiculous.
- vbezhenar 7y agoIt's funny how people like to bash long Java stacktraces, while they help immensely in the situations like these. It's obvious from the start where the error is and all those stacks can help you to find out every little detail that lead to the problem.
- weberc2 7y agoI’m only familiar with people bashing long Java stack traces because (1) they are often hilariously long, with stateless web applications dumping tenfold the frames of a crashing webbrowser, (2) they contain some number of abstract factory beans (contributing to the length problem) and (3) they show up far more frequently in user-facing applications than just about any other language. Stack traces are great, gratuitous abstraction, buggy code, and exposing stack traces to users are not great.
- klodolph 7y agoThe stack traces tend to be useful when there is a programming error in your program, and less useful when there is any other type of error. In Go, programming errors usually panic which will print a stack trace by default. But I’ll see Java programs that give me stack traces just because I’ve misconfigured them, or the server is down, or I have a bad network connection, or a file is missing. This is awful UX. Worse, the stack trace might not tell me WHICH file is missing, or WHICH server is down. You only get good UX for errors by manually annotating context in the places in your code where the context is known. (My personal experience is that FAR too many actual Java programs in the wild will give me a stack trace when they should give a short error message.)
- gregwebs 7y agoGreat to see this getting standardized! I maintain an error package that lets users use structured types for errors and error codes [1]. It is critical to be able to wrap errors without information loss. I used one standard, a Causer interface, but I will be switching to Unwrap. I see complaints about stacktraces here in the comments. I recommend always using pkg/errors or some error package with stack traces. [1] https://github.com/pingcap/errcode https://github.com/pingcap/errcode
- baby 7y agoOh I wish Golang had something similar to Rust's Options and Results. This `nil` value is the source of many crashes in applications I've audited (due to applications forgetting to verify if the value is `nil` before acting on it). Maybe something like this could be supported natively in Golang: func thing(arg bool) Error.Result(bool, error) { if arg { return Error.Ok(false) } return Error.Error(fmt.Errof("nope")) } func main() { if thing() == true {} // doesn't compile match thing(false) { Error.Ok(value) => { fmt.Println(value) }, Error.Error(err) => { log.Panic(err) } } } just for fun I wrote an ugly PoC for bool options: https://github.com/mimoo/Bool https://github.com/mimoo/Bool
- omegabravo 7y agosimilar to https://golang.org/pkg/database/sql/#NullBool https://golang.org/pkg/database/sql/#NullBool
- baby 7y agothis has a Value() getter though, but at least you would probably not make the mistake of not checking the error here.
- hackworks 7y agoAlmost every language supporting inheritance suffers from lack of ability to check if an error is part of the derivation chain. I have implemented this using expensive dynamic cast in C++ and instanceof in Java. Finally Go gets it ahead of the other languages!
- apta 7y ago`instanceof` in Java can be surprisingly fast. Plus, it seems that errors.Is and As rely on reflection, which is slow in golang.
- ivanjaros 7y agoAfter years of using Go, I came to realize that the ONLY people that complain about errors in Go are those who actually do not write any Go code and those who write Go code less than a year(ie. juniors). There is absolutely nothing wrong with errors in Go and the creators got it right form the start. Nobody is forcing you to use errors. You can use booleans if you want. You can panic(throw exception) if that is what you wish. To each its own. If you want more syntactic sugar, move to Java, it has plenty of it for you.
- frou_dh 7y agoDo have have experience using a language with sum types and exhaustive case analysis?
- rhinoceraptor 7y agoIMO Rust really got errors right, a sum type is the way to go. Not exceptions, multiple return, callback arguments, or sentinels. Encoding the control flow into the type system makes your code much more robust.