7 ms·
With generics landed, errors are what remains to complain about. Go’s error handling story actually strikes me as a very good fit for daemons, which should gene
by closeparen 5y ago
With generics landed, errors are what remains to complain about. Go’s error handling story actually strikes me as a very good fit for daemons, which should generally never crash and have no particularly good place to catch an unhandled exception. On the other hand for RPC and web services - which are indeed major use cases of Go! - the most common error handling strategy is to fail the RPC you’re in. Having to unwind the stack by hand in order to do this (and write all the associated tests) is a level of tedium that I get paid for at work but can’t quite bring myself to do at home.
- randomdata 5y agoThe tedium of errors irked me for a long time until I realized that it was my poor abstractions inherited from what I learned in other languages that lead to that state. As I established a more Go-minded approach, it became much more pleasant. To the point that I now find it desirable. I have been on Typescript/Javascript projects of late. Now that's a tedious error handling experience. I notice my colleagues have given up on error handling entirely due to how difficult it is.
- powerslacker 5y agoSame here. The Go approach is easy to read and eye compile. I can't stand exceptions now.
- philosopher1234 5y ago>eye compile i like this phrase
- eweise 5y agoIt still suffers from being returned as a separate value so not easy to chain methods together.
- robotastronaut 5y agoMethod chaining, while possible, isn’t something I’d say is really encouraged in Go. Returned values should be handled explicitly. There are plenty of cases where it could be fine, but if a function can error, you wouldn’t want to chain anyways.
- closeparen 5y agoYes, you would. “Chain all these methods and stop when you get to the first error” is an extremely common idiom. People write it all the time in lots of different languages. Go is unusual in that you have to spell it out.
- robotastronaut 5y agoRight, I know. You’re not wrong. I was meaning you wouldn’t want to do it in Go because it’s not idiomatic (and likely impossible as discussed). I use it all the time in other languages and don’t mind exception patterns, but method chaining feels very out of place when reading Go code.
- threatofrain 5y ago> As I established a more Go-minded approach, it became much more pleasant. Could you explain more by any chance?
- recov 5y agoHere's a good article form rob pike on one way to look at how Go can handle errors https://go.dev/blog/errors-are-values https://go.dev/blog/errors-are-values
- nprateem 5y agoExtra complexity to work around a shortcoming which ignores the fact that most users of a library only care about errors at the top level. Passing errors around manually when the machine could do it is a waste of time however it's dressed up.
- nemothekid 5y ago>Passing errors around manually when the machine could do it is a waste of time however it's dressed up. You, the writer, pass around errors manually so that the reader, who interfaces with the code 10x more than the writer, is able to reason about the code. Go's errors handling sucks, but I don't think everyone has the same opinion on why it sucks; I personally think that sum types should be added to the language making it impossible for errors to be silently ignored. Exceptions, while convenient, require too much discipline to use cleanly.
- nprateem 5y agoSomehow after 25 years of coding I've never seen issues with using exceptions. Java is more explicit, Python gets the job done, and a good IDE helps in most cases anyway
- jatone 5y agoevery java team i've worked with handles their exceptions by making them runtime exceptions to bypassing the very thing you're pro-porting.
- closeparen 5y agoYeah part of the reason it’s so tedious is that our application architecture is always Handler->Controller->Gateway or Repository->External Client, a pattern we first developed for Python. Errors mostly originate in the external clients and then have to be returned through all the layers. When I read open source projects there is a lot more “PHP spaghetti” with business logic and storage interactions right inside handlers, which is indeed easier for error handling, but definitely has other problems.
- mountainriver 5y agoSame, went back to python and I find it terribly annoying. You want to handle your errors, Go makes that simple, lots of other languages don’t
- closeparen 5y ago99.5% of the time I want to return a 500 to the caller. Other languages do that for free. Go makes it very manual.
- morelisp 5y agoI would argue that errors per se today are more than sufficient for this, but http.Handler is what's lacking. There's no idiomatic way to abort a multi-handler chain or to signal an error to a parent handler. This is the #1 value I get out of non-http.Handler-based frameworks like Gin. (RPCs tend not to have a comparable number of layers of middleware and more support for reading "downstream" errors, though I do wish gRPC supported nested interceptors better.)
- dhagz 5y agoI sort of get what you're saying, but at the same time I don't. Any logic that depends on what the "next/child" handler returns can get errors either from the response itself or the context, depending on the approach I want to take (context is probably most idiomatic, but its own bag of worms (I say that because values in the context are all empty interfaces/any type, which is a pain when you want a concrete type)). Unless there's some usecase I'm missing, since I haven't really touched Gin in awhile, and never got too crazy with what I wrung out of it.
- morelisp 5y ago> returns can get errors either from the response itself or the context Not really! To get errors "from the response" you're going to have to wrap the entire `ResponseWriter` to pass downstream, which is a) a lot of noise, b) can cause some subtle issues (you didn't really need to also implement Pusher, did you? you're sure no one already called Write(Header) on the thing you're wrapping, right? Ever heard of "requestTooLarger"?). To get errors "from the context", the downstream handler and you need to agree on how to record them. The downstream handler gets your context, you can't get its. So you need to prepare error logging for it, or agree on the same higher-level handler to do it for both of you. You're in trouble as soon as you try to integrate an arbitrary handler - which is the entire point of using the standard handler interface, so why bother? Or is there some magic pattern for either of these I'm missing?
- closeparen 5y agoFair point. I don’t spend much of my time on middlewares - write once, use forever - but we do have pretty nested and layered architectures between the user-level handler and what it’s ultimately doing. That’s where it’s painful to thread the errors through.
- eweise 5y agoHold on. I'm still complaining about generics since its still not possible to write a generic map function on a struct.
- monocasa 5y agoCan you expand on that? I sort of divorced myself from golang right as the final generics implementation was starting to bake.
- jerf 5y agohttps://go.dev/doc/faq#generic_methods https://go.dev/doc/faq#generic_methods You can't add generic parameters in methods. This means you can't implement a Map method on a slice, unless it's going to either A: return the same type as the slice or B: you explicitly decide at slice creation time what the one destination type you're going to have is and pass that as a parameter. This significantly limits "map" implementations, as well as "reduce". It does leave "filter" as a possible thing you can implement, since that's a hard-coded bool type.
- robertsdionne 5y agovia https://rakyll.org/generics-facilititators/ https://rakyll.org/generics-facilititators/ Example: https://go.dev/play/p/as3rT4fB3cn https://go.dev/play/p/as3rT4fB3cn // Invocation: strings := []string{"1", "2", "3"} ints, err := Mapper[string, int](strings).MapErr(strconv.Atoi) strings = Mapper[string, string](strings).Map(func(s string) string { return s + "_suffix" }) // Definition: type Mapper[T, U any] []T func (ts Mapper[T, U]) Map(f func(T) U) (us []U) { us, _ = ts.MapErr(func(t T) (u U, err error) { u = f(t) return }) return } func (ts Mapper[T, U]) MapErr(f func(T) (U, error)) (us []U, err error) { us = make([]U, 0, len(ts)) for _, t := range ts { var u U u, err = f(t) if err != nil { return } us = append(us, u) } return }
- deleted 5y ago[deleted]