Live data from Hacker News

Structured Errors in Go (2022)

southcla.ws

71–75 of 75 posts

Re: Structured Errors in Go (2022)

#71
post #69

Earlier quoted context omitted.

> t is deeply inefficient. As() traverses the cause tree recursively for every single error, so if you have 30 possible error types to compare, and an error typically wraps 3 layers deep, that's a worst case of 30 loop iterations with 90 cases ... I have no idea how you came to this conclusion. It's certainly not what happens when you call errors.As in your application code. There's no situation where your applicatio…

If you do this: var a, b, c error1, error2, error3 switch { case errors.As(&a): ... case errors.As(&b): ... case errors.As(&c): ... } …then yes, you will be doing 3 searches, each of which will do a loop (sometimes recursively if Unwrap() returns []error) over the chain of causes. > There's no situation where your application code would ever have 30 error types to compare against, if that were ever the case you have…

Any code that looks like that is almost certainly broken. If the things you're describing as "my opinion" are counter-indicated in the code that you're used to working with, then you're working with code that's seriously unconventional. Do with that feedback what you will.

Re: Structured Errors in Go (2022)

#72
post #71

Earlier quoted context omitted.

If you do this: var a, b, c error1, error2, error3 switch { case errors.As(&a): ... case errors.As(&b): ... case errors.As(&c): ... } …then yes, you will be doing 3 searches, each of which will do a loop (sometimes recursively if Unwrap() returns []error) over the chain of causes. > There's no situation where your application code would ever have 30 error types to compare against, if that were ever the case you have…

Any code that looks like that is almost certainly broken. If the things you're describing as "my opinion" are counter-indicated in the code that you're used to working with, then you're working with code that's seriously unconventional. Do with that feedback what you will.

You're wrong. You've not offered any evidence for why this is not just your opinion, and your claims are easily contradicted by examples.

As an example, say we have an API implemented on top of a complex data store. Every data store implementation can return errors like ObjectNotFound, InsufficientPermissions, and a dozen others. Every data store call can potentially return these. As well as, of course, standard Go errors like DeadlineExceeded or internal errors that cannot be exposed as user-facing API responses. However, some translation error has to translate those errors into API responses.

This cannot conveniently and consistently be done in each API handler, as it would repeat the same error translation for the same errors. An InsufficientPermissions error may happen in a "create" route as well as an in a "update" route, but also in any other route that deals with objects not being accessible.

Therefore it must be done in a central error translator. By definition. And this translation must either do a dozen+ Is() and As() calls, or it can be done efficiently, as I've described.

Anyway, I've said all I have needed to say and won't respond any further.

Re: Structured Errors in Go (2022)

#73
post #71

Earlier quoted context omitted.

Any code that looks like that is almost certainly broken. If the things you're describing as "my opinion" are counter-indicated in the code that you're used to working with, then you're working with code that's seriously unconventional. Do with that feedback what you will.

You're wrong. You've not offered any evidence for why this is not just your opinion, and your claims are easily contradicted by examples. As an example, say we have an API implemented on top of a complex data store. Every data store implementation can return errors like ObjectNotFound, InsufficientPermissions, and a dozen others. Every data store call can potentially return these. As well as, of course, standard Go e…

> it must be done in a central error translator. By definition. And this translation must either do a dozen+ Is() and As() calls, or it can be done efficiently, as I've described.

These claims are, bluntly, incorrect. There are no widely-used modules that work this way, and there are no properties of the language or its conventions that would suggest that this is a viable way to design an API. errors.Is and errors.As provide capabilities that type assertions -- as you've described -- factually do not provide. They're not equivalent, they're not normally used, they're not anything other than red flags in bad code that should be eliminated.

I'm not trying to pick a fight with you, I'm honestly just trying to prevent other people, reading this comment thread, from making the kinds of design mistakes that you're describing here as viable and efficient. They truly aren't.

Re: Structured Errors in Go (2022)

#74

Earlier quoted context omitted.

You need some kind of grouping otherwise any simple action ie. involving single io would require handling of dozens different classes.

Yes of course. That's why I mentioned polymorphism. A FileNotFoundException and NoDiskSpaceException can inherit from IOException for example. With polymorphic error classes, a caller can decide if they want to handle the different cases individually, or just catch the overarching IOException. All this flexibility comes for free when your use your language's type system, whereas with plain error codes you would have…

This classical, rigid OO way of thinking assumes there is single inheritance chain but that's not the case more often than not. For example i/o can have hierarchy based on operating system, kind of i/o (network, filesystem etc), access type (read/write), nature (idempotent etc), severity, abstraction (hardware, os, library, app levels), source (calee/caller errors or input/configuration/external service errors) etc.

Re: Structured Errors in Go (2022)

#75

Earlier quoted context omitted.

Yes of course. That's why I mentioned polymorphism. A FileNotFoundException and NoDiskSpaceException can inherit from IOException for example. With polymorphic error classes, a caller can decide if they want to handle the different cases individually, or just catch the overarching IOException. All this flexibility comes for free when your use your language's type system, whereas with plain error codes you would have…

This classical, rigid OO way of thinking assumes there is single inheritance chain but that's not the case more often than not. For example i/o can have hierarchy based on operating system, kind of i/o (network, filesystem etc), access type (read/write), nature (idempotent etc), severity, abstraction (hardware, os, library, app levels), source (calee/caller errors or input/configuration/external service errors) etc.

Ok, then use traits or composition instead of inheritance. Still using the type system, still better than hardcoding a complicated error code mapping system. I used inheritance as an example, my main point is to use the type system when dealing with... types of things.
Post reply on HN