Don't Return Err in Go
akavel.com
akavel.com
Yes indeed.
> by writing custom messages, we can provide more details useful for debugging (for example, the os.ReadFile helped us with a filename);
Custom exceptions anyone ?
> by not relying on code line numbers, the messages are more long-lived, and can actually be understood and reasoned about in isolation, without access to the source code.
Doubt...
I quite like Go (some aspects of it anyway) and it's proven both useful and successful, but some people will go great length to find virtues in its weaknesses.
It reminds me of the multi-year arguments about generics in Go. Obviously, they were needed, yet 100s(?) of blog posts were written about why we didn't need them. They have aged like fresh milk -- not well. See the same discussion about the keyword "final" in C++ to seal a class. For years, C++ was allergic to ideas from any other language that came after. Things are different now.
Panics in go do have traces, in practice they work like RuntimeExceptions in Java.
Overall I think Go makes a good compromise in this aspect, but it requires the developer to know how to structure their error handling in a way that it is easy to debug when the errors do happen (which is also true, and potentially harder, with exceptions).
Common Lisp conditions are the only thing I've used that provides that.
(well, perl's $SIG{__DIE__} + local to dynamically scope the override will somewhat let you do that, but it's a bit deep magic and something developers normally only ever interact with through a library rather than a basic feature)
Which is probably the only real way to figure out what 'simplest' is, I deliberately spent a couple of years writing OO perl code raw (i.e. the low level bless() based stuff that you normally only use for optimisations) to figure out which features of p3rl.org/Moose I couldn't live without - and came out the other side with a solid spec for p3rl.org/Moo which has since proven pretty much spot on as "what almost everybody needs almost all of the time."
Using their entire userbase as guinea pigs rather than only themselves is only going to work better for that.
(I'm not necessarily endorsing the choices, but I can see why they were made and why they were effective)
You can tack on stacktraces on go's errors manually, but it's somewhat limited. Rust has some env var to enable stacktraces IIRC, which is a better solution.
Check out https://github.com/rotisserie/eris. It really is a lifesaver
Like many things in go, I think a lot of us unwittingly hope it will behave in familiar ways because so much of the language seems simple or intuitive. Unfortunately, without a good grasp on it, many parts of go will appear to cooperate with your hopes until it doesn’t.
I think a great example of this is the type system. There is a correct way to use it, but it’ll let you misuse it quite extensively before things start to implode. Errors are a bit like this. You can return raw errors for a long time and it’ll seem fine until suddenly, debugging and tracing issues is incredibly inconvenient and unintuitive.
I found the book “Learning Go” really helpful in breaking these bad habits and helping me get acquainted with how things actually work under the hood.
There's plenty of stuff that I have checked by a lint test before commit but is allowed to stick around in the codebase for a while locally as I work.
e.g. if I have a debugging function that I'm adding/removing calls to a lot I'd like to be able to leave the import for it in place until I'm done even if my current step isn't using it.
Edit: my point is that it's not a design choice but a side effect of choosing "we don't have compiler warnings".
Right, but that doesn't mean that Go doesn't have an opinion about unused variables. Go's opinion is that they are compiler errors.
> my point is that it's not a design choice but a side effect of choosing "we don't have compiler warnings".
What makes you think that? I don't think "we don't have compiler warnings" was behind the choice to make unused variables a compiler error. It seems like a deliberate design decision on the part of the Go team.
https://go.dev/doc/faq#unused_variables_and_imports
> Some have asked for a compiler option to turn those checks off or at least reduce them to warnings. Such an option has not been added, though, because compiler options should not affect the semantics of the language and because the Go compiler does not report warnings, only errors that prevent compilation.
> There are two reasons for having no warnings. First, if it's worth complaining about, it's worth fixing in the code. (Conversely, if it's not worth fixing, it's not worth mentioning.) Second, having the compiler generate warnings encourages the implementation to warn about weak cases that can make compilation noisy, masking real errors that should be fixed.
So I comment out a line or move around some code and want to quickly run it, or run the tests, whatever. This results in a cascade of either removing more and more lines and imports, or not only renaming to _ but also changing := to =.
This is of course the usual golang gaslighting. "It was easier to implement" becomes a "deliberate design decision".
There are tons of things that linters warn about (today), that should have been errors too by their logic.
Can you give a couple examples?
The ones that are enabled by default. These are much more critical than unused variables or imports.
That's the key here - I would not need anything like this in Python either, because it has stack traces.
But Go designers decided to really drop the ball here - there are no stack traces on "err" values, so one has to implement them by hand as article demonstrates.
Which is a pity. Go has a lot of great things going for it, but the error handling just makes everything unnecessary verbose.
Instead with %w you pass an error upward, wrapping it at each func with another text annotation, and at the top it emerges a fully-formed story.
Or something like that. But anyways yeah error wrapping works great.
We wrap errors with stacktraces at library boundaries so that we can `return err` in most cases, unless we really want to add some information. But mostly we use sentry to push log messages with better data into breadcrumbs leading to the errors.
This is much too burdensome for me to cognitively bear so I prefer a closed set model as seen in Zig, Rust, or any ML.
I don't recall if Swift has exhaustive error handling.
The main problem with traditional stacktraces is that, while you get line numbers, local variables at every stack frame are not preserved. So unless the error is obvious like FileNotFound, you’re left guessing as to how the error happened.
While still being hella verbose and probably too much effort for most code, the upfront investment would obviate the need for adding debug print statements or dropping into a breakpoint to inspect the code after an exception.
That said, the post does feel like copium for the ridiculous verbosity in Go and re-inventing chained exceptions.
Though I suppose if this were another language it would be akin to a try catch around every line of code. Which in a way is great, but pretty labor intensive.
https://news.ycombinator.com/item?id=39121489
But I'll add that I'm happier in languages that are less structured with types and things. Perl, PHP, Erlang. Where I can just write my code and see if it works, and add checks later, if need be. Erlang's model of 'let it crash' is perfect for me, make the happy path with error checking, but only add error handling if you need to; with hotloading in production so there's little disruption. Faffing about with casting u64 to and from usize when everyone knows I'm only ever running on 64-bit is madness. C feels easier to write than Rust, but then that's because C doesn't care if you tell it to do something stupid, it'll just do it.
Also, I don't generally use an IDE, because I'm happy with an editor in one shell and another to compile and run. I do use an IDE when making terrible Android apps and terrible Arduino kinds of things; they're useful, but I can't focus in an IDE the way I can in a terminal text editor, and I've got 25 years of experience in the text editor, so I'm not planning to change.