Hacker News new | ask | show | jobs
by morelisp 1464 days ago
> It's not though, the return site is inside the go stdlib. I cannot annotate it with new methods.

    type httpClientError struct { error }
    func (err httpClientError) HTTPStatus() int { return 400 }
    func (err httpClientError) Unwrap() error { return err.error } // if needed
It's not even anything special around `error`, Go's entire type scaffolding is built around doing stuff like this.

> Do you just never actually need to classify an error?

Infrequently, and virtually never for errors types I didn't write myself (other than a tiny number of sentinels like UnexpectedEOF or DeadlineExceeded).

> Is it somehow weird to want to be able to provide a localized error to a user?

Yes, it's unusual for error details (rather than e.g. outcomes) to be localized for display directly to non-technical end users. This is also true of exception messages in Java. General-propose desktop client software is rarely written in either language.

I think you're too focused on the specific issue to see my general point about interface size.

1 comments

You've constructed 'httpClientError', but how do you end up using it in your program? You have "pkg/httputil" or whatever, and the methods in it return "(Response, error)", not "(Response, httpClientError)".

Even within your own program, you now have to read the code in "httputil" to understand what possible error types can be returned.

It's idiomatic to never return concrete error types, whether from the stdlib, or third party libraries, or even methods within your own program.

Even for types you do write yourself, you still have to either memorize what errors each method may return, or you have to constantly refer to docs or source code reading.

Clearly you think this is fine and go's type system is good enough for your use-cases, but every larger go program I've worked with, error handling has been painful since the errors are effectively untyped.

I assume we must have worked on different types of go projects if you haven't run into pain with this.

> Yes, it's unusual for error details (rather than e.g. outcomes) to be localized

I absolutely agree that it's outcomes which are localized, but to determine _outcomes_, you have to classify errors. If the _outcome_ is "File doesn't exist", that's a different error than "permission denied", so you need to classify. But the type you have is "error", so you have to constantly refer to docs.

As I already mentioned - Go has largely unbounded error types, not because `error` is a small interface, but because of how many interfaces get nested (a tar.Writer wrapping a gzip.Writer wrapping an hdfs.Writer etc.). If you don't like this, fine, we'll have to agree to disagree about the value of checked exceptions vs. massively leaking implementation details.

Nonetheless, Go does provide ways to check whether an error either is or can do what you want, and ways to annotate errors with logic specific to your program. An `httpClientError` is an `error`. When you get an error from a source you want to treat as a 400, you wrap it and return it, as an `error`. You use `errors.As` on it as a concrete type, or an `interface { HTTPStatus() int }`, to use the method you've added.

Regarding localization, which is a significantly different problem - the outcome is e.g. "file can't be opened". It's hard to write good error messages based on the language's error messages but this is not a Go-specific problem at all. Either you constrain your operations to the point you can bound all your error types, or you don't and report the outcome + raw message instead of trying to localize causes. And yes, this is an unusual space to be using Go or Java.

Since you mentioned Rust, we could also consider how it solves the problem - `Write` returns a `Result<usize, io::Error>` - `io::Error` has a (almost uselessly long and yet still) non-exhaustive `ErrorKind` - the last of which is `Other`, "used to construct your own Errors that do not match any ErrorKind." I.e. even in Rust's type system, they punted because otherwise you can't easily compose anything.