19 ms·
> Returning bogus values alongside errors. Unless documented otherwise, a non-nil error renders all other return values invalid, so there's no real sense of a
by kiitos 1y ago
> Returning bogus values alongside errors.
Unless documented otherwise, a non-nil error renders all other return values invalid, so there's no real sense of a "bogus value" alongside a non-nil error.
> Designing the error mechanism based on the assumptions that errors are primarily meant to be logged and that you have to get out of your way to develop errors that can actually be handled
I don't see how any good-faith analysis of Go errors as specified/intended by the language and its docs, nor Go error handling as it generally exists in practice, could lead someone to this conclusion.
- packetlost 1y ago> Unless documented otherwise, a non-nil error renders all other return values invalid, so there's no real sense of a "bogus value" alongside a non-nil error But you have to return something to satisfy the function signature's type, which often feels bad. >> Designing the error mechanism based on the assumptions that errors are primarily meant to be logged and that you have to get out of your way to develop errors that can actually be handled > I don't see how any good-faith analysis of Go errors as specified/intended by the language and its docs, nor Go error handling as it generally exists in practice, could lead someone to this conclusion. I agree to a point, but if you look at any random Go codebase, they tend to use errors.New and fmt.Errorf which do not lend themselves to branching on error conditions. Go really wants you to define a type that you can cast or switch on, which is far better.
- kiitos 1y ago> Go really wants you to define a type that you can cast or switch on, which is far better. Go very very much does not want application code to be type-asserting the values they receive. `switch x.(type)` is an escape hatch, not a normal pattern! And for errors especially so! > they tend to use errors.New and fmt.Errorf which do not lend themselves to branching on error conditions You almost never need to branch on error conditions in the sense you mean here. 90% of the time, err != nil is enough. 9% of the time, errors.Is is all you need, which is totally satisfied by fmt.Errorf.
- stouset 1y ago> 90% of the time, err != nil is enough Only if your only desire is to bubble the error up and quite literally not handle it at all. If you want to actually handle an error, knowing what actually went wrong is critical.
- kiitos 1y agoReturning an error -- or, more accurately, identifying an error and returning an annotation or transformation of that error appropriate for your caller -- is a way of handling it. The cases where, when your code encounters an error, that it can do anything other than this are uncommon.
- stouset 1y agoThis goes completely against the golang error-handling mindset. Error handling is so important, we must dedicate two-thirds of the lines of every golang program to it. It is so important that it must be made a verbose, manual process. But there's also nothing that can be done about most errors, so we do all this extra work only to bubble errors up to the top of the program. And we do all this work as a human exception-handle to build up a carefully curated manual stack trace that loses all the actually-useful elements of a stack trace like filenames and line numbers.
- tinthedev 1y agoThis is just nonsense. Handling errors this way is possible in only very brittle and simplistic software. I mean, you're contradicting your very own argument. If this was the primary/idiomatic way of handling errors... then Go should just go the way of most languages with Try/Catch blocks. If there's no valuable information or control flow to managing errors... then what's the point of forcing that paradigm to be so verbose and explicit in control flow? None.
- packetlost 1y ago> Go very very much does not want application code to be type-asserting the values they receive. `switch x.(type)` is an escape hatch, not a normal pattern! And for errors especially so! A type assert/switch is exactly how you implement Error.Is [^0] if you define custom error types. Sure it's preferable to use the interface method in case the error is wrapped, but the point stands. If you define errors with Errors.New you use string comparison, which is only convenient if you export a top level var of the error instead of using Errors.New directly. > You almost never need to branch on error conditions in the sense you mean here. 90% of the time, err != nil is enough. 9% of the time, errors.Is is all you need, which is totally satisfied by fmt.Errorf. I'd argue it's higher than 9% if you're dealing with IO, which most applications will. Complex interfaces like HTTP and filesystems will want to retry on certain conditions such as timeouts, for example. Sure most error checks by volume might be satisfied with a simple nil check, it's not fair to say branching on specific errors is not common. [0]: The documentations own example of implementing Error.Is uses a switch. https://cs.opensource.google/go/go/+/refs/tags/go1.24.3:src/syscall/syscall_unix.go;l=120 https://cs.opensource.google/go/go/+/refs/tags/go1.24.3:src/... It happens to be a syscall interface so errors are reported as numbers.
- stouset 1y ago> Unless documented otherwise, a non-nil error renders all other return values invalid, so there's no real sense of a "bogus value" alongside a non-nil error. Ah yes the classic golang philosophy of “just avoid bugs by not making mistakes”. Nothing stops you from literally just forgetting to handle ann error without running a bunch of third party linting tools. If you drop an error on the floor and only assign the return value, go does not care.
- kiitos 1y agoI know..! Ignoring an error at a call site is a bug by the caller, that Go requires teams to de-risk via code review, rather than via the compiler. This is well understood and nobody disputes it. And yet all available evidence indicates it's just not that big of a deal and nowhere near the sort of design catastrophe that critics believe it to be. If you don't care or don't believe the data that's fine, everyone knows your position and knows how dumb you think the language is.
- Yoric 1y ago(not the GP) Indeed, while not being a fan of how this aspect of Go, I have to admit that it seldom causes issues. It is, however, part of the reasons for which you cannot attach invariants to types in Go, which is how my brain works, and probably the main reasons for which I do not enjoy working with Go.
- kiitos 1y agoYeah, I mean, Go doesn't see types as particularly special, rather just as one of many tools that software engineers can leverage to ship code that's maintainable and stands the test of time. If your mindset is type-oriented then Go is definitely not the language for you!
- stouset 1y ago> all available evidence Where is this evidence? Where is the data that I am supposed to believe?
- Yoric 1y ago> I don't see how any good-faith analysis of Go errors as specified/intended by the language and its docs, nor Go error handling as it generally exists in practice, could lead someone to this conclusion. Let me detail my claim. Broadly speaking, in programming, there are three kinds of errors: 1. errors that you can do nothing about except crash; 2. errors that you can do nothing about except log; 3. errors that you can do something about (e.g. retry later, stop a different subsystem depending on the error, try something else, inform the user that they have entered a bad url, convert this into a detailed HTTP error, etc.) Case 1 is served by `panic`. Case 2 is served by `errors.New` and `fmt.Errorf`. Case 3 is served by implementing `error` (a special interface) and `Unwrap` (not an interface at all), then using `errors.As`. Case 3 is a bit verbose/clumsy (since `Unwrap` is not an interface, you cannot statically assert against it, so you need to write the interface yourself), but you can work with it. However, if you recall, Go did not ship with `Unwrap` or `errors.As`. For the first 8 years of the language, there was simply no way to do this. So the entire ecosystem (including the stdlib) learnt not to do it. As a consequence, take a random library (including big parts of the stdlib) and you'll find exactly that. Functions that return with `errors.New`, `fmt.Errorf` or just pass `err`, without adding any ability to handle the error. Or sometimes functions that return a custom error (good) but don't document it (bad) or keep it private (bad). Just as bad, from a (admittedly limited) sample of Go developers I've spoken to, many seem to consider that defining custom errors is black magic. Which I find quite sad, because it's a core part of designing an API. In comparison, I find that `if err != nil` is not a problem. Repeated patterns in code are a minor annoyance for experienced developers and often a welcome landscape feature for juniors.
- kiitos 1y agoAgain, you don't need to define a new error type in order to allow callers to do something about it. Almost all of the time, you just need to define an exported ErrFoo variable, and return it, either directly or annotated via e.g. `fmt.Errorf("annotation: %w", ErrFoo)`. Callers can detect ErrFoo via errors.Is and behave accordingly. `err != nil` is very common, `errors.Is(err, ErrFoo)` is relatively uncommon, and `errors.As(err, &fooError)` is extraordinarily rare. You're speaking from a position of ignorance of the language and its conventions.