Do I need clear and useful things? Maybe not. Would I like to have them anyway? Yes.
Years ago, I configured a Java project's logging framework to automatically exclude all the "uninteresting" frames in stack traces. It was beautiful. Every stack trace showed just the path taken through our application. And we could see the stack of "caused-by" exceptions, and common frames (across exceptions) were automatically cut out, too.
Granted, I'm pretty sure logback's complexity is anathema to Go. But my goodness, it had some nice features...
And then you just throw the stack trace in IntelliJ's "analyze stacktrace" box and you get clickable links to each line in every relevant file... I can dream.
> the wrapping is very greppable when done well
Yeah, that's my other problem with it. _When done well._ Every time I write an `if err != nil {}` block, I need to decide whether to return the error as is (`return err`) or decorate it with further context (`return fmt.Errorf("stuff broke: %w", err)`). (Or use `%v` if I don't want to wrap. Yet another little nuance I find myself needing to explain to junior devs over and over. And don't get me started about putting that in the `fmt` package.)
So anyway, I've seen monstrosities of errors where there were 6+ "statement: statement: statement: statement: statement: final error" that felt like a dark comedy. I've also seen very high-level errors where I dearly wished for some intermediate context, but instead just had "failed to do a thing: EOF".
That all being said, stack traces are really expensive. So, you end up with some "fun" optimizations: https://stackoverflow.com/questions/58696093/when-does-jvm-s...
Easy. Always wrap. Wrap with what you were doing when the error occurred.
> I'm pretty sure logback's complexity is anathema to Go. But my goodness, it had some nice features... And then you just throw the stack trace in IntelliJ's "analyze stacktrace" box and you get clickable links to each line in every relevant file... I can dream.
Yeah, despite the proliferation of IDEs for Go in recent years, Go has traditionally been pretty anti- big-iron IDE.
Then we're back to having stack frames for framework and runtime code in our error traces.
Or perhaps the people who wrote the Go standard library don't follow the ideal Go best practices?
The argument for explicit error values is often something like "it encourages people to actually handle their errors, rather than ignoring them". And on the face of it, this has some merit: we've all seen code that assumes an HTTP request can't fail, and now a small timeout crashes the entire backup procedure or whatever.
But if "handle the error" simply means "decorate it with a trace and return it", then exceptions already do this, then you're really admitting that there is no fundamental difference from a exception, because this is exactly what exceptions do, all on their own. Sure, they produce less useful traces, but that's usually a tiny difference. After all, the argument wasn't "you'll get better stack traces than exceptions give you", it was "people will be more careful to handle errors".
This is also relevant, because if the goal is to get better error traces, that can also be done with exceptions, with just some small improvements to syntax and semantics (e.g. add syntax for decorating a call site with user supplied context that will get included in any exception bubbled from it; add support in an exception to only print non-library stack frames, add support in the language to declare certain variables as "important" and have them auto-included in stack traces - many ideas).
Flow of control is obvious and traceable with explicit errors—they are not some “other” to be dealt with. Exceptions in many languages are gotos, except you don’t know where you are going to and when you might goto. Can this method fail? Who knows! What exceptions can be thrown by this? Impossible to say… better to simply `catch Exception` and be done with it.
That's a different discussion entirely. And even so, whether any statement can terminate early should not be very relevant: that's why we have try-with-resources/finally/defer and other similar mechanisms.
> Exceptions in many languages are gotos, except you don’t know where you are going to and when you might goto.
No, they are not, in any language with exceptions except Common Lisp and Windows SEH. Exceptions in all common languages are early returns, they return to the calling function. Of course, if the calling function doesn't catch them, it will also return early to its calling function. Tracing where control flow will continue after an exception is thrown is exactly equivalent to tracing where control flow will continue after an error is returned in Go.
> Can this method fail? Who knows!
Java has checked exceptions to explicitly mark functions that can throw. While there were some problems with that, especially around the lack of generic exceptions support, this seems like the right way to go, and it mostly got a bad rep simply because of a different zeitgeist in programming at the time.
> What exceptions can be thrown by this? Impossible to say… better to simply `catch Exception` and be done with it.
This is exactly the same in Go, where every single function returns `error`, and there is very rarely any documentation to say what types of errors it might actually return; and virtually all Go code does "if err != nil", which is exactly the same as catch(Exception). Not to mention that most errors used in most Go code are fmt.Errorf, so they don't carry any type information to begin with.