[HN Gopher] How we centralized and structured error handling in ...
       ___________________________________________________________________
        
       How we centralized and structured error handling in Golang
        
       Author : thunderbong
       Score  : 153 points
       Date   : 2024-12-18 03:04 UTC (19 hours ago)
        
 (HTM) web link (olivernguyen.io)
 (TXT) w3m dump (olivernguyen.io)
        
       | omeid2 wrote:
       | This is a cry for sum types.
        
       | nikolayasdf123 wrote:
       | > centralized system [... for errors]
       | 
       | dont think this will scale. errors are part of API. (especially
       | Go mantra errors are values https://go.dev/blog/errors-are-values
       | it is ever more prominent). and each API is responsibility of a
       | service
       | 
       | so unless you deal with infrastructure or standards/protocols
       | layer (say you define what HTTP 500 means or common pattern for
       | URL paths in your API), then better not couple all services.
       | those standards are very minimal and primitive that works for
       | everything, which is opposite what you doing here aggregating all
       | the specifics into single place
        
       | vrosas wrote:
       | Trying to shoehorn code errors into HTTP errors is a prime
       | example of conflating two very different things because sometimes
       | they look similar. Let different things be different, I like to
       | say. You either let your HTTP handlers do their own error-to-
       | http-code management or you end up with a massive switch
       | statement trying to map them all, or whatever monstrosity this
       | approach is.
       | 
       | Also the entire problem of the OP would go away if they just
       | implemented opentelemetry tracing to their logs.
        
         | pluto_modadic wrote:
         | ah, yes, completely separate.
         | 
         | HTTP code: 200 ok
         | 
         | Body: {"error":"internal server error"}
        
           | bb88 wrote:
           | My favorite example of this was renaming a 500 error due to
           | an unhandled exception to a 400 error to make it look like it
           | was the error of the caller. Management was also possibly
           | tracking 500 errors too, so the 400 could also have been
           | gaming the system.
           | 
           | In some mental models, though, it did make sense.
           | Particularly the one that went, "Well, we never would have
           | errored, if you never called us!"
        
             | xmprt wrote:
             | It's somewhat fair though. If there's a case that would
             | cause errors for the system and it's a case that you're not
             | supposed to handle, then a 400 error sounds perfect for
             | that case. For example, if you have a service and it
             | panics/returns 500 when you pass in an empty user id, then
             | you could instead return a 400 before you hit the panic and
             | all is good.
        
               | bb88 wrote:
               | Normally you should attempt to find all the corner cases
               | and present the errors to the user -- before processing
               | the request. If you can't do this, it's time to rethink
               | how your api works. A good api is simple to use and
               | simple to write.
               | 
               | It also simplifies your business logic in that all the
               | possible user defined idiocies are caught before your
               | business logic actually processes the request.
               | 
               | Some frameworks do this better than others. And rather
               | than documentation, I tend to prefer comprehensive error
               | messages.
        
               | fmbb wrote:
               | > Normally you should attempt to find all the corner
               | cases and present the errors to the user -- before
               | processing the request.
               | 
               | That is what they are suggesting. You check the request
               | and return 400 if it's bad.
        
               | bb88 wrote:
               | One example of a 500 error is a null pointer error. Was
               | it a bad request or a logic error? One is your problem
               | the other is not. Just returning a 400 hides that issue.
               | Validating the payload before processing it simplifies
               | the issue for everyone involved.
               | 
               | A 500 error should be your problem with a stack trace in
               | the log. A 400 error should provide enough description to
               | tell the user it's theirs and how to fix it.
               | 
               | Just marking recoding a 500 to a 400 because of a null
               | pointer error would get noticed on a code review and
               | marked up on a code review.
        
               | bdangubic wrote:
               | 400 - you fucked up
               | 
               | 500 - we fucked up
        
           | saghm wrote:
           | Honestly, my controversial take is that for APIs, it would be
           | cleaner to not use any HTTP status codes other than 200 and
           | have all of the semantics in the body of the response. I'm
           | sure someone smarter than me will jump in and explain why
           | this wouldn't work in practice, but it just feels like
           | application semantics are leaking from a much more natural
           | location in the body of the response. I feel similarly about
           | HTTP request methods other than POST in APIs; between the
           | endpoint route and the body, there should be more than enough
           | room to express the difference between POST, PATCH, and
           | DELETE without needing them to be encoded as separate HTTP
           | methods.
        
             | tayo42 wrote:
             | Your kind of describing things like thrift and other rpc
             | servers?
        
               | saghm wrote:
               | Possibly. I'm not sure why it should require switching to
               | an entirely different protocol though; my point is that
               | making an API that only uses POST and always returns 200
               | is something that already works in HTTP though, and I
               | have trouble understanding why that isn't enough for
               | pretty much everything.
        
               | tayo42 wrote:
               | You need some kind of structured way to describe the
               | action to take, what the result is or what the error is.
               | so the client and server can actually parse the data.
               | that's the protocol, whether its something formal like
               | rpc libraries, or "REST"-ish or w/e
               | 
               | json-rpc is probably what your describing over http,
               | maybe if you squint enough graphql too
        
               | lll-o-lll wrote:
               | You are thinking like a developer, but there is a world
               | of networking as well. Between your client and server
               | will be various bits of hardware that cannot speak the
               | language you invent. 200, 401, 500 -- these are not for
               | the use of the application developer -- but rather the
               | infrastructure engineer.
        
               | necovek wrote:
               | You lose some benefits of features already implemented by
               | existing HTTP clients (caching, redirection,
               | authorization and authentication, cross-origin
               | protections, understanding the nature of the error to
               | know that this request has failed and you need to try
               | another one...).
               | 
               | It's is certainly not comprehensive, but it's right there
               | and it works.
               | 
               | Moving to your own solution means that you have to
               | reimplement all of this in every client.
        
               | Joker_vD wrote:
               | > understanding the nature of the error to know that this
               | request has failed and you need to try another one...
               | 
               | Please elaborate. In my experience, most of HTTP client
               | libraries do not automatically retry any requests, and
               | thank goodness for that since they don't, and can't, know
               | whether such retries are safe or even needed.
               | 
               | > redirection
               | 
               | An example of service where, at the higher business logic
               | level, it makes sense to force the underlying HTTP
               | transport level to emit a 301/302 response, would be
               | appreciated. In my experience, this stuff is usually
               | handled in the load-balancing proxy before the actual
               | service, so it's similar to QoS and network management
               | stuff: the application does not care about it, it just
               | uses TCP.
        
               | kaoD wrote:
               | Something being "enough" doesn't mean it's optimal.
               | There's a huge stack of tools that speak HTTP semantics
               | out of the box; including the user agent, i.e. the
               | browser (and others), but also stuff like monitoring
               | tools, proxies, CORS, automation tools, web scrapers...
               | 
               | You don't need to reinvent HTTP semantics when HTTP is
               | already there, standard, doing the right thing,
               | compatible with millions of programs all across the
               | stack, out of the box.
               | 
               | HTTP is so well designed it almost makes me angry when
               | people try to sidestep it and inevitably end up causing
               | pain in the future due to some subtle semantic detail
               | that HTTP does right and they didn't even think to
               | reimplement.
               | 
               | And the only solution to such issues (as they arise, and
               | they will) is to slowly reimplement HTTP across the whole
               | stack: oh, you need to monitor your internal server
               | errors? Now you have to configure your monitoring tool
               | (or create your own) to inspect all your response bodies
               | (no matter how huge) and parse their JSON (no matter how
               | irrelevant) instead of just monitoring the status code in
               | the response header and easily ignore the expensive body
               | parsing.
               | 
               | Even worse when people go all the way. If we don't need
               | status codes, why do we need URLs at all? Just POST
               | everything to /api/rpc with an `operation` payload.
               | Congrats, none of your monitoring tools can easily
               | calculate request rates by operation without some
               | application-specific configuration (I wish this was a
               | made up scenario).
               | 
               | Just use HTTP ffs. You'd need a very good reason not to
               | use it.
        
             | evnix wrote:
             | This is the way to go, pretty much solves, 404 resource not
             | found or route not found. But you will get laughed at by so
             | called architectural dogmatists. Remember we aren't really
             | doing REST, it's just RPC and let's call it that.
             | 
             | Shoehorning http protocols error codes as application error
             | codes, drinking the cool aid and calling it best practice
             | is beyond bizzare.
        
               | marcus_holmes wrote:
               | Agree. "200 - successfully failed to do the thing" is
               | valid and useful.
               | 
               | 500 is "failed to do anything at all"
        
             | omcnoe wrote:
             | There are a lot of useful network monitoring tools that can
             | analyze HTTP response codes out of the box. They can't do
             | this for your custom application error format. You don't
             | have to go crazy with it, but supporting at least
             | 200/400/500 makes it so much easier to monitor the health
             | of your services.
        
             | bilbo-b-baggins wrote:
             | Go ahead try to implement something like cross-origin
             | requests or multipart encoded form uploads just using the
             | body semantics you described. I'll wait.
             | 
             | Also that is not a controversial take. It is at best a
             | naive or inexperienced take.
        
               | saghm wrote:
               | Both of those happen in the context of web browsing
               | rather than existing in APIs in a vaccuum; I'd argue that
               | there's absolutely no reason why the mechanism used to
               | request a webpage from a browser needed to be identical
               | to the mechanism used for the webpage to perform those
               | actions dynamically, which is pretty much my whole point:
               | it doesn't seem obvious to me that it's useful to encode
               | all of that information in an API that isn't also being
               | used to serve webpages. If you are serving webpages, then
               | it makes sense to use semantics that help with that, but
               | I can't imagine I'm the only one who's had to deal with
               | bikeshedding around this sort of thing in APIs that
               | literally only are used for backends.
        
               | yxhuvud wrote:
               | Multipart messages definitely happens in APIs as well, if
               | you are handling blobs that are potentially pretty big.
        
             | turbojet1321 wrote:
             | I'm sympathetic, but this can have issues if you want your
             | API to be used by anything other than your own client,
             | including stuff like logging middleware. A lot of tools
             | inherently support/understand HTTP status codes, so
             | building on top of that can make integration a lot easier.
             | 
             | We, very roughly, do it like this:
             | 
             | - 200: all good
             | 
             | - 401: we don't know who you are
             | 
             | - 403: you're not allowed to do that
             | 
             | - 400: something's wrong and you can fix it
             | 
             | - 500: something's wrong and you can't fix it
             | 
             | Each response (other than 401) includes a json blob with
             | details that our UI can do something with, but any other
             | consumer of the API or HTTP traffic still knows roughly
             | what's going on.
             | 
             | I've worked in places where we really sweated on getting
             | the perfect HTTP status codes, and I'm not sure it added
             | much benefit.
             | 
             | On POST - I find myself doing logical GETs with POST a lot,
             | because the endpoint requires more information than can be
             | conveyed in URL params. It makes me feel dirty, and it's
             | obviously not RESTful but you know - sometimes you just
             | have to get things done.
        
               | brabel wrote:
               | You've just described basically everything a dev needs to
               | know to implement HTTP APIs that report status codes
               | properly, yet some people still seem to think it's oh so
               | complicated. What has gone wrong?
        
               | turbojet1321 wrote:
               | I can understand how people might look at all the full
               | list status codes and think it's all too hard, but yes,
               | once you realize that there are only a handful you need
               | _most of the time_ it all becomes pretty simple.
        
               | saghm wrote:
               | Sure, but the problem in my opinion is that while the
               | handful that you pick is totally reasonable, someone else
               | might pick a slightly different handful that's just as
               | reasonable. If I want to use a new API and delete a user,
               | how do I know if it uses DELETE or POST, and if it will
               | return 401 or 403? At best, you'll be able to skim
               | through the documentation more quickly due to having
               | encountered similar conventions before, but nothing stops
               | that from happening in terms of request and response
               | bodies either.
               | 
               | The fact that existing tooling relies on some of these
               | conventions is probably a good enough reason to do things
               | this way, but it's not obvious to me that this is because
               | it's actually better rather than inertia. Conventions
               | could be developed around the body of requests as well,
               | and at least to me, it doesn't seem obvious that the
               | amount of information conveyed at the HTTP
               | method/response status layer was necessary to try to
               | separate from the semantics of the request and response
               | bodies. I'm sure that a part of that was due to HTTP
               | supporting different content types for payloads, but
               | nowadays it seems like quite a lot of the common
               | alternatives to JSON APIs were designed not to even use
               | HTTP (GraphQL, gRPC, etc.), which I'd argue is evidence
               | that HTTP isn't necessarily being used as well for APIs
               | as some people would like.
               | 
               | To make something explicit that I've been alluding to,
               | everything I've said is about using APIs in HTTP, not
               | HTTP in the context of viewing webpages in a browser. It
               | really seems like a lot of the complications in HTTP are
               | due to it trying to be sufficient for both browsers and
               | APIs, and in my opinion this comes mostly at the expense
               | of the latter.
        
               | bvrmn wrote:
               | It's quite unclear what's your point. HTTP APIs should
               | have minimal status code set. Parent described it
               | perfectly. It's simple, practical (especially from
               | monitoring perspective) and doesn't intervenes with a
               | service domain.
               | 
               | It seems you have some alternative in mind but it wasn't
               | presented.
        
               | saghm wrote:
               | I don't consider what the parent comment listed as
               | "minimal". The alternative I described is literally in my
               | initial comment; using only 200 for APIs is "minimal".
        
               | kdmtctl wrote:
               | Need an AI playground to paste error responses and fix
               | the code.
        
               | Izkata wrote:
               | > Each response (other than 401) includes a json blob
               | with details
               | 
               | ...until you discover an intermediate later strips the
               | body of certain error codes. Like apache, which IIRC
               | includes all 5xx and most 4xx.
        
             | lmm wrote:
             | Yeah, that's usually the pragmatic thing to do. Facebook
             | does that with their API, for example.
             | 
             | 4xx or 5xx gets you the default HTTP handling for that kind
             | of error. Occasionally - especially in small examples -
             | that default handling does what you want and saves you
             | duplicating a lot of work. More often it gets in your way.
             | 
             | I'd compare it to browser default styling - in small
             | examples it sounds useful, but in a decent-sized site you
             | just end up having to do a "CSS reset" to get it out of the
             | way before you do your styling.
        
             | JodieBenitez wrote:
             | > Honestly, my controversial take is that for APIs, it
             | would be cleaner to not use any HTTP status codes other
             | than 200 and have all of the semantics in the body of the
             | response.
             | 
             | We've been doing that for 20 years with json-rpc 1.0
             | --> { "method": "echo", "params": ["Hello JSON-RPC"],
             | "id":1}         <-- { "result": "Hello JSON-RPC", "error":
             | null, "id": 1}
             | 
             | In this context, HTTP is just the transport and HTTP errors
             | are only transport errors.
             | 
             | Yes, you throw away lots of HTTP goodies with that, but
             | there are many situations where it makes more sense than
             | some half-assed ReSTish API. YMMV.
        
             | LouisSayers wrote:
             | I like to find a middle ground.
             | 
             | I use http status codes to encode how the _request_ was
             | handled, not necessarily the data within the request.
             | 
             | A 400 if you send mangled JSON, but a 200 if the request
             | was valid but does not pass business validation rules.
             | 
             | Inside the 200 response is structured JSON that also has a
             | status that is relevant at the application level.
             | 
             | Otherwise how can for example you tell if a 404 response is
             | because the endpoint doesn't exist, or because the item
             | requested at the endpoint doesn't exist?
             | 
             | I believe it's important to have a separation between what
             | is happening at the API level vs Application, and this
             | approach caters for both.
        
               | bvrmn wrote:
               | > A 400 if you send mangled JSON, but a 200 if the
               | request was valid but does not pass business validation
               | rules.
               | 
               | What about empty required field in JSON? Is it still
               | mangled or it's already BL?
        
               | LouisSayers wrote:
               | As it's not to do with the http request and the body was
               | able to be parsed, in my book that'd be classified as
               | being at the application level, so results in a 200
               | status with a JSON response detailing the issue
               | 
               | 200 OK {status: "failed", errors: ["field X is
               | required"]}
               | 
               | How you deal with this on the application side, what JSON
               | statuses you have etc is up to you.
        
             | kdmtctl wrote:
             | Anecdotally the color codes make life much easier when
             | debugging a new API. You instantly see that's something is
             | wrong. If everything is green you don't realize that
             | something is wrong until you carefully read a uniquely
             | structured custom response. Saves a lot of effort.
        
             | int_19h wrote:
             | One reason for using HTTP verbs is to distinguish between
             | queries and updates, and for the latter, between idempotent
             | and non-idempotent updates. This in turn makes it possible
             | to do things like automatically retry queries on network
             | errors or cache responses where it is safe to do so.
        
           | maccard wrote:
           | Think about what the client code looks like to handle this
           | and the alternative, particularly if you're implementing an
           | sdk and the api is an implementation detail. I'm not saying I
           | would choose this path, but it certainly reduces the amount
           | of code on both sides that you have to write.
        
           | ranger207 wrote:
           | If HTTP is your API's transport layer, then HTTP errors
           | should be related to problems with the transport layer and
           | not to API itself. Is the internal server error caused by a
           | bad HTTP request or a bad API request?
        
         | rollulus wrote:
         | The error to code in the http handler is the true path. It's
         | the only place where the context and knowledge is about
         | semantics. In one endpoint if something is not found it can be
         | a proper 404, if its existence is truly optional. In another
         | endpoint the absence might very well qualify as a 500.
        
           | bvrmn wrote:
           | 404 is quite an ominous thing. 404 because route is not found
           | or entity not found. God bless your monitoring.
        
             | rounce wrote:
             | 422 is frequently used for this case despite being part of
             | the WebDAV extensions.
        
         | bedobi wrote:
         | The sane thing to do is to let lower layer functions return
         | Either<Error, Foo>
         | 
         | then, in the HTTP layer, your endpoint code just looks like
         | return fooService                     .getFoo(
         | fooId                     ) //Either<Error, Foo>
         | .fold({ //left (error) case                         when (it) {
         | is GetFooErrors.NotAuthenticated -> {
         | Response.status(401).build() }                             is
         | GetFooErrors.InalidFooId -> { Response.status(400).build() }
         | is GetFooErrors.Other -> { Response.status(500).build() }
         | }                     }, { //right case
         | Response.ok(it)                     })
         | 
         | the benefits of this are hard to overstate.
         | 
         | * Errors are clearly enumerated in a single place. * Errors are
         | clearly separated from but connected to HTTP, in the
         | appropriate layer. (=the HTTP layer) Developers can tell from a
         | glance at the resource method what the endpoint will return for
         | each outcome, in context. * Errors are guaranteed to be
         | exhaustively mapped because Kotlin enforces that sealed classes
         | are exhaustively mapped at compile time. So a 500 resulting
         | from forgetting to catch a ReusedPasswordException is
         | impossible, and if new errors are added without being mapped to
         | HTTP responses, the compiler will let us know.
         | 
         | It beats exceptions, it beats Go's shitty error handling.
        
       | rednafi wrote:
       | Go's error handling is still cumbersome and lacking. I love
       | writing Go but I don't want to ever adopt anything like. It's
       | bending over backwards to achieve something sum types provide and
       | this pattern is a mess.
        
         | solumunus wrote:
         | I would be all over Go with a better type system or exceptions.
        
           | gf000 wrote:
           | C#, OCaml, Java, Scala, Kotlin all fulfill these
           | requirements, while targeting the same niche.
        
             | solumunus wrote:
             | Yes there are indeed lots of languages in existence.
        
             | LinXitoW wrote:
             | Go has insanely good tooling and very fast single binary
             | compiling.
             | 
             | While all these languages (afaik) can reach similar levels
             | of functionality (GraalVM e.g.), it's more work. As much as
             | I hate the language Go, I can't deny how braindead simple
             | it is to just make a tool with it. I don't need to choose a
             | build tool, or a runtime version, there's a library for
             | everything and most developers with more than a room temp
             | IQ can immediately start working on it.
             | 
             | The only other language that currently comes close is Rust.
             | If only they had stuck to using a GC, I'd be in heaven.
        
           | thiht wrote:
           | If Go ever adds exceptions[1] as an error handling mechanism,
           | I'm out. Value errors are far superior to exceptions, even in
           | their current state in Go.
           | 
           | [1]: assuming panics are not an error handling mechanism but
           | a recovery mechanism
        
             | solumunus wrote:
             | Madness.
        
             | jdbxhdd wrote:
             | Panics can be values and errors don't have to be values in
             | go?
             | 
             | I think you are missing up concepts here
        
               | thiht wrote:
               | I'm not talking about the underlying model, I'm talking
               | about the control flow. What I mean is errors are
               | explicit values belonging to the signature of the
               | functions.
        
             | LinXitoW wrote:
             | Checked Exceptions are nothing but errors as values with
             | some syntactic sugar for the most common use case (bubbling
             | up the error).
             | 
             | Gos version of value errors is just micrometers ahead of C
             | style error codes. In both cases you get told "there could
             | be an error", the error is a value of one single type
             | (error/int), and you have to manually find out which
             | different errors this value could represent.
             | 
             | If you want to know what you're missing, check out Rusts
             | error handling.
        
               | thiht wrote:
               | I love Rust error handling and wish it would have been
               | the default in Go (too late to shoehorn it), but it has
               | nothing to do with exceptions.
        
         | KingOfCoders wrote:
         | I thought so too, after years with Scala and Rust. Now I think
         | (X, error) is fine, indeed I think it is great for it's
         | simplicity. I might want to have a safe assignment
         | // x() (X,error)        x != x()         // x is X        //
         | return on error
         | 
         | But the urge is not very high.
        
           | dontlaugh wrote:
           | The problem is indeed composition. How do I chain 3 calls
           | that short-circuit on the first error? In Go that's verbose
           | in the extreme. With exceptions it's easy to miss an error.
           | Sum type errors have neither problem.
        
       | winstonp wrote:
       | I agree Go error handling is unoptimal, but this is simply not
       | the right approach. This essentially turns error handling into a
       | whole other language, almost like how Ginkgo is a separate
       | language for handling tests.
        
         | wruza wrote:
         | And most languages are lacking this useful error language. You
         | can't speak if you have no language, so having it must be a
         | good thing.
         | 
         | The only questionable thing here is that this framework is not
         | a part of the main language still, which means near zero
         | adoption. But that train has sailed.
        
       | jaza wrote:
       | Feels like OP is basically implementing exceptions and exception
       | handling at the application level. If this is what you want, then
       | why not just switch to one of the many other languages that has
       | exceptions built in at the language level?
        
         | KingOfCoders wrote:
         | I think they use too many sentinel errors [0] I have been doing
         | Java for two decades, and I thought you need to handle
         | individual errors by type. Using Go, I've learned from the code
         | I write, 90%+ of errors I don't need to handle individually, or
         | I can't do anything except bubble an error up. There is the
         | rare case (10%) when a file does not exist, and I try to read
         | an alternative one and I don't bubble up an error.
         | 
         | For customer support I also found it much easier, instead of an
         | error number, print a UUID that customers can give to support,
         | and that UUID (Request ID) then can be found in the logs to
         | find out what happened by developers.
         | 
         | [0]:https://dave.cheney.net/2016/04/27/dont-just-check-errors-
         | ha...
        
           | RVuRnvbM2e wrote:
           | This was exactly my train of thought. I even went looking for
           | Dave's blog post about it before I saw your comment. :D
        
           | gf000 wrote:
           | > Using Go, I've learned from the code I write, 90%+ of
           | errors I don't need to handle individually, or I can't do
           | anything except bubble an error up
           | 
           | So... exceptions are better, because they would do the
           | correct thing by default in the majority of cases?
        
             | lordofgibbons wrote:
             | Unless you forget to catch the right type of exception.
             | Then all hell breaks loose.
        
               | bigstrat2003 wrote:
               | People bitch about checked exceptions in Java but this is
               | precisely why I think they're a great idea. You can't
               | forget to catch the right type of exception.
        
               | lomnakkus wrote:
               | No, but you can easily end up missing some because
               | somebody wrapped them in some sub-type of
               | RuntimeException because they were forced(!) to. This
               | happens all the time because the variance on throws
               | clauses it at odds with the variance of method signatures
               | (well, implementations, really -- see below).
               | 
               | A new implementation of a ThingDoer usually needs to do
               | something more/different from a StandardThingDoer... and
               | so may need to throw more types of exceptions. So you end
               | up having to wrap exceptions ... but now they don't get
               | caught by, say, catch(IOException exc). If you're lucky
               | you own the ThingDoer interface, but now you have a
               | different problem: It's only JDBCThingDoer which can
               | throw SQLException, so why does code which only uses a
               | StandardThingDoer (via the ThingDoer interface) need to
               | concern itself with SQLException?
               | 
               | Checked exceptions in Java are worse than useless -- they
               | actively make things _worse_ than if there were only
               | unchecked exceptions. (Because they sometimes force the
               | unavoidable wrapping -- which _every_ place where
               | exceptions are caught needs to deal with somehow... which
               | no help from the standard  "catch" syntax.)
        
               | iainmerrick wrote:
               | One thing you can do in Java is parameterise your
               | interface on the exception type. That way, if the
               | implementation finds it needs to handle some random
               | exception, you can expose that through the interface --
               | e.g. "class JDBCThingDoer implements
               | ThingDoer<SQLException>". Helper classes and functions
               | can work with the generic type, e.g. "<E> ThingDoer<E>
               | thingDoerLoggingWrapper(ThingDoer<E> impl)".
               | 
               | I think this works really well to keep a codebase with
               | checked exceptions tractable. I've always been surprised
               | that I never saw it used very often. Anyone have any
               | experience using that style?
               | 
               | I guess it's not very relevant any more because checked
               | exceptions are sadly out of fashion everywhere. I haven't
               | done any serious Java for a while so I'm not on top of
               | current trends there.
        
               | lomnakkus wrote:
               | In a former life I worked with a codebase that used that
               | style. Let's just say it isn't enough.
        
               | iainmerrick wrote:
               | Can you remember what sort of problems you were hitting?
        
               | int_19h wrote:
               | How do you handle the situation where the code might need
               | to throw (pre-existing) exceptions that don't share a
               | useful base class?
        
               | iainmerrick wrote:
               | I don't remember! Possibly that's one of the cases where
               | it doesn't work out.
               | 
               | Of course, if you had proper sum types, that situation
               | wouldn't be a problem.
        
               | pjmlp wrote:
               | Additional info, they predate Java, having made an
               | appearance in CLU, Modula-3 and C++, before Java was
               | invented.
               | 
               | I miss them in other languages every time I need to track
               | down an unhandled exception in a production server.
        
               | Tempest1981 wrote:
               | >> People bitch about checked exceptions
               | 
               | > they predate Java, having made an appearance in CLU,
               | Modula-3 and C++
               | 
               | Checked exceptions in C++? Can you force/require the call
               | chain to catch an exception in C++? At compile time?
        
               | pjmlp wrote:
               | That was part of the idea behind them yes, as many things
               | in WG21 design process, reality worked out differently,
               | and they are no longer part of ISO C++ since C++17.
               | 
               | Although some want to reuse the syntax for value type
               | exceptions, if that proposal ever moves forward, which
               | seems unlikely.
        
               | Fire-Dragon-DoL wrote:
               | The problem is that a checked exception makes sense only
               | at a relatively high level of the app, but they are used
               | extensively at a low level
        
               | piva00 wrote:
               | My main gripe with checked exceptions is they create a
               | whole other possible code path on each `catch` clause. I
               | tend to keep checked exceptions to the absolute minimum
               | where they actually make sense, all the rest are
               | RuntimeExceptions that should bubble up the stack.
        
               | LinXitoW wrote:
               | But so would every single other method to react to
               | different types of errors, no?
               | 
               | In something like go, you're even required to create the
               | separate code path for EVERY SINGLE erroring line, even
               | if your intention is simply to bubble it up.
        
               | KarlKode wrote:
               | That's kind of how you do it in go. Either:
               | 
               | 1. Bubble up error (as is/wrapped/different error. 2.
               | Handle error & have a (possibly complex) new code path.
               | 
               | There's also the panic/recover that sometimes is misused
               | to emulate exceptions.
        
               | evantbyrne wrote:
               | That would be true if not for Java making the critical
               | mistake of excluding RuntimeException from method
               | definitions, so in-practice people just extend
               | RuntimeException to keep their methods looking "clean".
        
               | bbatha wrote:
               | Or are forced to because they want to use generics or
               | lambdas.
        
               | gf000 wrote:
               | Both work with checked exceptions.
        
               | int_19h wrote:
               | The problem is that there's no way to specify an
               | exception specification like "I propagate everything that
               | this lambda throws" (or, for generics, "that method M of
               | class C throws").
        
               | LinXitoW wrote:
               | The biggest issue with checked exceptions in modern Java
               | is that even the Java makers themselves have abandoned
               | them. They don't work well with any of the fancy
               | features, like Streams.
               | 
               | Checked Exceptions are nothing but errors as return
               | values plus some syntactic sugar to support the most
               | common response to errors, bubbling.
        
               | gf000 wrote:
               | > They don't work well with any of the fancy features,
               | like Streams.
               | 
               | Because that would require effect types, which is quite
               | advanced/at a research level currently.
        
               | ndriscoll wrote:
               | Scala's zio library basically gives you checked
               | exceptions that work with things like type inference,
               | streams, async operations, and everything else.
        
               | Yoric wrote:
               | Is it time to brag about Rust error-handling or should we
               | wait a little?
        
               | gf000 wrote:
               | It's certainly better than Go's (Go's is barely better
               | than C's and that's quite a low bar), but I don't think
               | that sum types are the global optimum.
               | 
               | Exceptions are arguably better from certain aspects, e.g.
               | defaulting to bubbling up, covering as small or wide
               | range as needed (via try-catch blocks), and auto-
               | unwrapping without plus syntax. So when languages with
               | proper effect types come into mainstream we might reach a
               | higher optimum.
        
               | LinXitoW wrote:
               | Maybe I'm too pessimistic, but Rust style error handling
               | feels like the global optimum under the constraint that
               | the average developer understand it.
               | 
               | Go is a language that exists purely because people saw
               | Monads in the horizon and, in their panic, went back to
               | monke, programming wise. Rust error handling is something
               | that even many Go fans have said is a good abstraction.
        
               | Yoric wrote:
               | No, sum types are certainly not a global optimum. But
               | they remain the best error-handling mechanism that I've
               | used professionally so far.
               | 
               | Effect types (and effect handlers) are very nice, but
               | they come with their own complexities. We'll see if some
               | mainstream language manages to make them popular.
        
               | xienze wrote:
               | You may be thinking a bit too much about what happens in
               | _Go_ when you forget to check for an error response from
               | a function -- the current function continues on with
               | (probably) incorrect/nil values being fed to subsequent
               | code. In Java when an uncaught exception is thrown, the
               | exception makes its way back up the call stack until it's
               | finally caught, meaning subsequent code is _not_
               | executed. It's actually a very orderly termination. In
               | any Java web framework (Spring et al) there's always a
               | centralized point at which exceptions are caught and
               | either built-in or user-specified code is used to
               | translate the error to an HTTP response.
               | 
               | This makes for much more pleasant code that is mostly
               | only concerned with the happy path, e.g., my REST
               | endpoint doesn't have to care if an exception is thrown
               | from the DAO layer as the REST endpoint will simply
               | terminate right then and there and the framework will map
               | the exception to a 500 error. Why anyone would prefer
               | Go's `if err != nil {}` error handling that must be added
               | All. Over. The. Place. at every single level of the
               | application is beyond me.
        
               | LinXitoW wrote:
               | My slightly snarky take is that liking Go is simply a
               | defensive reaction to one too many
               | AbstractFactoryBeanFactory. Too many abstractions
               | overloaded their "abstraction-insulin", so now they can
               | only handle minute amounts of abstraction.
        
               | gf000 wrote:
               | I liked your other comment's take with the monads better
               | :P
        
               | LinXitoW wrote:
               | Which Go doesn't fix either, because their errors are all
               | just "error", aka you can also forget to catch the right
               | type of error.
               | 
               | If only there was a way to combine optimizing the default
               | path (bubbling), and still provide information on what
               | errors exactly could happen. Something like a "?"
               | operator and a Result monad...
        
             | ceving wrote:
             | Exceptions are easier for the programmer. The programmer
             | has to write less and they clutter the code less. But
             | exceptions require stack traces. An exception without a
             | stack trace is useless. The problem with stack traces is:
             | they are hard to read for non-programmers.
             | 
             | On the other side Go's errors are more work for the
             | programmer and they clutter the code. But if you
             | consequently wrap errors in Go, you do not need stack
             | traces any more. And the advantage of wrapped errors with
             | descriptive error messages is: they are much easier to read
             | for non-programmers.
             | 
             | If you want to please the dev-team: use exceptions and
             | stack traces. If you want to please the op-team: use
             | wrapped errors with descriptive messages.
        
               | prirun wrote:
               | I tend to use "catch and re-raise with context" in Python
               | so that unexpected errors can be wrapped with a context
               | message for debugging and for users, then passed to
               | higher levels to generate a stack trace with context.
               | 
               | For situations where an unexpected error is retried, eg,
               | accessing some network service, unexpected errors have a
               | compressed stack trace string included with the context
               | error message. The compressed stack trace has the program
               | commit id, Python source file names (not pathnames) and
               | line numbers strung together, and a context error
               | message, like:
               | 
               | [#3271 a 25 b 75 c 14] Error accessing server xyz; http
               | status 525
               | 
               | Then the user gets an idea of what went wrong, doesn't
               | get overwhelmed with a lot of irrelevant (to them)
               | debugging info, and if the error is reported, it's easy
               | to tell what version of the program is running and
               | exactly where and usually why the error occurred.
               | 
               | One of the big reasons I haven't switched from Python to
               | Go for HashBackup (I'm the author) is that while I'd love
               | to have a code speed-up, I can't stomach the work
               | involved to add 'if err return err("blah")' after most
               | lines of existing code. It would hugely (IMO) bloat the
               | existing codebase.
        
               | ndriscoll wrote:
               | Messages and stack traces in the error are orthogonal to
               | errors-as-values vs. exceptions for control flow. You
               | could have `throw Exception("error fooing the bar",
               | ctx)`. You could also `return error("error fooing the
               | bar", ctx, stacktrace())`. Stack traces are also
               | occasionally useful but not really necessary most of the
               | time IME.
               | 
               | Go's error handling is annoying because it requires
               | boilerplate to make structured errors and gives you
               | string formatting as the default path for easy-to-create
               | error values. And the whole using a product instead of a
               | sum thing of course. And no good story for exception-like
               | behavior across goroutines. And you still need to deal
               | with panics anyway for things like nil pointers or
               | invalid array offsets.
        
               | gf000 wrote:
               | Go messages are harder for both devs and users to read.
               | Grepping for an error message in a codebase is a special
               | hell.
               | 
               | Besides, it's quite trivial to simply return the
               | exception's getMessage in a popup for an okay-ish error
               | message (but writing a stacktrace prettifier that writes
               | out the caused by exception's message as well is trivial,
               | and you can install exception handlers at an appropriate
               | level, unlike the inexpensibility of error values)
        
             | bccdee wrote:
             | When there's an exceptional case, it's better to handle
             | that explicitly. I think Rust does that best with its
             | single-character ? operator, but I don't want exceptions
             | invisibly breaking out of control flow unless I give them
             | permission to. `if err != nil` is a fair enough way of
             | doing that.
        
               | int_19h wrote:
               | It's not a good way to do this because it doesn't force
               | you to either handle or propagate.
        
               | bccdee wrote:
               | Yeah there are linters that force you not to implicitly
               | discard errors, but that should really be a compiler
               | error. Still, that's not a problem inherent to the Go's
               | error-handling model.
        
             | prisenco wrote:
             | Better is subjective, but I prefer errors as return values
             | because then the function signature states whether an error
             | has to be handled or not. Exceptions can be forgotten
             | about, but returned errors have to be explicitly ignored.
        
               | gf000 wrote:
               | That's an independent problem - checked exceptions (and
               | the even better effect types) are part of the method
               | signature.
        
               | prisenco wrote:
               | Checked exceptions feels six of one half dozen of the
               | other to me.
        
         | oefrha wrote:
         | No, TFA is mostly about making errors consistent in a large
         | application, while exception (vs error as standard return
         | value) is largely about easier bubbling, which is one thing TFA
         | hardly talked about (maybe I missed it, I only skimmed the
         | article). In fact it spends a lot of energy on wrapping which
         | is the opposite of automatic bubbling provided by exceptions by
         | default. Throwing random, inconsistent module/package/whatever-
         | specific exceptions from everywhere causes most of the same
         | problems described in TFA.
         | 
         | I feel like all the canned comments saying TFA is about
         | implementing exceptions / ADT result type are from people who
         | didn't read the article and just want to repeat all the cliche
         | on the topic (for easy karma? No idea what's the point).
        
         | zeroxfe wrote:
         | That's not how I read it. It's more about having a consistent
         | approach to managing error types in large code bases. This is a
         | common problem with exception-based languages too.
        
         | ori_b wrote:
         | How so? This is about how errors are defined, not how they're
         | propagated through the application. Feels like you didn't
         | actually read what was being done by the OP.
        
           | retrodaredevil wrote:
           | Exceptions have a hierarchical nature to them in most
           | languages, or at least have some sort of identity to them.
           | Your correct that the author doesn't try to change the way
           | errors are propagated, but you can see similarities between
           | what the author is creating themselves, and what already
           | exists in languages with exceptions.
        
       | revskill wrote:
       | Too much writing and lack of diagramming is a sign of digging
       | through the rabitt hole.
        
       | rollulus wrote:
       | Ah, a God error package that has all seeing knowledge of the
       | domain around it. What a monstrosity.
        
         | mrkeen wrote:
         | It's not the worst idea for an organisation to centralise stuff
         | that needs to be centralised.
         | 
         | Like defining protobuf schemas, it's no good if each team does
         | its own thing.
        
       | bilbo-b-baggins wrote:
       | Bro got dragged so hard in the comments he took his site down.
       | Oof.
        
         | jatins wrote:
         | I mean their intentions are good but if I worked at a place
         | that made me use that error package I'd not have a good time
         | 
         | In general with golang, if something is not idiomatic Go then
         | don't try too hard to fit constructs from other languages into
         | it. Even the use of lodash like packages feels awkward in Go
        
         | asabla wrote:
         | more like hug of death from HN users. Since the site is back up
         | and working again
        
       | tzahifadida wrote:
       | I arrived to a similar conclusion. I come from Java and in Java
       | you have exceptions with TryCatch clauses and declaring them in
       | function signatures. It works fairly well but very difficult and
       | not idiomatic to Golang.
       | 
       | Therefor, I created a simple rule. If you do not know what this
       | error means to the user yet then let it stay a
       | fmt.errorf("xx:%w",err). If you do, wrap it in your own custom
       | ServerError struct and return that type from now on. Do not
       | change the meaning of ServerError even if you wrap the Error with
       | another ServerError.
        
         | RVuRnvbM2e wrote:
         | It is telling that you come from Java with this opinion. OP's
         | approach is certainly not idiomatic Go.
        
           | wruza wrote:
           | Idiomatic here means no idiom suggested really. So yeah, non-
           | idiomatic.
        
       | Animats wrote:
       | Is this just someone's proposal, or a formal addition to Go, or
       | what?
       | 
       |  _" All errors must implement the Error interface."_ That's a
       | step forward.
       | 
       | Rust really has the same error handling as Go - return an error
       | status. But the syntax is cleaner. Rust thrashed around with
       | errors at first. Then things sort of settled down. At this point,
       | everybody uses Result<UsefulValue, Error>, but "Error" is just a
       | trait that doesn't require much information. And "?" for
       | propagating errors upwards is a huge convenience.
       | 
       | It's probably too late to retrofit "Result" and "?" into Go
       | libraries, although they'd fit the language.
        
         | gf000 wrote:
         | > Rust really has the same error handling as Go
         | 
         | Not at all. Rust has proper sum types, that it can return just
         | like anything else in the language, while Go has a special
         | cased error return slot (one may be tempted to call it an ugly
         | hack), and it can return a value on _both_ , which it does in
         | some standard library calls.
        
           | margalabargala wrote:
           | Not at all. Go has an error type, and Go functions have the
           | ability to return zero, one, two, or more items, ordered
           | however the developer likes. An error may be among those, as
           | desired, and populated as desired.
           | 
           | Some software also writes to both STDOUT and STDERR.
        
             | gf000 wrote:
             | I know, special cased may have been better worded as "just
             | a convention". My point is, this is not much different than
             | using a thread-local variable, like errno, and adds useless
             | confusion - your return values represent n*m values, while
             | there is only n+m case with proper error semantics.
             | 
             | Re STDERR: but shells don't decide whether a program
             | execution failed on having written to STDERR, but by the
             | returned singular error code.
        
               | margalabargala wrote:
               | I agree with everything you've written in this comment.
               | 
               | I'd like to split a hair here and say, this is a "Go's
               | standard library" problem, and not a "Go language"
               | problem.
               | 
               | Good API design for a software package should have proper
               | error semantics.
               | 
               | Good API design for a language, allows for flexibility in
               | actual implementation, alongside standards that say "you
               | SHOULD do this".
        
               | gf000 wrote:
               | Disagree. This level of convention is inseparable from
               | the language.
               | 
               | Not doing the conventional error return in go would be
               | akin to using a Return sum type in reverse, putting the
               | success value into the Error case..
        
         | arp242 wrote:
         | One of the issues in Go is that if all you ever do if "if err
         | != nil { return err }", you will quickly run in to problems
         | because you will have errors like "open foo: no such file or
         | directory" or "sql: no rows in result set" without a clue where
         | that error came from. Sometimes that's obvious, often it's not.
         | 
         | I'm not sure how Rust handles that? But it's more than just
         | "propagate errors", but more like "propagate errors with the
         | appropriate context for this specific error".
        
           | bbatha wrote:
           | Rust uses the `?` operator to convert between error types
           | which allows for users and libraries to hook in to the error
           | before its returned.
           | 
           | There are a number of helper libraries that provide an
           | extended type erased error type to attach a real stack trace
           | to the error, such as `anyhow`. These helper libraries also
           | provide ways to attach extra metadata to the error so you can
           | do things like `returns_a_result().context("couldn't do
           | it")?` so you can quickly annotate the error. The standard
           | library is support for this through a `context.Value` like
           | api on the Error trait. The std lib `Error` trait also has
           | functions for find the cause of the error and traverse a
           | collected chain of errors, very similar to go's
           | `errors.Cause` api.
           | 
           | Rust also has a number of libraries for making specific error
           | types like `thiserror` which can help generate error enums
           | with the implementations required to carry backtraces,
           | context and causes.
        
             | kbolino wrote:
             | Yep, if you want wrapped errors in Rust, you use the anyhow
             | crate. It leans heavily into dyn so has some performance
             | tradeoffs, but it's roughly the same performance-wise as
             | Go's error interface (which also uses a vtable under the
             | hood).
        
               | kibwen wrote:
               | Though using a dynamic error in Rust should only impose
               | an allocation cost on the error path, and I presume Go is
               | the same.
        
       | wruza wrote:
       | When I thought about errors/exceptions, I basically came to the
       | same conclusion. To reiterate or add to tfa: standard
       | formulations, expected vs. happened, reasonable context visible
       | in logs, error trees, automatic http/etc codes, tidy client
       | messages in prod, reasonable distinction between: unexpected,
       | semi-normal, programming error, likely fatal.
       | 
       | Not sure why most (all?) programming languages have such poor
       | support for errors. Coding may feel like 2024, but error handling
       | like 1980. Anyone with 2-5 years of any programming experience
       | (in where errors do happen and they choose to handle them) will
       | come to similar ideas.
       | 
       | Also the fact that try {} and catch/finally {} are always three
       | different scopes is just idiotic. It should be try {catch{}
       | finally{}}, what in the cargo cult that {}{}{} is? Everyone
       | copies it blindly from grammar to grammar.
        
       | ikiris wrote:
       | The fact that this code also has gorm in it in one of the
       | examples is neither supportive of the proposal's fit for the
       | language, nor really surprising.
        
       | rollulus wrote:
       | This approach is so bad, I don't even know where to start. But
       | it's all symptoms of their, sorry, incompetence. Take the
       | loadCredentials example on top. If os.ReadFile cannot find the
       | file, it returns an error with string representation: "open
       | cred.json: no such file or directory". This comes straight from
       | the std lib as it is, a great error. What does the errors.Is(err,
       | os.ErrNotExist) do: prepend "file not found" to it, rendering:
       | "file not found: open cred.json: no such file or directory". So
       | this adds exactly nothing. The next if will prepend "failed to
       | read file" to it, again, adding nothing as well. The two errors
       | checks should be replaced by one if statement, optionally
       | wrapping it with a context string but I cannot think of any use.
       | Then the next step, error handling of verifyCredentials. I can
       | only guess what it does, but assume that it returns an "username
       | 'foo bar' cannot contain spaces" error. Does prepending "invalid
       | credentials" help anything? Nope, so the whole if can be removed
       | as well. No surprise your errors get clunky if you make them
       | clunky.
       | 
       | I have more pressing things to do than dissect this article line
       | by line, but let me suffice that I feel sorry for newcomers to
       | the language that an article like this is so high on HN. Back in
       | the days there was just Dave Cheney's material to read [1], and
       | it was excellent. It's unfortunately outdated in certain regards
       | (e.g. with new Is/As functionality in the errors package for
       | inspection and the %w formatting directive in fmt.Errorf) but
       | it's still an excellent article.
       | 
       | [1]: https://dave.cheney.net/2016/04/27/dont-just-check-errors-
       | ha...
        
         | franticgecko3 wrote:
         | I'm worried readers of this article will be horrified and
         | believe this kind of DIY error handling is necessary in Go.
         | 
         | The author has attempted to fix their unidiomatic error
         | handling with an even more unidiomatic error framework.
         | 
         | New Go users: most of the time returning an error without
         | checking its value or adding extra context is the right thing
         | to do
        
           | vultour wrote:
           | From my experience this is not the case. If you error out 7
           | functions deep and only return the original error there's no
           | chance you're figuring out where it happened. Adding context
           | on several levels is basically a simplified stack trace which
           | lets you quickly find the source of the error.
        
             | richbell wrote:
             | I agree; I've wasted countless hours troubleshooting errors
             | returned in complex Go applications. The original error is
             | _not_ sufficient.
        
             | tetha wrote:
             | It's not a binary decision though. Just because the article
             | arrives at overkill for most things in my opinion doesn't
             | mean sentinel errors or wrapping errors in custom types
             | should be avoided at all costs in all situations.
             | 
             | In my experience, it's good and healthy to introduce this
             | additional context on the boundaries of more complex
             | systems (like a database, or something accessing an
             | external API and such), especially if other code wants to
             | behave differently based on the errors returned (using
             | errors.Is/errors.As).
             | 
             | But it's completely not necessary for every single plumping
             | function starts inspecting and wrapping all errors it
             | encounters, especially if it cannot make a decision on
             | these errors or provide better context.
        
             | mrj wrote:
             | I inherited a codebase with the same problem. After a few
             | debugging sessions where it wasn't clear where the error
             | was coming from, I decided the root problem was that we
             | didn't have stack traces.
             | 
             | Fortunately, the code was already using zap and it had a
             | method for doing exactly that:
             | 
             | zap.AddStacktrace(zap.LevelEnablerFunc(func(lvl
             | zapcore.Level) bool { return lvl >= zapcore.InfoLevel }))
             | 
             | Because most of the time if there's an error, you'd likely
             | want to log it out. Much of the code was doing this
             | already, so it made sense to ensure we had good stack
             | traces.
             | 
             | There's overhead to this, but in our codebase there was a
             | dearth of logging so it didn't matter much. Now when things
             | are captured we know exactly where it happened without
             | having to do what the post is doing manually... adding
             | stack info.
        
             | mplanchard wrote:
             | We actually went through the same realization when we
             | started writing Rust a few years ago. The `thiserror` crate
             | makes it easy to just wrap and return an error from some
             | third-party library, like:
             | #[derive(Debug, thiserror::Error)]         enum MyError {
             | #[error(transparent)]           ThirdPartyError(#[from]
             | third_party::Error)         }
             | 
             | Since it derives a `From` implementation, you can use it as
             | easily as:                   fn some_function() ->
             | Result<(), MyError> {           third_party::do_thing()?;
             | }
             | 
             | But if that's happening somewhere deep in your application
             | and you call that function from more than one place, good
             | luck figuring out what it is! You wind up with an error log
             | like `third_party thing failed` and that's it.
             | 
             | Generally, we now use structured error types with context
             | fields, which adds some verbosity as specifying a context
             | becomes required, but it's a lot more useful in error logs.
             | Our approach was significantly inspired by this post from
             | Sabrina Jewson: https://sabrinajewson.org/blog/errors
        
           | mariusor wrote:
           | Do you maybe have a constructive advice for people that need
           | to return errors that demand different behaviour from the
           | calling code?
           | 
           | I gave an example higher in the thread: if searching for the
           | entity that owns the creds.json files fails, we want to
           | return a 404 HTTP error, but if creds.json itself is missing,
           | we want a 401 HTTP error. What would be the idiomatic way of
           | achieving this in your opinion?
        
             | sethammons wrote:
             | Use errors.Is and compare to the returned err to
             | mypkg.ErrOwnerNotExists and mypkg.ErrMissingConfig and the
             | handler decides which status code is appropriate
        
               | mariusor wrote:
               | Cool, but error.Is what? In my case would both come as a
               | os.NotExist errors because both are files on the disk.
               | 
               | I think that the original dismissal I replied to, might
               | not have taken into account some of the complexities that
               | OP most likely has given thought to and made decisions
               | accordingly. Among those there's the need to extract or
               | append the additional information OP seems to require
               | (request id, tracking information, etc). Maybe it can be
               | done all at the top level, but maybe not, maybe some come
               | from deeper in the stack and need to be passed upwards.
        
               | sethammons wrote:
               | no no no; do not return os.NotExists in both cases. The
               | function needs to handle os.NotExists and then return
               | mypkg.ErrOwnerNotExists or mypkg.ErrMissingConfig (or
               | whatever names) depending on the state in the function.
               | 
               | The os.NotExists error is an implementation detail that
               | is not important to callers. Callers shouldn't care about
               | files on disk as that is leaking abstraction info. What
               | if the function decides to move those configs to s3? Then
               | callers have to update to handle s3 errors? No way.
               | Return errors specific to your function that abstract the
               | underlying implementation.
               | 
               | Edit: here is some sample code
               | https://go.dev/play/p/vFnx_v8NBDf
               | 
               | Second edit: same code, but leveraging my other comment's
               | kverr package to propagate context like kv pairs up the
               | stack for logging: https://go.dev/play/p/pSk3s0Roysm
        
               | mariusor wrote:
               | Exactly, and that's what OP argues for, albeit in a very
               | complex manner.
               | 
               | Distilling their implementation to the basics, that's
               | what we get: typed errors that wrap the Go standard
               | library's ones with custom logic. Frankly I doubt that
               | the API your library exposes (kv maps) vs OPs typed
               | structs, is better. Maybe their main issue is relying on
               | stuffing all error types in the same module, instead of
               | having each independent app coming up with their own, but
               | probably that's because they need the behaviour for
               | handling those errors at the top of the calling stack is
               | uniform and has only one implementation.
               | 
               | A quick back of the napkin list for what an error needs
               | to contain to be useful in a post execution debugging
               | context would be:
               | 
               | * calling stack
               | 
               | * traceability info like (request id, trace id, etc)
               | 
               | * data for the handling code to make meaningful
               | distinction about how to handle the error
               | 
               | I think your library could be used for the last two, but
               | I don't know how you store calling stack in kv pairs
               | without some serious handwaving. Also kv is unreliable
               | because it's not compile time checked to match at both
               | ends.
        
               | sethammons wrote:
               | I'm not saying use kverr for explicit error handling
               | (like, you could, but that is non ideal), use kverr as a
               | context bag of data you want to capture in a log. If you
               | programmatically are routing with untyped string data, I
               | agree, unreliable
        
             | tetha wrote:
             | With some of these examples, I'd change the API of the
             | lower-level methods. Instead of a (Credentials, err) and
             | the err is a NotFound sometimes, I'd rather make it a
             | (*Credentials, bool, err) so you can have a (creds, found,
             | err), and err would be used for actual errors like "File
             | not found"/"File unreadable"/...
             | 
             | But other than that, there is nothing wrong with having
             | sentinel errors or custom error types on your subsystem /
             | module boundaries, like ErrCredentialsNotFetched,
             | ErrUserNotFound, ErrFileInvalid and such. That's just good
             | abstraction.
             | 
             | The main worry is: How many errors do you actually need,
             | and how many functions need to mess about with the errors
             | going around? More error types mean harder maintenance in
             | the future because code will rely on those. Many plumbing
             | or workflow functions probably should just hand the errors
             | upwards because they can't do much about it anyways.
             | 
             | A lot of the details in the errors of the article very much
             | feel like business logic and API design is getting
             | conflated with the error framework.
             | 
             | Is "Cannot edit a whatsapp message template more than 24
             | hours" or "the users account is locked" really an error
             | like "cannot open creds.json: permission denied" or "cannot
             | query database: connection refused"? You can create working
             | code like that, but I can also use exceptions for control
             | flow. I'd expect these things to come from some OpenAPI
             | spec and some controller-code make this decision in an if
             | statement.
        
           | mattgreenrocks wrote:
           | > New Go users: most of the time returning an error without
           | checking its value or adding extra context is the right thing
           | to do
           | 
           | Thank you.
           | 
           | Feels like Go is having its Java moment: lots of people
           | started using it, so questions of practice arise despite the
           | language aiming at simplicity, leading to the proliferation
           | of questionable advice by people who can't recognize it as
           | such. The next phase of this is the belief that the std
           | library is somehow inadequate even for tiny prototypes
           | because people have it beaten over their heads that
           | "everybody" uses SuperUltraLogger now, so it becomes orthodox
           | to pull that dependency in without questioning it.
           | 
           | After a bunch of iterations of this cycle, you're now far
           | away from simplicity the language was meant to create. And
           | the users created this situation.
        
             | int_19h wrote:
             | Go is having a Go moment: lots of people using it are
             | realizing that other programming languages have all that
             | complexity for a reason, and that "aiming at simplicity" by
             | aggressively removing or ignoring well-established language
             | features often results in more complicated code that's
             | easier to get wrong and harder to reason about.
        
         | vl wrote:
         | >it returns an error with string representation: "open
         | cred.json: no such file or directory". This comes straight from
         | the std lib as it is, a great error.
         | 
         | It's a terrible error. It's not structured, so you can't
         | aggregate it effectively in logs, on top of that it leaks
         | potential secret, so you can't return it from RPC handler.
        
           | rollulus wrote:
           | The string representation is obviously not structured,
           | because it's a string representation and strings are scalars.
           | The typed representation is structured, which you can put
           | into your structured logs as you'd like, omitting sensitive
           | information where needed.
        
         | mariusor wrote:
         | As someone that ended up implementing something very similar to
         | TFA, I'd like to ask in which way can you pass errors from 3
         | layers deep in your stack to the top layer and maintain
         | context?
         | 
         | Ie, when I can't find cred.json I want to return a 401 error,
         | but when I can't find the entity cred.json is supposed to be
         | owned by I want to return 404. How can one "not incompetent" Go
         | developer solve this and distinguish between the two errors?
        
         | tetha wrote:
         | > No surprise your errors get clunky if you make them clunky.
         | 
         | From a user perspective, good errors in go make me think or
         | Perls croak/carp. Croak and carp gave you a stacktrace of your
         | error, but it cut out all the module-internal calls and left
         | you with the function calls across module boundaries. Very
         | useful - enough so that Java discovered it again later on.
         | 
         | Personally, I wouldn't wrap the errors in loadCredentials at
         | all. I'd just wrap the result of this method into an
         | fmt.Errorf("failed to load credentials: %w"). This way the user
         | knows the context the error happened in, and then we have to
         | cross our fingers the error returned by this is good enough.
         | 
         | But something like "application startup failed: failed to load
         | credentials: open cred.json: no such file or directory" is a
         | very nice error message from an application. Just enough
         | context to know what's going on, but no 1200 line stacktrace to
         | sift through.
        
       | ThePhysicist wrote:
       | I think that's overkill, most of the time I just bubble errors up
       | and I have very few cases where the error handling depends on the
       | type of error. I guess it's because I don't use errors for things
       | that are recoverable and try to fix them instead inside the given
       | function. An example given here in the thread is reading from a
       | file and if it doesn't work try a backup. Rather than having a
       | function that reads from a file and returns a bunch of different
       | errors I'd just make one with a list argument and then handle the
       | I/O errors inside, and return an "unrecoverable" error otherwise.
       | 
       | For adding context, %w is good enough I find, though as I said I
       | only very sparingly use errors.Is(...). Go isn't a language
       | that's designed around rich error or exception types, and I don't
       | think you should use it like that.
        
         | Yoric wrote:
         | Well, yes, if you're just using errors as error messages, you
         | only need strings and %w. That's usually good enough if you're
         | writing an application.
         | 
         | However, if you're writing a library, chances are that your
         | users want to catch the errors, find out whether the call
         | failed because, say, the remote API is down or because the
         | password is wrong.
         | 
         | Or if you're writing an API, you probably want to return
         | different error codes. If your errors are bubbling, you'll need
         | to somehow `errors.Is`/`errors.As` somewhere.
        
         | eptcyka wrote:
         | Yea, but like, when making an HTTP request, a timeout is
         | significantly different from a failure to open a socket from a
         | failure to resolve the hostname from a 429 error. And often it
         | is up to the caller to decide how to handle those situations.
        
       | sethammons wrote:
       | The concepts aren't wrong (structured logs from structured
       | errors), but I find this code to be very un-go-like and there are
       | obvious signs of trying to write java in go (iFace, structs with
       | one property "because everything needs to be contained in an
       | object", and others).
       | 
       | Return "error" and not a custom type "mypkg.Error" - you run into
       | more nil interface pointer problems and you are breaking an
       | idiom.
       | 
       | Let me provide a counter example for helping create structured
       | logs from structured errors that I wrote up that is much more
       | idiomatic if not more narrowly focused:
       | 
       | https://github.com/sethgrid/kverr
       | 
       | As in the article, if you want to attach "username: foo", this
       | package lets you return kverr.New(err, "username", foo, ...), and
       | then extract a slice or map later for logging like
       | logger.WithArgs(YoinkArgs(err)...).
        
       | Cloudef wrote:
       | Posts like these remind me how go really has nothing going for it
       | apart from goroutines and channels. It's awkward mix of low level
       | and high level with C like influence, which is weird considering
       | it's a GC language.
        
       | binary132 wrote:
       | I have been seeing this pattern repeated over and over since I
       | started using Go in 2014 where people think they should be
       | "building my favorite missing feature" -- whether that's futures,
       | generics, structural processes, OTP, version managers, package
       | managers, or now apparently exceptions. I always get the sense
       | that the authors think they've done something cool and helpful
       | when in the first place if they had simply put more effort into
       | comprehending the simple "Go way" it wouldn't have been necessary
       | at all, and the needed functionality would have fallen out of the
       | design.
        
         | jdbxhdd wrote:
         | You realize that have of the features you are counting are now
         | in Go while missing in the beginning exactly because people
         | were missing them and Go simply did not offer a sane way to
         | work around the missing features?
         | 
         | I'm also quite sure that Go will provide a more sane way to
         | handle errors in the not so far future, since it's continuously
         | at the top of people's complaints
        
           | binary132 wrote:
           | your comment exemplifies the mentality, yes, and
           | unfortunately it has now been adopted by project leadership,
           | so I'm sure you are quite right that more "missing features"
           | will get baked into the language soon :)
        
             | int_19h wrote:
             | It's far better to have those features well-designed and
             | baked into the language once, then to have them constantly
             | poorly redesigned and baked into every other Go app.
        
               | binary132 wrote:
               | nobody would ever use this argument for the design of C.
               | It's good for C to stay lean and simple while communities
               | using C (please let's not with this imaginary monolithic
               | "The Community") are free to try things and offer
               | competing solutions that others are free to ignore.
               | 
               | kitchen sink languages are bad. Justifying them with
               | "well the community is bad, so we need the bad thing to
               | be mainlined" is maybe worse
        
               | int_19h wrote:
               | C is legacy tech on life support.
               | 
               | By Go standard, all other languages are "kitchen sink".
               | Conversely, I would argue that basics like decent error
               | handling are not in any meaningful sense a "kitchen sink"
               | thing.
        
               | binary132 wrote:
               | C is still #4 on TIOBE, right behind Java, so that is not
               | at all true.
               | 
               | Go is good because it's not like the other languages.
               | 
               | It should stay not being like them, not try to be more
               | like them.
        
       | mukunda_johnson wrote:
       | Adding error checks everywhere when you don't care about them is
       | one of the ugliest things about Go.
       | 
       | What I do is have a utility package that lets me panic on most
       | errors, so I can recover in a generalized handler.
       | 
       | x, err := doathing()
       | 
       | Catch(err, "didn't do the thing")
       | 
       | The majority of error handling is "the operation failed, so
       | cancel the request." Sure there are places where the error
       | matters and you can divert course, but that is far from the
       | majority of cases.
        
         | kbolino wrote:
         | I don't agree, but having said that, this feels like an
         | entirely predictable/justifiable perspective to hold, given the
         | terrible design of net/http in the standard library. Of course
         | it feels easier to just panic, it's not like you can return an
         | error from a handler. There is so much compatibility baggage
         | from Go 1.0 in that package, that doing the right thing
         | (contexts, errors, etc.) is so much harder than it should be,
         | and most people end up doing the wrong thing because it's more
         | ergonomic.
        
       | samiv wrote:
       | The most crucial thing that I've seen over the years is that most
       | developers are simply afraid of bringing the application down on
       | bugs.
       | 
       | They conflate error handling with writing code for bugs and this
       | leads to proliferation of issues and second/third/etc degree
       | issues where the code fails because it already encountered a BUG
       | but the execution was left to continue.
       | 
       | What do I mean in practice? Practical example:
       | 
       | I program mostly in C and C++ and I often see code like this
       | if (some_pointer) { ... }
       | 
       | and the context of the code is such that some_pointer being a
       | NULL pointer is in fact not allowed and is thus a BUG. The right
       | thing to do would be to ABORT the process execution immediately
       | but instead the programmer turned this it into a logical
       | condition. (probably because they were taught to check their
       | pointers).
       | 
       | This has the side effect that:                 - The pre-
       | condition that some_pointer may not be null is now lost. Reading
       | the code it looks like this condition IS allowed.        - The
       | code is allowed to continue after it has logically bugged out.
       | Your 1+1 = 2 premise no longer holds. This will lead to second
       | order bugs later on when the BUG let program to continue
       | execution in buggy condition.  False reporting will likely
       | happen.
       | 
       | The better way to write this code is:
       | ASSERT(some_pointer);
       | 
       | Where ASSERT is a unconditional check that will always
       | (regardless of your build config) abort your process gracefully
       | and produce a) stack trace b) core dump file.
       | 
       | My advice is:
       | 
       | If your environment is such that you can immediately abort your
       | process when you hit a BUG you do so. In the long run this will
       | help with post-mortem diagnosis and fixing of bugs and will
       | result in more robust and better quality code base.
        
         | bluepizza wrote:
         | I think the issue is that bringing the application down might
         | mean cutting short concurrent ongoing requests, especially
         | requests that will result in data mutation of some sort.
         | 
         | Otherwise, some situations simply don't warrant a full
         | shutdown, and it might be okay to run the application in
         | degraded mode.
        
           | samiv wrote:
           | "I think the issue is that bringing the application down
           | might mean cutting short concurrent ongoing requests,
           | especially requests that will result in data mutation of some
           | sort."
           | 
           | Yes but what is worse is silently corrupting the data or the
           | state because of running in buggy state.
        
             | jayd16 wrote:
             | This is a false choice.
        
               | int_19h wrote:
               | If you don't know why a thing that's supposed to never be
               | null ended up being null, you don't know what the state
               | of your app is.
               | 
               | If you don't know what the state of your app is, how do
               | you prevent data corruption or logical errors in further
               | execution?
        
               | victorbjorklund wrote:
               | You can "drop" that request which fails instead of
               | crashing the whole app (and dropping all other requests
               | too).
        
               | kstrauser wrote:
               | Sure. You wouldn't want a webserver to crash if someone
               | sends a malformed request.
               | 
               | I'd have to think long and hard about each individual
               | case of running in degraded mode though. Sometimes that's
               | appropriate: an OS kernel should keep going if someone
               | unplugs a keyboard. Other times it's not: it may be
               | better for a database to fail than to return the wrong
               | set of rows because of a storage error.
        
               | buttercraft wrote:
               | That's exactly what the attacker wants you to do after
               | their exploit runs: ignore the warning signs.
        
               | ndriscoll wrote:
               | You don't ignore it. You track errors. What you don't do
               | is crash the server for all users, giving an attacker an
               | easy way to DoS you.
        
               | buttercraft wrote:
               | > If you don't know what the state of your app is, how do
               | you prevent data corruption or logical errors in further
               | execution?
               | 
               | Even worse, you might be in an unknown state because
               | someone is trying to exploit a vulnerability.
        
               | jayd16 wrote:
               | If you crash then you've handed them a denial of service
               | vulnerability.
        
               | jayd16 wrote:
               | > If you don't know what the state of your app is, how do
               | you prevent data corruption or logical errors in further
               | execution?
               | 
               | There are a lot of patterns for this. Its perfectly fine
               | and often desirable to scope the blast radius of an error
               | short of crashing everything.
               | 
               | OSes shouldn't crash because a process had an error.
               | Servers shouldn't crash because a request had an error.
               | Missing textures shouldn't crash your game. Cars
               | shouldn't crash because the infotainment system had an
               | error.
        
               | int_19h wrote:
               | If you can actually isolate state well enough, and code
               | every isolated component in a way that assumes that all
               | state external to it is untrusted, sure.
               | 
               | How often do you see code written this way?
        
         | ramses0 wrote:
         | "MIT v. Berkeley - Worse is Better" =>
         | https://blog.codinghorror.com/worse-is-better/
         | 
         | "Fail Fast / Let it Crash" =>
         | https://erlang.org/pipermail/erlang-questions/2003-March/007...
         | 
         | ...you're in good company. :-)
        
         | jayd16 wrote:
         | Like everything in life, it depends.
         | 
         | If this is some inconsequential part of the codebase it might
         | be better to limp on then to completely stop anyone, user or
         | fellow dev, from running the app at all.
         | 
         | Said another way, graceful degradation is a thing.
        
           | samiv wrote:
           | How do you gracefully degrade when your program is in a buggy
           | state and you no longer know what data is valid, what is
           | garbage and what conditions hold ?
           | 
           | If I told you to write a function that takes a chunk of
           | customer JSON data but I told you that the data was produced
           | / processed by some code that is buggy and it might have
           | corrupted the data and your job is to write a function that
           | works on that data how would you do it?
           | 
           | Now your answer is likely to be "just check sum it", but what
           | if i told you that the functions that compute the check sums
           | sometimes go off rails in buggy branches and produce
           | incorrect checksums.
           | 
           | Then what?
           | 
           | In a sane world your software is always well defined state.
           | This means buggy conditions cannot be let to execute. If you
           | don't honor this you have _no chance_ of correct program.
        
             | gf000 wrote:
             | Contrary to people's dislike of OOP, I think it pretty well
             | solves the problem.
             | 
             | You have objects, and calling a method on it may fail with
             | an exception. If the method throws an exception, it itself
             | is responsible for leaving behind a sane state, but due to
             | encapsulation it is a feasible task.
             | 
             | (Of course global state may still end up in illegal states,
             | but if the program architecture is carefully designed and
             | implemented it can be largely mitigated)
        
             | ndriscoll wrote:
             | Why not bring down the entire server if you detect an error
             | condition in your application? You build things in a way
             | where a job or request has isolated resources, and if you
             | detect an error, you abort the job and free those
             | resources, but continue processing other jobs. Operating
             | systems do this through processes with different memory
             | maps. Applications can do it through things like arenas or
             | garbage collection.
        
           | gf000 wrote:
           | I think this is precisely why exceptions model particularly
           | well - well - exceptional situations.
           | 
           | They let you install barriers, and you can safely fail up
           | until that point, disallowing the program from entering
           | cursed states, all the while a user can be returned a
           | readable error message.
           | 
           | In fact, I would be interested in more research into
           | transactions/transactional memory.
        
         | texuf wrote:
         | If you're validating parameters that originate from your
         | program (messages, user input, events, etc), ASSERT and ASSERT
         | often. If you're handling parameters that originate from
         | somewhere else (response from server, request from client,
         | loading a file, etc) - you model every possible version of the
         | data and handle all valid and invalid states.
         | 
         | Why? When you or your coworkers are adding code, the stricter
         | you make your code, the fewer permutations you have to test,
         | the fewer bugs you will have. But, you can't enforce an
         | invariant on a data source that you don't control.
        
           | samiv wrote:
           | Yes of course the key here is to understand the difference
           | between BUGS and logical (error) conditions.
           | 
           | If I write an image processing application failing to process
           | an image .png when:                 - user doesn't permission
           | to the file       - file is actually not a file       - file
           | is actually not an image       - file contains a corrupt
           | image       etc.
           | 
           | are all logical conditions that the application needs to be
           | able to handle.
           | 
           | The difference is that from the software correctness
           | perspective none of these are errors. In the software they're
           | just logical conditions and they are only errors to the USER.
           | 
           | BUGS are errors in the software.
           | 
           | (People often get confused because the term "error" without
           | more context doesn't adequately distinguish between an error
           | condition experienced by the user when using the software and
           | errors in the program itself.)
        
           | keybored wrote:
           | > But, you can't enforce an invariant on a data source that
           | you don't control.
           | 
           | This is obvious.
        
         | kstrauser wrote:
         | I largely agree. If it came to pass that the precondition
         | fails, there's a bug somewhere and this code just hides it. At
         | the very least, that should go to an error log that someone
         | actually sees.
         | 
         | I'm writing a Rust project right now where I deliberately put
         | almost no error handling in the core of the code apart from the
         | bits accepting user input. In Rust speak, I use .unwrap() all
         | over the place when fetching a mandatory row from the DB or
         | loading config files or opening a network connection to listen
         | on or writing to stdout. If any of those things fail, _there 's
         | not a thing I can plausibly do to recover from it in this
         | context_. I suppose I _could_ write code like
         | if let Ok(cfg) = load_config() {         println!("Loaded the
         | config without failing!");         Ok(cfg);       }       else
         | {         eprintln!("Oh no! Couldn't load the config file!";
         | Err("Couldn't load the config file");       }
         | 
         | and make the program exit if it returns an error, but that's
         | just adding noise around:                 return
         | load_config().unwrap();
         | 
         | The only advantage is that the error message is more gentle, at
         | the expense of adding a bunch of code and potentially hiding
         | the underlying error message from the user so that they could
         | fix it.
         | 
         | I think Python also gets that right, where it's common to raise
         | exceptions when exceptional things happen, and only ever handle
         | the exceptions you can actually do something about. In 99.999%
         | of projects, what are you actually going to do at the
         | application level to properly deal with an OOM or disk full
         | error? Nothing. It's almost always better to just crash and let
         | the OS / daemon manager / top level event loop log that
         | something bad happened and schedule a retry.
        
           | samiv wrote:
           | The whole story is 3-fold. We have                 - errors
           | in the software itself, aka BUGS       - logical conditions
           | that are expected part of the program execution flow and
           | expected state. some of these might be error conditions but
           | only for the *user*. In other words they're not errors in the
           | software itself.       - unexpected failures where none of
           | the above applies. typically only when some OS resource
           | allocation fails, failed to allocate memory, socket, mutex
           | etc and the reason is not because the programmer called the
           | API wrong.
           | 
           | In the first category we're dealing with BUGS and when I
           | advocate asserting and terminating the process that only
           | really applies to BUG conditions. If you let an application
           | to continue in a buggy state then you cannot logically reason
           | about it anymore.
           | 
           | The logical conditions are the typical cases for example
           | "file not found" or whatever. User tries to use the software
           | but there's a problem. The application needs to deal with
           | these but from the _software correctness perspective_ there
           | 's no error. The error is only what the user perceives. When
           | your browser prints "404" or "no internet connection" the
           | software works correctly. The error is only from the user
           | perspective.
           | 
           | Finally the last category are those unexpected situations
           | where something that should not fails. It is quite tricky to
           | get these right. Is straight up exiting the right choice?
           | Maybe the system will have more sources later if you just
           | back off and try again later. Personally in C++ projects my
           | strategy is to employ exceptions and let the callstack unwind
           | to the UI level, inform the user and and then just return to
           | the event loop. Of course the real trick is to keep the
           | program state such that it's in some well defined state and
           | not in a BUGGY state ;-)
        
             | kstrauser wrote:
             | That sounds about right to me. Worry about the things you
             | can fix and don't worry abut the things outside your
             | control.
        
           | veidelis wrote:
           | Makes sense. Better to unwrap via .expect("msg"), though.
        
             | kstrauser wrote:
             | That's a good callout, but I do that if and when I can add
             | extra meaningful context.
             | 
             | From a user's POV, "I already know what file not found
             | means. You don't have to explain it to me again in your own
             | words."
        
         | dehrmann wrote:
         | This is often called offensive programming.
        
           | samiv wrote:
           | Hot damn, I never heard of this term before but yeah that's
           | exactly what it is.
           | 
           | TIL, thanks.
        
         | crabbone wrote:
         | I don't agree with any of this.
         | 
         | First of all, this results in unintelligible errors. Linux is
         | famous for abysmal error reporting, where no matter what the
         | problem really is, you get something like ENOENT, no context,
         | no explanation. Errors need to propagate upwards and allow the
         | handling code to reinterpret them in the context of the work it
         | was doing. Otherwise, for the user they are either meaningless
         | or dangerous.
         | 
         | Secondly, any particular function that encounters an unexpected
         | condition doesn't have a "moral right" to terminate the entire
         | program (who knows how many layers there are on top of what
         | this particular function does?) Perhaps the fact that a
         | function cannot handle a particular condition is entirely
         | expected, and the level above this function is completely
         | prepared to deal with the problem: insufficient permissions to
         | access the file -- ask user to elevate permission;
         | configuration file is missing in ~/.config? -- perhaps it's in
         | /etc/? cannot navigate to URL? -- perhaps the user needs to
         | connect to Wi-Fi network? And so on.
         | 
         | What I do see in practice, is that programmers are usually
         | incapable of describing errors in a useful way, and are very
         | reluctant to write code that automates error recovery, even if
         | it's entirely within reach. I think, the reason for this is
         | that the acceptance criteria for code usually emphasizes the
         | "good path", and because usually multiple bad things can happen
         | down the "bad path", it becomes cumbersome and tiresome to
         | describe and respond to the bad things, and then it's seldom
         | done.
        
       | gregwebs wrote:
       | There's a lot of comments here that seem overly critical. The
       | author came up with solutions to extend Go's errors to meet their
       | needs and shared that with the world- thank you.
       | 
       | I have been solving all the same problems and providing libraries
       | that allow for more flexibility so that users can come up with
       | approaches that best meet their needs. I am finally polishing the
       | libraries and starting to write about them:
       | 
       | https://blog.gregweber.info/blog/go-errors-library/ (errors with
       | stack traces and metadata)
       | 
       | https://github.com/gregwebs/errcode (adding codes to errors-
       | working on improving docs and writing about this now).
        
       | AceCream wrote:
       | type xError struct { msg message, stack: callers(), }
       | 
       | is this legit in go?
        
       ___________________________________________________________________
       (page generated 2024-12-18 23:01 UTC)