7 ms·
> I'd spend hours condensing 10 lines of perfectly working code into 1 line of the most concise text possible Which is also the hardest part of convincing peo
by new_stranger 5y ago
> I'd spend hours condensing 10 lines of perfectly working code into 1 line of the most concise text possible
Which is also the hardest part of convincing people to use Go for me: "Why can't I just .map()/.filter()/.find()?" followed closely by "Why do I always have to check for errors?"
What is odd to me is that while yes, my Go code is more verbose than my node services - I always end up writing less Go for the same thing.
- _0w8t 5y agoMost perceived verboseness of Go comes not from the language or libraries but from the formater that does not allow to compress 3 lines of the error check down to single if err != nil { return err }
- throwaway894345 5y agoIf this is the case, I think the fault here lies with flawed perception and not the formatter.
- _0w8t 5y agoThe formater in many cases essentially doubles the number of lines. That requires to scroll much more frequently than with more compact formatting.
- throwaway894345 5y agoI understand, but I take issue with the implication that compression makes code more readable.
- pxue 5y agoCompressing this into 1 line provides absolutely nothing for the reader, in fact, it absolutely takes away reability.
- The_Colonel 5y agoReadability absolutely suffers when almost everything you see on your screen is boilerplate.
- thatswrong0 5y agoSeriously. I see so many functions in my code see that consist of 1 line of thing I actually care about followed by 3 lines of boilerplate if err not nil… over and over again. IMO if Go didn’t have its tooling, no one would care about it.
- pxue 5y agoi have the completely opposite take. every if err != nil; return err let's me mentally draw a line in the sand and not worry about exception handling for code above that line. It lets me start fresh and restart my mental model with the line of codes below the error handling block. it doesn't take years of writing Go to understand this, all you need is a open mind to how Go does things.
- thatswrong0 5y ago> It lets me start fresh and restart my mental model with the line of codes below the error handling block. My code is almost exactly: err := doThing() if err != nil { return err } err = doAnotherThing() if err != nil { return err } etc. There's no need for me to "start fresh". Each line of actual stuff doing might return an error which needs to be returned. That's it. It's a complete waste of space and inhibits readability to absolutely no benefit. And this is extremely common across our code base.
- Zababa 5y ago> if err != nil { return err } I'm far from a Go expert, but I feel like this line is a bit pointless, especially if you write it a lot. At this point, why not just not handle the error, or panic?
- closeparen 5y agoThe community strongly discourages panic.
- Zababa 5y agoI think a panic is still better than a chain of if err != nil { return err } that bubbles up and does nothing. Of course the best solution would be proper error handling but not everyone does that (and it's not always obvious what to do).
- merb 5y agoand most of the time you can't handle it anyway. I mean consider you are having a database query and it fails because the connection error'd (network split). what to do know? restart the network switches and wait? of course not in http you will just print a 5xx err and hope it comes back. in go you need to bubble up these errors to your middleware and handle it there.
- sangnoir 5y agoAt the very least, you have to log that error.
- closeparen 5y agoOur coding convention (and I happen to agree) is that the outermost layer to touch the error is the one to log it. In this case the HTTP handler, or perhaps a gRPC middleware. You don't want to be logging errors as you propagate them, or the logs will show the same error at a bunch of different call sites vs. one complete picture of what happened.
- stouset 5y agoWorse, if res, err := actually_important_bits(...); err != nil { return nil, err } The actually important bits are hidden in the middle of line noise. In the "common case" where `actually_important_bits` is just a simple function call it's not necessarily as bad, but the problem is when you have ten successive instances of this and one is slightly different. It's impossible to notice the important difference at a glance. For an industry that is just starting to understand that code is read hundreds of times more often than it's written, golang fails at the few things we actually know for certain about what makes it easier to understand code at a glance.
- jrockway 5y agoIf you're using `res`, then you aren't going to scope it to the `if` statement. That is maybe another problem, sometimes you have to do this: res, err := actuallyImportantBits(...) if err != nil { return nil, fmt.Errorf("actually important bits: %w", err) } And other times you have to do this: if err := actuallyImportantBits(...); err != nil { return nil, fmt.Errorf("actually important bits: %w", err) } The problem here is that you really don't want "err" to leak to the outer scope, so the second case is preferable from an absolute reliability and least-surprise perspective... but you can only do that in certain arbitrary cases. I think it's a bit of a wart. You can certainly handle "res" in an else block, or even write "err == nil" and handle it there, but that is surprsing. I would say it's simply not done, ever, but the Go codebase itself does it (src/go/parser/interface.go.ParseDir was the first example I found; but my search returned many screenfuls of candidates so there are probably more cases lurking in there). The fact that you have a choice is not ideal, basically. But, having actually important bits in if err := ...; err != nil {} blocks is not detrimental to readability. You will know how to read that after 5 minutes of reading any Go program.
- jerf 5y agoI almost never use that in my code, I almost always use res, err := ... if err != nil { return nil, fmt.Errorf("...: %w", err) } for that exact reason. This doesn't scan any worse than any other text-based code. (Page in the people selling visual programming here.)
- jerf 5y agoMy non-Go code is starting to look more like my Go code. One of the things Go taught me is that I was not being as careful about my errors as I should be. It can be argued that exception-based handling provides you a nice baseline default, but it makes it way to easy when doing network or system-type programming to thoughtlessly default to that, when you need to be thoughtfully defaulting to that. With sufficient care, exception-based programming and errors-as-values converge in the end anyhow in "code that treats errors correctly". But my exception-based code is a lot more informed by the errors-as-values approach now; a lot more try statements with catch statements that actually do something. Even Haskell's very nice Either monadic handling can make it too easy to be in the heat of the moment and not thinking about what the errors actually mean and what I can do about them. I don't think it's appropriate for every domain, which is why I qualified the code I tend to work on. But in those domains I'm thinking a lot more about how every single line of code can go wrong rather than leaning on default exception-based handling and expecting it all to work out.
- vlunkr 5y ago> One of the things Go taught me is that I was not being as careful about my errors as I should be. I identify with this so much. Especially when dealing with external things (file system, database, network) things can go wrong at nearly every step. And yeah, that means you have to check errors at every step, but it forces you to think about how you want to handle them, and what message you want to propagate when they happen. As a result, my Go code has very few unexpected errors in production.
- new_stranger 5y agoHard to overemphasize this. Handling errors is similar to the benefits that writing tests provides - slower upfront, but a more stable product gets shipped. Handling errors everywhere means problems are already solved before they happen. No 1 am pagerduty alerts because we didn't consider what would happen if a DNS server hung until we timed out and thought just wrapping in a try/catch and crashing was a good idea.
- closeparen 5y ago
- initplus 5y agoI'm a huge go proponent, but I do think the lack of map/filter/find etc. is a big downside to the language. I know how to write for (if item == myItem...) or a for (if item > max...) but it feels like a colossal waste of time every single time I write one of these loops. Go would benefit a lot more from some basic slice manipulation tools compared to features like generics that have actually made it into the language. The error handling I don't find so frustrating - it's great that it explicitly marks at the call site which functions can potentially error. It's like a working version of an inverse noexcept from C++. I do wish they would take a leaf out of swifts book though, with the try keyword. In practice error handling flow in my go programs is identical to using exceptions, it would be nice to have some helpers to make this default less verbose without losing the ability to distinguish at the call site which calls are error-y.
- grey-area 5y agoI imagine they'll add map/filter/find after generics are in. It's pretty easy to define some slice types though which include those in the meantime, type Slice []string and add some functions then just use your new type for collections.
- ohCh6zos 5y agoI suspect but do not know that multiple return values in a function combined with the inability to write functions against multiple return types like (T,error) will limit the usefulness of traditional iterators in Go. I'd love to be proved wrong as they'd really clean up my codebase though.
- grey-area 5y agoDepends what you're doing I guess, not everything needs to return an error, and errors can be accumulated and stored during certain operations and dealt with at the end.